166 lines
11 KiB
Markdown
166 lines
11 KiB
Markdown
|
|
## **部署数据库到程序中**
|
|||
|
|
|
|||
|
|
存储股票代码、价格和数量,并允许实时端读取数据显示——**内存数据库**是一个理想的选择,因为它们能提供**微秒级的读写速度**和**高并发访问能力**。
|
|||
|
|
|
|||
|
|
下面我为你推荐几个选项,并用一个表格快速对比它们的核心特性:
|
|||
|
|
|
|||
|
|
| 特性 | eXtremeDB (McObject) | Redis | DiceDB (兼容Redis协议) | 备注 |
|
|||
|
|
| :----------- | :----------------------------------------------- | :----------------------------------------- | :----------------------------------------------- | :----------------------------------------------------------- |
|
|||
|
|
| **主要特点** | 嵌入式、确定性实时事务、超低延迟 | 丰富数据结构、高并发、持久化选项 | 实时反应式、兼容Redis、支持QWATCH监听 | |
|
|||
|
|
| **数据模型** | 支持原生C/C++结构、SQL查询 | 键值(多种数据结构:Hash, Sorted Set等) | 兼容Redis协议 | Redis的Hash和Sorted Set非常适合存储和查询股票价格与数量的累积。 |
|
|||
|
|
| **持久化** | 支持(eXtremeLog, eXtremeHA) | RDB快照、AOF日志 | (搜索结果未明确提及) | 防止数据丢失。 |
|
|||
|
|
| **实时监听** | 事件通知机制 | Pub/Sub | **QWATCH命令** (类似SQL实时监听) | DiceDB的QWATCH允许客户端监听特定查询结果的变化,非常适合实时更新显示端。 |
|
|||
|
|
| **适用场景** | 对延迟和确定性要求极高的嵌入式系统、金融交易系统 | 缓存、会话存储、实时排行榜、频繁读写的应用 | 需要实时反应式更新的场景,如实时排行榜、实时分析 | |
|
|||
|
|
|
|||
|
|
🧩 选择建议
|
|||
|
|
|
|||
|
|
- 如果你的项目处于**嵌入式环境**(如工业控制器、电信设备)或对**事务的确定性和微秒级延迟有极致要求**(例如高频交易系统),**eXtremeDB**很合适。
|
|||
|
|
- 如果你需要一款**成熟、社区活跃、支持多种数据结构**的内存数据库,并且你的场景需要**高并发读写**和**丰富的持久化选项**,**Redis**会是一个可靠且常见的选择。
|
|||
|
|
- 如果你希望**兼容Redis协议**,同时又需要数据库能**主动将数据变化推送给客户端**,实现真正的**反应式实时更新**,那么可以关注一下**DiceDB**及其独特的**QWATCH**命令。
|
|||
|
|
|
|||
|
|
💡 实现思路简介 (以Redis为例)
|
|||
|
|
|
|||
|
|
假设使用Redis,通常可以这样设计:
|
|||
|
|
|
|||
|
|
1. **数据结构选择**:
|
|||
|
|
- 使用 **Hash** 来存储单个股票在不同价格下的累计数量。`Key` 可以是股票代码,`Field` 是价格,`Value` 是该价格的累计数量。
|
|||
|
|
- 同时,可以使用 **Sorted Set** 来为每个股票代码存储所有价格点,以便快速进行范围查询(例如查询某个价格区间的累计数量),成员是价格,分数也是价格。
|
|||
|
|
2. **更新累计数量**:
|
|||
|
|
- 接收到新的股票代码、价格、数量后,使用 `HINCRBY` 命令直接对对应Hash的Field进行原子增加操作。
|
|||
|
|
- 使用 `ZADD` 将价格添加到Sorted Set中(如果已存在则会更新分数,但这里分数和成员都是价格,所以不影响)。
|
|||
|
|
3. **实时端显示**:
|
|||
|
|
- 实时端可以通过 `HGETALL` 获取单个股票所有价格和累计数量。
|
|||
|
|
- 可以通过 `ZRANGEBYSCORE` 查询Sorted Set中某个价格区间的数据。
|
|||
|
|
- 如果需要实时推送数据变化,实时端可以**订阅** Redis 的 **Pub/Sub** 频道。每当有数据更新时,应用服务器在更新数据库后,再向特定频道发布一条消息,通知实时端数据已变化,实时端再主动查询更新显示。
|
|||
|
|
|
|||
|
|
📝 简单代码示例 (Redis + C++)
|
|||
|
|
|
|||
|
|
这里以Redis为例,使用`hiredis`客户端库:
|
|||
|
|
|
|||
|
|
cpp
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
#include <hiredis/hiredis.h>
|
|||
|
|
#include <iostream>
|
|||
|
|
#include <string>
|
|||
|
|
|
|||
|
|
void updateStockVolume(redisContext* context, const std::string& stockCode, double price, int volume) {
|
|||
|
|
// 将价格转换为字符串作为Hash的Field
|
|||
|
|
std::string priceStr = std::to_string(price);
|
|||
|
|
|
|||
|
|
// 使用HINCRBY累加数量
|
|||
|
|
redisReply* reply = (redisReply*)redisCommand(context,
|
|||
|
|
"HINCRBY %s %s %d",
|
|||
|
|
stockCode.c_str(), priceStr.c_str(), volume);
|
|||
|
|
|
|||
|
|
if (reply == nullptr || reply->type == REDIS_REPLY_ERROR) {
|
|||
|
|
std::cerr << "Update failed for " << stockCode << " at price " << price << std::endl;
|
|||
|
|
if (reply) {
|
|||
|
|
std::cerr << "Error: " << reply->str << std::endl;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 成功
|
|||
|
|
std::cout << "Updated " << stockCode << " at price " << price << ". New total volume: " << reply->integer << std::endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
freeReplyObject(reply);
|
|||
|
|
|
|||
|
|
// (可选) 将价格添加到Sorted Set中
|
|||
|
|
reply = (redisReply*)redisCommand(context,
|
|||
|
|
"ZADD %s %s %s",
|
|||
|
|
(stockCode + ":prices").c_str(), priceStr.c_str(), priceStr.c_str());
|
|||
|
|
|
|||
|
|
if (reply == nullptr || reply->type == REDIS_REPLY_ERROR) {
|
|||
|
|
std::cerr << "Failed to add price to sorted set for " << stockCode << std::endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
freeReplyObject(reply);
|
|||
|
|
|
|||
|
|
// (可选) 发布Pub/Sub消息通知数据变更
|
|||
|
|
// redisCommand(context, "PUBLISH stock-updates %s", stockCode.c_str());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询某只股票的所有价格和数量分布
|
|||
|
|
void queryStockData(redisContext* context, const std::string& stockCode) {
|
|||
|
|
redisReply* reply = (redisReply*)redisCommand(context, "HGETALL %s", stockCode.c_str());
|
|||
|
|
|
|||
|
|
if (reply && reply->type == REDIS_REPLY_ARRAY) {
|
|||
|
|
std::cout << "Data for " << stockCode << ":" << std::endl;
|
|||
|
|
for (size_t i = 0; i < reply->elements; i += 2) {
|
|||
|
|
std::string priceStr = reply->element[i]->str;
|
|||
|
|
std::string volumeStr = reply->element[i+1]->str;
|
|||
|
|
std::cout << " Price: " << priceStr << ", Total Volume: " << volumeStr << std::endl;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
std::cerr << "Query failed for " << stockCode << std::endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
freeReplyObject(reply);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int main() {
|
|||
|
|
// 连接到Redis服务器
|
|||
|
|
redisContext* context = redisConnect("127.0.0.1", 6379);
|
|||
|
|
if (context == nullptr || context->err) {
|
|||
|
|
if (context) {
|
|||
|
|
std::cerr << "Connection error: " << context->errstr << std::endl;
|
|||
|
|
} else {
|
|||
|
|
std::cerr << "Can't allocate redis context" << std::endl;
|
|||
|
|
}
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 示例:更新数据
|
|||
|
|
updateStockVolume(context, "AAPL", 150.5, 100);
|
|||
|
|
updateStockVolume(context, "AAPL", 150.5, 50); // 再次更新同一价格
|
|||
|
|
updateStockVolume(context, "GOOGL", 2800.0, 200);
|
|||
|
|
|
|||
|
|
// 示例:查询数据
|
|||
|
|
queryStockData(context, "AAPL");
|
|||
|
|
|
|||
|
|
// 关闭连接
|
|||
|
|
redisFree(context);
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
(**注意**:以上代码示例需要安装`hiredis`库,并在编译时链接。)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
在构建实时应用,比如处理股票数据时,Redis的发布订阅(Pub/Sub)和DiceDB的QWATCH都是值得考虑的工具。它们在**易用性**和**响应速度**上各有特点。下面我将从几个方面对它们进行比较,并提供一些选型建议。
|
|||
|
|
|
|||
|
|
为了让你能快速了解两者的主要区别,我准备了一个表格:
|
|||
|
|
|
|||
|
|
| 特性 | Redis Pub/Sub | DiceDB QWATCH |
|
|||
|
|
| :----------- | :------------------------------------------------- | :----------------------------------------------------------- |
|
|||
|
|
| **数据模型** | 基于频道(Channel)或模式(Pattern)的简单消息传递 | **类SQL查询**,可基于键值内容进行复杂过滤和排序 |
|
|||
|
|
| **连接方式** | TCP长连接,二进制安全协议 | 兼容Redis协议(默认端口7379),同样支持TCP长连接 |
|
|||
|
|
| **易用性** | **API简单直观**,学习成本低,客户端支持广泛 | **查询更灵活**,但需学习类SQL语法,目前客户端生态和文档相对较弱 |
|
|||
|
|
| **响应速度** | **极低延迟**(微秒级),成熟稳定 | **多线程架构**(Shared-nothing),理论上有更高吞吐量,但仍在开发中,稳定性待验证 |
|
|||
|
|
| **实时性** | 消息**即时推送**,但无状态追溯能力 | **反应式通知**,查询结果变化时主动推送新结果集 |
|
|||
|
|
| **可靠性** | **无持久化**,消息可能丢失 | (搜索结果未明确提及持久化机制) |
|
|||
|
|
| **生产就绪** | **久经考验**,广泛应用于生产环境 | **目前不推荐用于生产**,仍处于开发阶段 |
|
|||
|
|
|
|||
|
|
🧩 选型建议
|
|||
|
|
|
|||
|
|
了解了两者的区别后,你可以根据具体场景来选择:
|
|||
|
|
|
|||
|
|
- **追求稳定成熟,业务逻辑基于频道或简单模式,且能接受无持久化**:**Redis Pub/Sub** 是一个可靠的选择。特别是在你需要广泛应用的生产环境、需要大量客户端支持,或者业务模型简单直接的场景下。
|
|||
|
|
- **需要更灵活的、基于数据内容的实时通知,并且愿意尝试新技术**:可以关注 **DiceDB 的 QWATCH**。它更适合用于实时排行榜、实时监控和需要复杂过滤条件的场景。**但目前仅建议在测试环境或特定非关键业务中评估**。
|
|||
|
|
|
|||
|
|
💡 决策前的重要检查项
|
|||
|
|
|
|||
|
|
在做最终决定前,建议你:
|
|||
|
|
|
|||
|
|
1. **进行性能测试(POC)**:无论是Redis还是DiceDB,**务必在你的实际硬件环境和网络条件下进行性能测试**。模拟真实的业务数据量和并发连接,测量它们的延迟和吞吐量是否满足你的要求。
|
|||
|
|
2. **评估数据持久化需求**:如果你的股票数据**不允许任何丢失**,单纯使用Redis Pub/Sub可能不够。你需要考虑:
|
|||
|
|
- 对于Redis:配合 **RDB或AOF持久化机制**(但这主要针对数据库本身,Pub/Sub消息仍是临时的),或者将重要消息**额外写入持久化存储**。
|
|||
|
|
- 对于DiceDB:需**仔细查阅其最新文档**,了解其对数据持久化的支持情况。
|
|||
|
|
3. **考虑技术风险和团队技能**:
|
|||
|
|
- **Redis** 风险较低,资源丰富,开发者容易上手。
|
|||
|
|
- **DiceDB** 作为较新的项目,本身仍在开发中,可能会遇到bug、功能变更或社区支持跟不上的情况。团队也需要时间学习其特性。
|