
为实时应用而生的数据库DiceDB 的愿景与四大核心设计决策【免费下载链接】dicedbOpen-source, low-latency key/value engine built on Valkey with query subscriptions and hierarchical storage tiers.项目地址: https://gitcode.com/GitHub_Trending/dic/dicedb在实时系统的世界里前端与后端都在以前所未有的速度演进支撑它们的底层基础设施也必须随之进化。传统的键值数据库擅长缓存与存储却难以满足现代实时应用数据一变、结果即达的诉求。DiceDB 正是带着这样一项使命而生的开源键值数据库专门为构建与规模化实时应用而设计将查询订阅reactive作为一等公民。本文以 hello-world.md 为起点结合当前仓库的源码与文档深入拆解 DiceDB 的核心设计决策并通过真实示例展示如何用它对数据变化作出实时响应。DiceDB 的使命重新思考实时应用的存储层DiceDB is being revived with a bold new mission: to optimize and build a database that truly meets the demands of real-time applications.这是 DiceDB 启动博客 hello-world.md 的开篇宣言。它传达的核心理念是实时系统无论是前端还是后端的需求正在快速演化而数据库作为应用的地基应当提供健壮且高性能的解决方案专门为实时场景而工程化。DiceDB 并非要成为又一个通用型数据库而是把焦点收敛到实时应用的核心痛点上当数据发生变化时客户端如何以最低延迟、最少轮询地感知到这种变化围绕这一痛点项目确定了四项关键设计决策key design decisions它们共同构成了 DiceDB 的架构灵魂响应式reactivity——订阅一个查询让结果集被推送到客户端原生、一等公民的 JSON 支持——面向读写与查询纯内存引擎——无磁盘开销网络往返延迟极低为现代硬件而构建——榨取硬件的每一分性能。下面我们逐一展开并用仓库中的真实实现加以印证。设计决策一响应式查询订阅Reactivitysubscribe to a SQL query and get results pushed to clients——这是 DiceDB 最与众不同的特性。传统数据库中客户端需要不断轮询poll才能拿到最新数据而 DiceDB 希望客户端订阅一个查询当底层数据发生影响查询结果的变更时新的结果集会被主动推送给订阅方。从源码看订阅机制的实现响应式的落地依赖一个专门的 Watch Manager 组件其实现位于 internal/watchmanager/watch_manager.go。它维护了三张核心映射表querySubscriptionMap从 Key 到一组指纹fingerprint的映射记录哪些查询正关注哪些键tcpSubscriptionMap从指纹到一组客户端通道的映射记录每个查询订阅被哪些客户端订阅fingerprintCmdMap从指纹到原始命令DiceDBCmd的映射用于在事件触发时重新执行查询。事件驱动逻辑体现在handleWatchEvent中当某个键被修改时Watch Manager 先检查是否有查询订阅关注该键再通过affectedCmdMap判断哪个写命令会影响哪个读命令var affectedCmdMap map[string]map[string]struct{}{ dstore.Set: {dstore.Get: struct{}{}}, dstore.Del: {dstore.Get: struct{}{}}, dstore.Rename: {dstore.Get: struct{}{}}, dstore.ZAdd: {dstore.ZRange: struct{}{}}, dstore.PFADD: {dstore.PFCOUNT: struct{}{}}, }这段代码的含义是例如当执行SET时只有GET类查询的订阅者需要被通知当执行ZADD时只有ZRANGE类查询的订阅者需要被通知。这种命令影响映射机制避免了无关的查询重算是响应式引擎高效的关键设计。随后notifyClients会把重新执行后的命令推送到所有订阅该指纹的客户端通道由客户端完成查询执行与结果展示。Q.WATCHSQL 风格的订阅命令响应式能力通过Q.WATCH命令对外暴露其完整规范见 QWATCH.md。它的定位是基于底层数据变化向客户端提供实时更新的新特性——工作方式类似SUBSCRIBE但订阅的是 SQL 风格的查询DSQL而非单纯的频道名。Q.WATCH支持 TCP-RESP、HTTP 与 WebSocket 三种协议语法为Q.WATCH dsql-queryDSQL 查询语法如下SELECT $key, $value WHERE condition ORDER BY field [ASC | DESC] LIMIT n各子句的要点子句说明SELECT指定返回字段支持$key键名、$value键值及$value.attr值的属性WHERE可选过滤条件支持比较运算符 !、逻辑运算符AND/OR、括号分组以及LIKE pattern键模式匹配ORDER BY可选按字段排序ASC/DESCLIMIT可选限制返回条数从 internal/watchmanager/watch_manager.go 的源码结构看DiceDB 为每个订阅计算一个指纹fingerprint作为唯一标识并在订阅/退订时对上述三张映射表进行增删——这正是实现初始结果集下发 持续监控数据源 数据变化时重新评估查询这一行为模型的底层支撑。实战示例实时排行榜QWATCH.md 给出了一个非常直观的场景游戏匹配的实时排行榜。假设我们要监控比赛 100 中所有玩家的得分只关心得分大于 10 的玩家并按分数降序取前三名127.0.0.1:7379 Q.WATCH SELECT $key, $value WHERE $key like match:100:* AND $value 10 ORDER BY $value DESC LIMIT 3接下来模拟几轮数据更新每个玩家的分数存储在match:100:user:userID这样的键中SET match:100:user:0 5——得分为 5不满足 10订阅无响应SET match:100:user:1 15——推送结果[[match:100:user:1, 15]]SET match:100:user:2 20——推送结果[[match:100:user:2, 20], [match:100:user:1, 15]]SET match:100:user:3 12——推送结果[[match:100:user:2, 20], [match:100:user:1, 15], [match:100:user:3, 12]]SET match:100:user:4 25——推送结果[[match:100:user:4, 25], [match:100:user:2, 20], [match:100:user:1, 15]]SET match:100:user:0 30——推送结果[[match:100:user:0, 30], [match:100:user:4, 25], [match:100:user:2, 20]]。可以看到客户端完全不需要轮询每一次写入都会触发结果集的实时重算与推送排行榜始终与最新数据保持一致。仓库中还提供了完整的教程 realtime-leaderboard.mdx 与 Go 示例代码 examples/leaderboard-go/main.go供想动手实践的读者参考。使用建议来自 QWATCH.md 的最佳实践尽可能在LIKE子句中使用精确的键模式缩小查询监控范围保持WHERE条件尽量简单以获得更好的性能在应用层保证比较操作的类型一致性避免运行时错误。设计决策二原生、一等公民的 JSON 支持native, first-class JSON support for reads, writes, and queries——JSON 不是被当作普通字符串塞进键值而是作为一种一等公民的数据类型被原生支持可以针对 JSON 内部的路径path进行细粒度的读写与查询。在命令注册中心 internal/eval/commands.go 中可以看到一整套完整的JSON.*命令族均已完成向新评估机制NewEval的迁移基础读写JSON.SET、JSON.GET、JSON.DEL、JSON.FORGET类型与结构JSON.TYPE、JSON.STRLEN、JSON.OBJLEN、JSON.OBJKEYS、JSON.RESP数组操作JSON.ARRAPPEND、JSON.ARRINSERT、JSON.ARRPOP、JSON.ARRTRIM、JSON.ARRLEN、JSON.ARRINDEX数值操作JSON.NUMINCRBY、JSON.NUMMULTBY其他JSON.TOGGLE、JSON.CLEAR、JSON.DEBUG、JSON.INGEST。例如JSON.SET的元信息定义internal/eval/commands.gojsonsetCmdMeta DiceCmdMeta{ Name: JSON.SET, Info: JSON.SET key path json-string Sets a JSON value at the specified key. Returns OK if successful., NewEval: evalJSONSET, Arity: -3, KeySpecs: KeySpecs{BeginIndex: 1}, IsMigrated: true, }KeySpecs{BeginIndex: 1}表明第一个参数是键名这与所有标准命令保持一致。每个JSON.*命令都接受key [path]形式的参数这意味着你可以只更新一个 JSON 文档中的某个嵌套字段而无需读取整个文档——这在实时应用中是至关重要的性能特性。此外仓库中还提供了大量 JSON 相关测试用例如 tests0/json_test.go以及每一条 JSON 命令的独立文档位于 docs/src/content/docs/commands 与 docs/src/_skipped_commands足见该项目对 JSON 支持的重视程度。设计决策三纯内存、低延迟的引擎in-memory, no disk overhead and every operation completes in 8 ms over the network——DiceDB 是一个内存优先的键值引擎核心数据结构全部驻留内存从而消除了磁盘 I/O 带来的开销。这一点从项目描述 low-latency key/value engine 也能得到印证。DiceDB 的核心存储抽象是Store其实现位于 internal/store/store.go配套的过期管理、淘汰策略与 AOF 持久化逻辑分别位于 internal/store/expire.go、internal/store/eviction.go 与 internal/store/aof.go。内存中的对象通过 internal/object/object.go 统一封装并附带类型编码type/encoding信息见 internal/object/typeencoding.go。值得一提的是无磁盘开销并不意味着完全没有持久化能力——仓库中的 WALWrite-Ahead Log模块 internal/wal/wal.go 与 AOF 模块表明DiceDB 在追求内存极致性能的同时也为可靠性保留了持久化路径用户可以按需启用。设计决策四为现代硬件而构建built for modern hardware and squeezes every single ounce of performance——要支撑亚毫秒级的实时推送仅仅快还不够还必须充分利用现代硬件的全部潜力。这一点在 DiceDB 的网络与并发架构中体现得淋漓尽致原生系统调用服务器在 internal/server/ironhawk/main.go 中直接使用syscall.Socket、syscall.Bind、syscall.Listen、syscall.Accept等系统调用并将监听套接字设置为非阻塞模式syscall.SetNonblock避免了高层网络库带来的额外抽象开销多路复用 I/O仓库内置了iomultiplexer模块在 Linux 上基于epollinternal/iomultiplexer/epoll_linux.go在 macOS 上基于kqueueinternal/iomultiplexer/kqueue_darwin.go通过事件驱动的方式让单个线程高效管理海量连接IO 线程模型internal/server/ironhawk/iothread.go 中每个客户端连接对应一个独立的 IO 线程配合 IO 线程管理器iothread_manager.go与分片shard架构internal/shard、internal/shardmanager实现了连接的并行处理与数据的水平切分让多核硬件被充分利用。这种为现代硬件而构建的思路正是 DiceDB 敢于宣称低延迟、高吞吐的底气所在。三种连接方式TCP-RESP、HTTP 与 WebSocket作为为实时应用而构建的数据库DiceDB 提供了三种连接协议详见 supported-protocols.mdx协议默认端口适用场景TCP-RESP7379默认通信模式面向服务端与 CLI 客户端HTTP8082前端直连、Webhook 集成、无原始 TCP 连接的环境WebSocket8379高频实时推送、协同编辑、实时游戏等全双工场景TCP-RESP是 DiceDB 的默认协议适合服务端到服务端的高性能通信HTTP让浏览器前端可以直接访问 DiceDB将数据库能力延伸到 Web 栈WebSocket是构建高频实时应用的利器它是全双工协议客户端与服务器可以同时收发消息弥补了 HTTP 轮询与 SSE 在推送能力上的不足适合协同编辑、实时对战等场景。从 internal/cmd/cmds.go 的命令定义可以看出每个命令的元信息DiceCmdMeta统一承载了命令名、参数数量Arity、键位置KeySpecs、评估函数等信息使得同一套命令逻辑可以无缝地服务于 RESP、HTTP 与 WebSocket 三种协议入口——这是多协议支持能够保持行为一致的架构基础。快速上手在体验 DiceDB 之前你可以先了解其安装方式见 installation.mdx。DiceDB 以 Go 编写仓库根目录下的 main.go 是程序入口package main import github.com/dicedb/dice/cmd func main() { cmd.Execute() }启动服务器后即可通过redis-cli兼容的 RESP 客户端连接到127.0.0.1:7379先执行SET/GET等基础命令再尝试Q.WATCH体验实时订阅。对于 Go 开发者仓库还提供了 hello-world-go 与 leaderboard-go 等示例工程可以直接作为 SDK 接入的起点。结语从 hello-world.md 的宣言到仓库中的每一行实现DiceDB 的目标始终清晰不是再造一个又一个数据库而是为实时应用时代打造一个专用平台。响应式查询订阅让客户端从轮询的枷锁中解放出来一等公民的 JSON 支持让复杂数据的读写与查询变得自然纯内存引擎与面向现代硬件的 I/O 架构则保证了实时推送所需的极致性能。对于想要构建排行榜、实时仪表盘、协同编辑或任何数据一变、界面即新的应用的开发者来说DiceDB 提供了一个值得关注的全新选择——正如其博客所言We are just getting started.【免费下载链接】dicedbOpen-source, low-latency key/value engine built on Valkey with query subscriptions and hierarchical storage tiers.项目地址: https://gitcode.com/GitHub_Trending/dic/dicedb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考