1. 项目概述:从“分布式键值存储”到“实时数据服务引擎”的蜕变
最近在梳理团队内部的数据架构选型时,一个名为 DingoDB 的项目引起了我的注意。准确地说,我关注的是其核心存储组件dingodb/dingo-store。乍一看仓库名,很容易让人联想到又一个“分布式键值存储”系统,毕竟市面上已经有了 RocksDB、TiKV 等成熟方案。但当我深入其设计文档和代码实现后,发现它的野心远不止于此。dingo-store的定位,是一个为实时分析(Real-time Analytics)和混合事务/分析处理(HTAP)场景而生的高性能、低延迟分布式存储引擎。它试图在传统 OLTP 数据库的事务一致性与大数据分析系统的高吞吐、低延迟查询之间,架起一座桥梁。这让我想起了过去几年在构建实时推荐、风控和运营看板时,那种在多个存储系统间“缝缝补补”的酸楚——事务数据在 MySQL,分析数据在 HBase 或 ClickHouse,中间靠 Kafka 和 Flink 做流式同步,架构复杂,运维成本高,端到端延迟还难以控制。dingo-store的出现,似乎提供了一种“All in One”的新思路,它通过一套存储同时支持点查、范围扫描和聚合分析,并且原生拥抱向量计算,这正好切中了当前 AI 应用对“向量数据库”和“实时特征库”的迫切需求。接下来,我将从一个一线架构师的视角,拆解dingo-store的核心设计、技术选型背后的权衡,并分享如何在实际场景中评估和试用它。
2. 核心架构与设计哲学拆解
2.1 存储引擎的“三驾马车”:LSM-Tree、Raft 与向量索引
dingo-store的架构基石可以概括为三个关键技术:LSM-Tree(日志结构合并树)、Raft 一致性协议,以及为向量数据优化的索引结构。选择 LSM-Tree 而非 B+Tree,是其面向写优化和顺序 I/O 性能的关键决策。在实时数据场景下,写入往往是爆发式的(如用户行为日志、IoT 设备上报),LSM-Tree 通过将随机写转换为顺序追加写(WAL + MemTable),并异步进行 Compaction,能够轻松应对高吞吐写入。但 LSM-Tree 的读放大(Read Amplification)问题也众所周知。dingo-store对此的优化在于其多级 Compaction 策略和 Bloom Filter 的精细使用。例如,在内存表(MemTable)达到阈值后,会 Flush 成不可变的 SSTable(Sorted String Table),后台线程会根据数据的热度和重叠度,进行分层(Leveled)或分级(Tiered)的 Compaction,这个过程会合并重复键、删除标记,并重新组织数据以优化读取路径。
一致性方面,dingo-store选择了 Raft 而非 Paxos。Raft 以其易于理解和实现的特性著称,它将共识问题分解为领导选举、日志复制和安全性三个相对独立的子问题。在dingo-store中,数据被分片(Region)存储,每个 Region 的多个副本(通常为 3 或 5 个)构成一个 Raft Group。所有写请求都必须通过 Leader 副本,由 Leader 将操作追加到自己的日志中,然后并行复制到 Follower,在获得多数派确认后才提交并应用到状态机(即存储引擎)。这套机制保证了数据的强一致性和高可用性——即使少数节点宕机,集群仍可正常服务;Leader 宕机后,Follower 能通过选举快速产生新的 Leader。
最让我感兴趣的是它对向量数据的原生支持。这不仅仅是简单地将向量存储为二进制大对象(BLOB)。dingo-store在存储层集成了诸如 HNSW(Hierarchical Navigable Small World)、IVF(Inverted File Index)等近似最近邻(ANN)索引算法。这意味着,当你插入一个高维向量(如图像特征、文本嵌入)时,存储引擎会同步构建或更新对应的向量索引结构。查询时,可以直接在存储层进行高效的向量相似度搜索,避免了传统方案中需要将数据拉到计算层(如 Spark、Flink)再建索引的额外开销和延迟。这种“存算一体”的设计,是它宣称低延迟实时分析的关键。
2.2 数据模型与 API 设计:超越简单的 Key-Value
虽然底层是键值存储,但dingo-store向上暴露的数据模型更为丰富。它支持类似关系型的表(Table)概念,表有预定义的 Schema,包含标量列(整数、字符串等)和向量列。这使其更接近一个分布式的、支持向量的“宽表”存储。在 API 层面,它提供了多维度的访问接口:
- 点查(Point Get):通过主键(可以是标量或向量)快速检索单行数据。这是 OLTP 类操作的基础。
- 范围扫描(Range Scan):通过主键范围进行迭代查询,支持前缀匹配,适用于时序数据或范围查询场景。
- 向量搜索(Vector Search):基于向量列进行 K-最近邻(K-NN)或近似最近邻搜索,可结合标量过滤条件(如
WHERE category = ‘A’ AND vector_distance < 0.2)。 - 批量操作(Batch Put/Get):支持高性能的批量写入和读取,提升吞吐量。
这种设计使得应用层无需再维护复杂的多系统集成。例如,一个商品推荐系统,可以将商品 ID、属性、实时销量(标量)和商品特征向量存储在同一张表、同一行中。一次查询既能通过商品 ID 快速获取详情(点查),也能根据用户画像向量找到最相似的商品(向量搜索),还能筛选出特定类目下销量最高的商品(标量过滤+向量搜索)。所有操作都在一次存储引擎的交互中完成,极大地简化了应用逻辑并降低了延迟。
注意:这种融合模型也带来了新的挑战,比如资源隔离。一个消耗大量 CPU 的向量搜索查询,可能会影响同一 Region 上点查的延迟。
dingo-store通常通过资源组(Resource Group)或查询优先级(Query Priority)机制来进行一定程度的隔离,但在设计数据分片(Sharding)策略时,仍需考虑工作负载的特性,尽量避免“热点”Region。
3. 部署、配置与核心操作实战
3.1 集群部署模式与选型建议
dingo-store支持多种部署模式,以适应从开发测试到生产环境的不同需求。
All-in-One 单机模式:所有组件(存储节点、元数据服务、协调节点)运行在单个进程中。这仅用于功能验证和开发调试,绝对不能用于生产环境。启动命令通常很简单,如
./dingodb_store --config config.yaml。多进程分布式模式:这是生产环境的推荐模式。核心组件包括:
- Store Node:数据存储节点,负责实际数据的读写、Raft 副本管理、Compaction 等。是资源(CPU、内存、磁盘)消耗的主要部分。
- Coordinator:协调节点,负责集群元数据管理、负载均衡、Region 调度(分裂、合并、迁移)和全局时钟服务(TSO)。它本身不存储用户数据,但需要高可用。
- Proxy / SDK:客户端访问入口。应用通过 SDK 或独立的 Proxy 服务连接到集群。SDK 会从 Coordinator 获取数据路由信息(哪个 Key 在哪个 Store Node 的哪个 Region),然后直接与对应的 Store Node 通信,避免单点瓶颈。
在生产部署时,我建议采用至少 3 个 Coordinator 节点构成一个高可用组(使用 Raft 保证元数据一致性)。Store Node 的数量则取决于数据量和吞吐需求,初期可以从 3 个开始,随着数据增长线性扩展。每个 Store Node 应部署在独立的物理机或虚拟机上,并配备高性能 SSD(如 NVMe)以获得最佳的 I/O 性能,因为 LSM-Tree 的 Compaction 是 I/O 密集型操作。
网络配置至关重要。所有节点间需要低延迟、高带宽的网络互通。通常需要设置内部域名或静态 IP,并在配置文件中明确指定。防火墙需开放节点间用于 Raft 通信、数据迁移、心跳检测的端口,以及客户端访问 Proxy/SDK 的端口。
3.2 关键配置参数解析与调优
配置文件是性能调优的核心。以下是一些关键参数及其影响:
存储引擎相关 (store配置块):
region.max-size:单个 Region 的最大容量(如 96MB)。当 Region 数据量超过此阈值时,会自动触发分裂(Split)。设置太小会导致 Region 数量过多,增加元数据管理和 Raft 开销;设置太大会导致单个 Region 负载过重,不利于并行化和负载均衡。需要根据平均键值大小和访问模式进行权衡。raft.max-size-per-msg:Raft 日志条目最大大小。影响日志复制的吞吐量。对于大向量插入场景,可能需要调大此值。rocksdb.write-buffer-size/max-write-buffer-number:MemTable 的大小和数量。更大的 Write Buffer 可以吸收更多的突发写入,减少 Flush 频率,但会增加内存占用和故障恢复时间。rocksdb.level0-file-num-compaction-trigger:触发 L0 Compaction 的 SST 文件数阈值。L0 文件是直接由 MemTable Flush 产生,未排序。此值设置较小会频繁触发 Compaction,增加写放大;设置较大会增加读放大(因为读可能需要遍历多个 L0 文件)。需要监控rocksdb.stats中的stalls(写停顿)和get延迟来调整。
向量索引相关 (vector配置块):
index.type:选择 HNSW 或 IVF 等算法。HNSW 通常查询精度和速度更好,但构建耗时和内存占用更高;IVF 构建更快,内存更省,但需要基于数据分布训练聚类中心。hnsw.efConstruction/M:HNSW 索引的构建参数。M影响图的连通性和内存占用,efConstruction影响构建时的搜索范围,值越大精度越高,构建越慢。这需要在索引质量和构建成本间取得平衡。ivf.nlist:IVF 索引的聚类中心数量。影响搜索时需要遍历的聚类数量,同样需要在精度和速度间权衡。
一个实用的调优流程是:先在测试环境用代表性数据负载进行压测,观察 Grafana 监控面板(如果集成)中的关键指标:写吞吐(Ops/Sec)、写延迟(P99)、读延迟(P99)、Compaction 压力、CPU/内存/磁盘 IO 使用率。然后有针对性地调整上述参数。通常遵循“先默认,后微调”的原则,优先调整最可能成为瓶颈的参数。
3.3 基础数据操作与向量搜索示例
假设我们使用 Python SDK 操作一个存储商品信息的表。
# 1. 连接集群 from dingodb import DingoDB client = DingoDB(user='user', password='passwd', host=['coordinator1:port', 'coordinator2:port']) # 2. 创建包含向量列的表 table_name = "product_features" schema = [ {"name": "product_id", "type": "INTEGER", "primaryKey": True, "autoIncrement": False}, {"name": "category", "type": "VARCHAR", "length": 50}, {"name": "price", "type": "DOUBLE"}, {"name": "feature_vector", "type": "FLOAT_VECTOR", "dimension": 128, "indexType": "HNSW"} # 指定向量维度和索引类型 ] client.create_table(table_name, schema) # 3. 插入数据(混合标量和向量) import numpy as np data = [ [1001, "electronics", 299.99, np.random.random(128).astype(np.float32).tolist()], [1002, "clothing", 59.99, np.random.random(128).astype(np.float32).tolist()], ] client.insert(table_name, data) # 4. 点查 result = client.get(table_name, [1001]) print(f"Product 1001: {result}") # 5. 向量相似度搜索 query_vector = np.random.random(128).astype(np.float32).tolist() search_params = {"search_params": {"ef": 100}} # HNSW搜索参数 # 查找最相似的5个商品,并过滤类别为'electronics' results = client.vector_search( table_name, query_vector, top_k=5, search_params=search_params, filter="category = 'electronics'" ) for item in results: print(f"Similar product ID: {item['product_id']}, distance: {item['distance']}")这个例子展示了从建表、插入到查询的完整流程。关键在于,向量搜索和标量过滤在存储层被合并执行,这比先过滤再搜索或先搜索再过滤的效率要高得多。
4. 性能压测、监控与问题排查
4.1 设计有意义的性能基准测试
评估dingo-store是否适合你的业务,不能只看官方数据,必须用自己的数据和访问模式进行压测。压测应覆盖以下场景:
- 纯写入负载:模拟日志灌入场景。使用工具(如自己编写的脚本,或适配的 YCSB 工具)持续写入随机或有序的键值对(可包含向量)。监控指标:写入吞吐量(ops/sec)、写入延迟分布(P50, P90, P99)、磁盘 I/O 利用率、Compaction 线程 CPU 使用率。观察随着数据量增长,性能是否平滑,有无剧烈波动或写入停顿(Write Stall)。
- 读写混合负载:模拟在线业务场景。配置一定比例的读(点查、范围扫描)和写操作。监控指标:读写吞吐、读写延迟、CPU 综合使用率。特别注意在持续写入背景下,读延迟是否会劣化。
- 向量搜索负载:模拟 AI 查询场景。并发执行向量相似度搜索,可搭配不同 selectivity 的标量过滤条件。监控指标:查询吞吐(QPS)、查询延迟(P99)、向量索引内存占用。测试在不同数据集规模(如 100万、1000万向量)下的性能表现。
- 稳定性与故障恢复:在压测过程中,随机重启某个 Store Node 或 Coordinator 节点,观察集群是否能够自动恢复服务,数据是否一致,性能影响持续时间。
压测数据应尽可能接近生产环境的数据特征,如键的大小、分布(是否热点),向量维度、分布等。压测时间应足够长(如持续数小时),以观察 Compaction 对长期性能的影响。
4.2 核心监控指标与告警设置
一个可观测的集群是稳定运行的保障。dingo-store通常暴露了丰富的 Prometheus 指标。以下是我认为必须监控的核心指标:
| 监控类别 | 关键指标 | 说明与告警阈值建议 |
|---|---|---|
| 集群健康 | cluster_state | 集群状态(1正常,0异常)。任何非1状态立即告警。 |
region_count | Region 总数。短期内剧烈增长可能预示分裂异常或负载不均。 | |
store_liveness | Store Node 存活状态。节点宕机立即告警。 | |
| 性能 | request_duration_seconds | 请求延迟,按类型(get, put, scan, vector_search)区分。P99延迟超过 SLA(如50ms)告警。 |
request_throughput | 请求吞吐量。大幅下跌可能预示瓶颈或故障。 | |
| 存储引擎 | rocksdb_stall_percentage | 写停顿比例。持续大于0表示写入被 Compaction 阻塞,需调优或扩容。 |
rocksdb_compaction_pending | 待 Compaction 任务数。持续高位表示 Compaction 跟不上写入速度。 | |
rocksdb_memtable_size | MemTable 大小。接近配置上限可能触发频繁 Flush。 | |
| 资源 | process_cpu_seconds_rate | CPU 使用率。持续高于80%可能成为瓶颈。 |
process_resident_memory_bytes | 内存占用。关注是否持续增长(潜在内存泄漏)或接近物理内存(导致Swap)。 | |
disk_usage_percentage | 磁盘使用率。超过85%需考虑清理或扩容。 | |
| Raft | raft_log_lag | Follower 日志落后 Leader 的条数。持续落后可能影响可用性。 |
raft_propose_duration | 提案延迟。延迟高可能网络或磁盘有问题。 |
建议使用 Grafana 搭建监控面板,将上述指标可视化,并设置相应的告警规则接入到钉钉、企业微信等告警平台。
4.3 典型问题排查实录
在实际测试和运维中,你可能会遇到以下问题:
问题一:写入速度突然变慢,客户端出现超时。
- 排查思路:
- 查看监控:首先检查
rocksdb_stall_percentage和rocksdb_compaction_pending。如果很高,说明写停顿了。 - 检查磁盘:通过
iostat -x 1查看磁盘利用率(%util)和响应时间(await)。如果磁盘已饱和(%util持续接近100%),说明 I/O 是瓶颈。 - 检查内存:确认
rocksdb_block_cache_size和 MemTable 总大小是否配置合理。如果 Block Cache 太小,会导致频繁读盘,影响 Compaction 和写入速度。
- 查看监控:首先检查
- 解决方案:
- 短期:如果磁盘 I/O 饱和,可能是 Compaction 过于频繁。可以临时调大
level0_file_num_compaction_trigger和write_buffer_size,减少 Compaction 频率,但会牺牲一些读性能。 - 长期:升级硬件,使用更高性能的 SSD(如 NVMe)。优化 Compaction 策略,考虑使用
universalcompaction 模式(如果支持)来减少写放大。增加 Store Node 节点,分散写入压力。
- 短期:如果磁盘 I/O 饱和,可能是 Compaction 过于频繁。可以临时调大
问题二:向量搜索查询延迟不稳定,偶尔出现长尾延迟。
- 排查思路:
- 检查查询负载:是否并发查询过高?监控
vector_search的 QPS 和并发连接数。 - 检查资源竞争:观察在查询延迟高的时间段,CPU 使用率是否也飙高,是否有后台 Compaction 或 Region 迁移任务在运行。
- 检查索引参数:对于 HNSW 索引,查询时指定的
ef参数是否过小?过小的ef会影响搜索精度和速度(可能需要回溯更多)。
- 检查查询负载:是否并发查询过高?监控
- 解决方案:
- 使用资源组隔离线上查询任务和后台管理任务。
- 优化查询的
ef参数,在精度和速度间找到平衡点。可以在测试环境绘制ef值与召回率、延迟的关系曲线。 - 考虑将向量索引加载到更快的存储介质(如内存映射文件或 PMem),如果索引文件在普通磁盘上,I/O 可能成为瓶颈。
问题三:集群扩容后,负载没有均匀分布。
- 排查思路:
- 检查 Region 分布:通过管理命令或监控查看每个 Store Node 上的 Region 数量。是否严重不均?
- 检查热点 Key:如果业务存在热点 Key(如某个热门商品ID),会导致该 Key 所在的 Region 成为热点,即使 Region 数量均衡,负载也不均衡。
- 解决方案:
- 等待 Coordinator 的自动调度。Coordinator 会周期性地检查节点负载,并尝试将 Region 从高负载节点迁移到低负载节点。
- 如果存在明确的热点,可以考虑从业务层面进行优化,例如对热点 Key 进行打散(Salting),或者在表设计时使用更合理的分片键(Shard Key),让写入和查询能分散到多个 Region。
5. 适用场景分析与选型对比
5.1 哪些场景适合使用 DingoDB Store?
经过上面的分析,dingo-store的核心优势在于“混合负载”和“实时分析”。以下几类场景是其发挥价值的舞台:
- 实时特征平台(Feature Store):这是我认为最契合的场景。机器学习模型需要最新的用户特征和物品特征进行实时推理。传统方案需要从 OLTP 数据库、消息队列、离线数仓等多个源头拼接特征,流程长、延迟高。
dingo-store可以作为一个统一的实时特征库,同时存储标量特征(用户画像、商品属性)和向量特征(Embedding),支持高吞吐更新和低延迟点查、向量检索,极大简化特征管线。 - 融合搜索与推荐系统:用户搜索时,既要基于文本匹配(标量过滤),又要基于语义相似度(向量搜索)。
dingo-store可以在一张表内同时支持这两种索引,实现混合查询,避免跨系统 JOIN 带来的复杂度和延迟。 - 实时数据湖/湖仓一体入口:作为流式数据(如 Kafka 数据)的实时摄入层,在提供低延迟查询服务的同时,可以将数据定期快照到更廉价的离线存储(如 HDFS、S3)中进行深度分析,形成实时与离线联动的架构。
- 物联网(IoT)时序数据监控:设备上报的带有时序标签和指标向量的数据,可以按设备ID分片,高效支持按时间范围扫描和特定模式的向量异常检测。
5.2 与同类系统的横向对比
为了更清晰地定位,我们可以将其与几个知名系统进行简单对比:
| 特性/系统 | DingoDB Store | TiKV | Apache HBase | Elasticsearch |
|---|---|---|---|---|
| 核心数据模型 | 强 Schema 宽表(标量+向量) | 分布式事务键值 | 稀疏列族宽表 | JSON 文档 |
| 一致性模型 | 强一致性(Raft) | 强一致性(Raft) | 最终一致性/强一致性(Region级别) | 最终一致性(多副本) |
| 索引支持 | 主键索引 + 向量索引 | 主键索引 | 仅行键索引 | 倒排索引 + 向量索引(插件) |
| 典型查询 | 点查、范围扫描、向量混合搜索 | 点查、范围扫描 | 点查、范围扫描、过滤器 | 全文检索、聚合、向量搜索 |
| 写入优化 | LSM-Tree,高吞吐写入 | LSM-Tree,高吞吐写入 | LSM-Tree,高吞吐写入 | Lucene 段合并,写入吞吐相对较低 |
| 场景侧重 | HTAP,实时分析,AI 数据平台 | OLTP 基础存储,TiDB 的存储引擎 | 海量数据存储,随机读写 | 全文搜索,日志分析,可观测性 |
| 向量原生支持 | 是,核心特性 | 否(可通过外部方案) | 否 | 是(通过插件,非原生) |
总结一下选型建议:
- 如果你的核心需求是“强事务关系型数据库”,那么 TiDB(基于 TiKV)或传统 RDBMS 更合适。
- 如果你的核心需求是“海量半结构化数据存储与随机访问”,HBase 或其云托管版(如 BigTable)依然是非常成熟的选择。
- 如果你的核心需求是“文本搜索与日志分析”,Elasticsearch 的生态和工具链更完善。
- 而如果你的场景是“需要同时处理高速写入、实时点查和复杂的向量/标量混合分析”,特别是 AI 驱动的应用,那么
dingo-store所代表的融合架构就非常值得深入评估和尝试。它试图用一个系统解决多个问题,虽然增加了系统的复杂性,但也为架构简化带来了新的可能性。在技术选型时,没有银弹,关键在于认清自己业务当前和未来最核心的负载特征。