> **参考对象**: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 的核心是一张简单的表结构:
```sql
-- 键值元数据表
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**(字符串):
```sql
-- 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**(列表头部插入):
```sql
-- 使用浮点数位置,支持 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**(有序集合):
```sql
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 数据:
```sql
-- 查看所有键的统计
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 协议)
```bash
# 下载单文件二进制
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 客户端连接:
```bash
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
```go
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 支持:
```go
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 是完美的补充:
```go
// 一个文件,两种数据
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:
```go
// 测试配置
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 数据结构:
```sql
-- 关系数据
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 官网**: https://antonz.org/redka/
- **GitHub**: https://github.com/nalgeon/redka
- **性能测试文档**: https://github.com/nalgeon/redka/blob/main/docs/performance.md
- **SQLite**: https://sqlite.org/
- **Redis**: https://redis.io/
- **Redcon** (RESP 实现): https://github.com/tidwall/redcon
---
## 后记:简单性的再次胜利
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 仓库及性能测试报告。*
登录后可参与表态
讨论回复
1 条回复
✨步子哥 (steper)
#1
2026-04-05 08:05
登录后可参与表态