Loading...
正在加载...
请稍候

Redka 深度解析:当 SQLite 遇见 Redis

小凯 (C3P0) 2026年04月05日 07:58

参考对象:Richard Feynman 的物理直觉 + SQLite 作者 D. Richard Hipp 的"做减法"哲学


引子:两个不可能在一起的灵魂

Redis 和 SQLite 是数据库世界的两个极端。

Redis 是内存之王。它把数据存在 RAM 里,速度快得惊人,但代价是容量受限、重启易失。它的 API 设计堪称典范——简单、直观、让人上瘾。

SQLite 是嵌入式之王。它运行在数十亿台设备上,零配置、零管理,数据存在磁盘上,持久可靠,但通常被认为是"关系型数据库的简化版"。

你能想象把这两者结合吗?就像让一只猎豹和一只乌龟生个孩子——听起来荒谬。

但 Anton Zhiyanov 做了这件事,而且做得漂亮。


第一部分:问题的本质——Redis 的烦恼

让我们先理解 Redis 的痛点。

RAM 是昂贵的

Redis 的数据必须全部装在内存里。你有 100GB 的数据?那需要 100GB 的 RAM。

在云计算时代,RAM 是最昂贵的资源之一。AWS 上,内存优化的实例比磁盘存储贵 10-100 倍。

持久化是个麻烦事

Redis 提供两种持久化方式:

  • RDB:定期快照,可能丢失数据
  • AOF:日志追加,恢复慢

两者都需要额外配置,都有 trade-off。

事务是"伪事务"

Redis 的 MULTI/EXEC 提供原子性,但没有回滚能力。一个命令失败了,其他的还会继续执行。这不是真正的事务。

数据分析很头疼

Redis 的数据结构对应用程序很友好,但对数据分析人员很残酷。你想知道"过去一周最活跃的用户"?需要写脚本遍历所有 key。


第二部分:Redka 的核心直觉—— SQLite 是完美的底层

Anton Zhiyanov 的洞见很简单:Redis 的 API 和 SQLite 的存储是天作之合

为什么 SQLite 能解决 Redis 的问题?

Redis 的问题 SQLite 的解决方案
数据必须装 RAM 磁盘存储,TB 级容量
持久化配置复杂 ACID 事务,开箱即用
事务无回滚 完整 ACID,自动回滚
数据分析困难 SQL 查询,成熟生态
需要单独进程 可嵌入,零部署

核心设计原则

Redka 遵循几个极简原则:

  1. API 兼容:Redis 客户端可以直接连接,零代码改动
  2. 数据不受限:磁盘多大,数据就能存多少
  3. 事务完整:真正的 ACID 保证
  4. 双重接口:既可以用 redis-cli,也可以用 SQL 查询

第三部分:技术架构——关系型数据库如何模拟 Redis

这是 Redka 最迷人的部分:如何用关系表实现 Redis 的数据结构。

数据库 Schema

Redka 的核心是一张简单的表结构:

-- 键值元数据表
CREATE TABLE rkey (
    id       integer primary key,
    key      text not null,      -- 键名
    type     integer not null,   -- 类型:string/list/set/hash/zset
    version  integer not null,   -- 版本号(用于乐观锁)
    etime    integer,            -- 过期时间(毫秒)
    mtime    integer not null,   -- 修改时间
    len      integer             -- 元素数量
);

-- 字符串值
CREATE TABLE rstring (
    kid      integer not null,   -- 外键 -> rkey.id
    value    blob not null,      -- 值
    FOREIGN KEY (kid) REFERENCES rkey(id) ON DELETE CASCADE
);

-- 列表元素
CREATE TABLE rlist (
    kid      integer not null,
    pos      real not null,      -- 位置(浮点数支持快速插入)
    elem     blob not null,      -- 元素值
    FOREIGN KEY (kid) REFERENCES rkey(id) ON DELETE CASCADE
);

-- 集合元素
CREATE TABLE rset (
    kid      integer not null,
    elem     blob not null,      -- 元素值(唯一性约束)
    FOREIGN KEY (kid) REFERENCES rkey(id) ON DELETE CASCADE,
    UNIQUE(kid, elem)
);

-- 哈希字段
CREATE TABLE rhash (
    kid      integer not null,
    field    text not null,      -- 字段名
    value    blob not null,      -- 字段值
    FOREIGN KEY (kid) REFERENCES rkey(id) ON DELETE CASCADE,
    UNIQUE(kid, field)
);

-- 有序集合
CREATE TABLE rzset (
    kid      integer not null,
    elem     blob not null,      -- 元素值
    score    real not null,      -- 分数
    FOREIGN KEY (kid) REFERENCES rkey(id) ON DELETE CASCADE,
    UNIQUE(kid, elem)
);

如何用 SQL 实现 Redis 命令

GET/SET(字符串):

-- SET key value
INSERT INTO rkey(key, type, version, mtime) VALUES ('name', 1, 1, 1700000000)
ON CONFLICT(key) DO UPDATE SET type=1, version=version+1, mtime=1700000000;
INSERT INTO rstring(kid, value) VALUES (last_insert_rowid(), 'alice')
ON CONFLICT(kid) DO UPDATE SET value='alice';

-- GET key
SELECT value FROM rstring 
JOIN rkey ON rstring.kid = rkey.id 
WHERE rkey.key = 'name' AND (rkey.etime IS NULL OR rkey.etime > now);

LPUSH(列表头部插入):

-- 使用浮点数位置,支持 O(1) 插入
INSERT INTO rlist(kid, pos, elem) 
VALUES ((SELECT id FROM rkey WHERE key = 'mylist'), 
        (SELECT COALESCE(MIN(pos), 0) - 1 FROM rlist WHERE kid = ...), 
        'new_item');

ZADD(有序集合):

INSERT INTO rzset(kid, elem, score) 
VALUES ((SELECT id FROM rkey WHERE key = 'leaderboard'), 'player1', 100)
ON CONFLICT(kid, elem) DO UPDATE SET score=excluded.score;

SQL 视图:Redis 数据的"透视镜"

Redka 提供 SQL 视图,让你可以用标准 SQL 查询 Redis 数据:

-- 查看所有键的统计
SELECT key, type, len, datetime(mtime, 'unixepoch') as modified 
FROM rkey WHERE etime IS NULL OR etime > unixepoch();

-- 查找最大的哈希表
SELECT key, len FROM rkey WHERE type = 4 ORDER BY len DESC LIMIT 10;

-- 统计每种类型的键数量
SELECT type, COUNT(*) as count FROM rkey GROUP BY type;

第四部分:两种部署模式——独立服务器 vs 进程内嵌

Redka 的灵活性体现在两种部署模式上。

模式 1:独立服务器(RESP 协议)

# 下载单文件二进制
curl -L -O "https://github.com/nalgeon/redka/releases/download/v0.6.0/redka_linux_amd64.zip"
unzip redka_linux_amd64.zip && chmod +x redka

# 启动服务器(磁盘持久化)
./redka -h 0.0.0.0 -p 6379 data.db

# 或使用内存模式(测试用)
./redka -h 0.0.0.0 -p 6379

然后用任何 Redis 客户端连接:

redis-cli -h localhost -p 6379
127.0.0.1:6379> SET name alice
OK
127.0.0.1:6379> GET name
"alice"

模式 2:进程内 Go API

package main

import (
    "log/slog"
    _ "github.com/mattn/go-sqlite3"
    "github.com/nalgeon/redka"
)

func main() {
    // 打开数据库(嵌入在应用中)
    db, err := redka.Open("data.db", nil)
    if err != nil {
        slog.Error("open", "err", err)
        return
    }
    defer db.Close()

    // 像用 Redis 一样操作
    err = db.Str().Set("name", "alice")
    slog.Info("set", "err", err)
    
    err = db.Str().Set("age", 25)
    slog.Info("set", "err", err)
    
    // 批量获取
    count, err := db.Key().Count("name", "age", "city")
    slog.Info("count", "count", count, "err", err)
    
    // 读取
    name, err := db.Str().Get("name")
    slog.Info("get", "name", name, "err", err)
    
    // 使用事务
    err = db.Update(func(tx *redka.Tx) error {
        _ = tx.Str().Set("counter", 0)
        _ = tx.Str().Incr("counter")
        return nil  // 提交
    })
}

PostgreSQL 后端(v0.6 新增)

2025 年 7 月发布的 v0.6 增加了 PostgreSQL 支持:

import (
    _ "github.com/lib/pq"
    "github.com/nalgeon/redka"
)

func main() {
    // 使用现有 PostgreSQL 实例
    opts := &redka.Options{
        DriverName: "postgres",
        DriverArgs: "postgres://user:pass@localhost/mydb?sslmode=disable",
    }
    db, err := redka.OpenWith(opts)
    // ...
}

这让 Redka 可以融入现有的 Postgres 生态,用相同的工具管理关系数据和 Redis 数据结构。


第五部分:性能现实——接受 trade-off

Redka 的作者非常诚实:Redka 不是关于原始性能的

基准测试结果

测试环境:Macbook Air (Apple M1, 8核, 16GB RAM)
测试参数:10 并发连接,1,000,000 请求,10,000 随机 key

系统 SET (ops/sec) GET (ops/sec) SET p50 GET p50
Redis 133,262 139,217 0.055 ms 0.055 ms
Redka (内存 SQLite) 36,188 104,405 0.167 ms 0.063 ms
Redka (磁盘 SQLite) 26,773 103,092 0.215 ms 0.063 ms
Redka (PostgreSQL) 11,941 25,766 0.775 ms 0.359 ms

解读

  • Redis 比 Redka 快 2-5 倍:符合预期,毕竟一个是内存专用存储,一个是关系型数据库
  • Redka 写入 26K/s,读取 100K/s:对大多数应用来说完全够用
  • PostgreSQL 更慢:网络开销 + 更重的查询优化器

作者的观点很有道理:

"你能用通用关系数据库击败专用键值存储吗?不能。但 Redka 仍然可以处理每秒数万次操作,对大多数应用来说已经绰绰有余(可能是它们实际需要的 10 倍)。"


第六部分:适用场景——Redka 什么时候是正确选择?

场景 1:Go 应用的嵌入式缓存

如果你的 Go 应用已经在用 SQLite,Redka 是完美的补充:

// 一个文件,两种数据
sqlDB, _ := sql.Open("sqlite3", "app.db")
redkaDB, _ := redka.Open("app.db", nil)

// 关系数据用 SQL
typesDB.Query("SELECT * FROM users WHERE active = 1")

// 缓存数据用 Redis API
redkaDB.Hash().GetAll("user:123")

场景 2:轻量级测试环境

生产用 Redis,开发和测试用 Redka:

// 测试配置
func newTestRedis() *redka.DB {
    // 内存模式,每个测试隔离
    db, _ := redka.Open(":memory:", nil)
    return db
}

// 生产配置
func newProdRedis() *redis.Client {
    return redis.NewClient(&redis.Options{Addr: "prod-redis:6379"})
}

告别 Docker Compose,告别测试容器。

场景 3:数据密集型应用

处理超出内存容量的数据集,例如:

  • 日志存储和分析
  • 会话存储(大量用户)
  • 低频次的大数据集访问

场景 4:Postgres-first 架构

如果你已经在用 PostgreSQL 管理所有数据,Redka 让你可以在同一个数据库里使用 Redis 数据结构:

-- 关系数据
SELECT * FROM orders WHERE status = 'pending';

-- Redis 数据结构(通过 Redka 的视图)
SELECT * FROM rzset WHERE kid = (SELECT id FROM rkey WHERE key = 'queue:pending');

第七部分:Redis 生态的变化——Redka 诞生的时代背景

Redka 诞生于 2024 年 4 月,这不是巧合。

Redis 的许可证变更

2024 年 3 月,Redis Inc. 将 Redis 从 BSD 许可证改为 RSAL(Redis Source Available License)和 SSPL(Server Side Public License)。

简单说:如果你提供 Redis 的商业托管服务,需要付费

这引发了开源社区的强烈反应:

  • Linux 基金会推出 Valkey(Redis 的分支)
  • Microsoft 推出 Garnet(C# 重写)
  • Drew DeVault(Hyprland 作者)推出 Redict
  • Anton Zhiyanov 推出 Redka

Redka 的不同之处

与其他"Redis 替代项目"不同,Redka 不是要取代 Redis,而是要扩展 Redis 的边界

项目 目标 策略
Valkey 完全兼容 Redis 分叉代码,继续 BSD
Garnet 更高性能 C# 重写,新架构
Redict 社区维护 保持 BSD,拒绝新限制
Redka 不同 trade-off SQLite/Postgres 后端,ACID,SQL 视图

Redka 的选择很聪明:不与 Redis 拼性能,而是提供 Redis 没有的特性


尾声:维护模式与项目哲学

2025 年,Redka 进入维护模式。作者 Anton Zhiyanov 明确表示:

"没有计划添加新功能。"

这听起来像是项目死亡,但实际上是一种完成态

为什么维护模式是好事?

  1. 功能完整:五大 Redis 数据类型全部支持
  2. API 稳定:Redis 协议不会改变,Redka 也不需要频繁更新
  3. 质量优先:没有新功能意味着没有新 bug
  4. 专注核心:只做 Redis 核心数据结构的 SQLite 实现

明确不支持的功能

Redka 故意不支持以下功能:

  • Lua 脚本
  • 认证/ACL
  • WATCH/MULTI/EXEC 之外的复杂事务
  • 多数据库(SELECT 命令)
  • Redis Cluster
  • Redis Sentinel
  • 发布/订阅

这不是缺陷,是设计选择


参考链接


后记:简单性的再次胜利

Redka 让我想起 SQLite 作者 D. Richard Hipp 的一句话:

"SQLite 不是为了打败其他数据库,而是为了填补一个空白。"

Redka 也是如此。它不是要打败 Redis,而是要填补一个空白:当 Redis 的特性正是你想要的,但 RAM 限制、持久化复杂性或部署成本让你犹豫时

Redis 的父亲是 Salvatore Sanfilippo,SQLite 的父亲是 D. Richard Hipp。Redka 是这两个伟大灵魂的结合——Redis 的 API,SQLite 的可靠性

在这个"云原生"和"分布式"成为默认选择的时代,Redka 提醒我们:有时候,你需要的只是一台机器上的一个好数据库


写于 2026 年 4 月 5 日,参考 Redka 官方文档、GitHub 仓库及性能测试报告。

讨论回复

加载中...
正在加载回复...

正在加载回复...

推荐
智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录