CacheSQL:一个面向政务系统的内存缓存数据库中间件
把 Oracle 的热点表装进 JVM,用 B+ 树做微秒级查询,86 项测试全通过,JAR 包仅 80KB。
一、它解决什么问题?
做过政务系统的同学都知道——数据库永远是瓶颈。
一张社保参保表几百万行,前端列表查询一次 SELECT * FROM KCA2 WHERE AAC001 = '12345',Oracle 硬解析 + 磁盘读,200ms 起步。并发一上来,连接池打满,接口超时。
CacheSQL 的思路很简单:把这些读多写少的热点表,整个装进 JVM 内存里。
查询走 B+ 树索引,延迟从毫秒级降到微秒级。后端 Oracle 只承担写操作和冷数据查询。
┌──────────────┐ 读请求(微秒级) ┌──────────────────┐
│ 应用服务 │ ──────────────────────→ │ CacheSQL (内存) │
│ │ │ B+树索引查询 │
│ │ ←────────────────────── │ 320万 QPS 峰值 │
│ │ 查询结果 │ │
└──────────────┘ └──────────────────┘
│ ▲
│ 写请求(直接写库) │ 启动时全量加载
▼ │
┌──────────────┐ ┌──────────────────┐
│ Oracle/MySQL │ │ 定时刷新 / 手动 │
│ 关系数据库 │ │ cache.refreshInterval │
└──────────────┘ └──────────────────┘
本质是双写模型:应用写关系数据库(权威数据源),CacheSQL 做读缓存加速。缓存丢了?reload 即可。
二、它有多快?
内嵌模式(Java API 直调):
| 操作 | QPS | 延迟 |
|---|---|---|
table.get() 单线程 | 36 万 | 2.8 us |
table.get() 16 线程 | 320 万 | 0.31 us |
| SQL 等值查询 8 线程 | 87 万 | 1.15 us |
HTTP 模式(REST API,推荐 Undertow NIO 引擎):
| 操作 | 8 线程 QPS |
|---|---|
| SQL 等值查询 | 2.7 万 |
| 索引等值查询 | 3.0 万 |
| 范围查询 | 2.4 万 |
| 写入(insert/update) | 2.7~2.9 万 |
80KB 的 JAR 包(JDK 引擎零外部依赖),在 8 核机器上跑到这个数字,性价比相当高。
三、核心特性
3.1 B+ 树索引
自研 B+ 树,阶 256,支持等值/范围/前缀 LIKE 查询。非唯一索引同一 key 可对应多行。
索引列类型自动推断(Long/Double/String),HTTP 写入参数自动类型转换,不会因为 "0500" 被转成 500 导致查不到。
3.2 SQL 查询引擎
不用学新 API,直接写 SQL:
SELECT * FROM KCA2 WHERE AAC001 = '12345'
SELECT * FROM KCA2 WHERE AAC001 > '1000' AND AAC001 < '2000'
SELECT * FROM KCA2 WHERE AAC002 LIKE '张%'
底层用 jsqlparser 4.9 解析,执行计划缓存复用。同模板 SQL 只解析一次,后续走缓存。
3.3 主从复制
三种角色,一个配置项搞定:
# Master 节点
server.role=master
# Slave 节点
server.role=slave
server.master.url=http://master-host:8080
数据流:
写入请求 → Slave → HTTP 转发 → Master 执行 → OpLog 记录 → 异步广播 → Slave 回放
容错设计:
- Slave 写入时 Master 挂了?操作缓存在
pendingQueue(FIFO 队列) - Master 恢复后,后台线程自动重放缓冲操作,数据不丢
- insert 的 upsert 语义保证重放不会产生重复行
- Master 心跳检测 Slave 存活,Slave 断线重连后增量补发
3.4 客户端容灾
当单机内存装不下所有表时,按表分组到多个 CacheSQL 集群。CacheSQLClient 自动路由:
# cachesql.properties
cachesql.connectTimeout=3000
cachesql.readTimeout=5000
cachesql.group.insurance.master=http://192.168.1.10:8080
cachesql.group.insurance.slaves=http://192.168.1.11:8080,http://192.168.1.12:8080
cachesql.group.insurance.tables=KCA2,KCA3
cachesql.group.medical.master=http://192.168.1.20:8080
cachesql.group.medical.slaves=http://192.168.1.21:8080
cachesql.group.medical.tables=YB01,YB02
CacheSQLClient client = new CacheSQLClient("cachesql.properties");
// 自动路由到 insurance 组,随机选 master/slave
List<Map<String, Object>> rows = client.get("KCA2", "AAC001", "12345");
// 写优先发 master,master 挂了自动切 slave(slave 会转发给 master)
client.insert("KCA2", "AAC001", "99999", data);
已验证的容灾行为(13 项 Failover 测试全通过):
| 场景 | 行为 |
|---|---|
| master 挂了,读 | 自动切到 slave,无感 |
| slave 挂了,读 | 自动切到 master,无感 |
| master 挂了,写 | 自动切到 slave,slave 转发给 master |
| master 恢复后 | pendingQueue 缓冲的操作自动重放 |
| master + slave 都挂 | 抛异常,应用层兜底 |
3.5 双引擎切换
# 零依赖,80KB(默认)
server.http.engine=jdk
# 高性能,+1.2MB,8 线程 QPS 提升 49~108%
server.http.engine=undertow
一行配置切换,业务代码零改动。
四、5 分钟上手
第 1 步:编译
git clone https://github.com/yuhou25/CacheSQL.git
cd CacheSQL
mvn clean package -DskipTests
第 2 步:配置
编辑 config.properties:
# 连接你的数据库
db.driver=oracle.jdbc.driver.OracleDriver
db.url=jdbc:oracle:thin:@192.168.1.10:1521:orcl
db.username=your_user
db.password=your_pass
# 定义缓存表
cache.table.KCA2.sql=SELECT * FROM KCA2
cache.table.KCA2.indexes=AAC001,AAC002,AAC003
# 也支持 MySQL
# db.driver=com.mysql.cj.jdbc.Driver
# db.url=jdbc:mysql://localhost:3306/mydb
第 3 步:启动
java -Xmx2g -jar target/CacheSQL-1.0-SNAPSHOT.jar
第 4 步:查询
# SQL 查询
curl "http://localhost:8080/cache/query?sql=select%20*%20from%20KCA2%20where%20AAC001%20%3D%20%2712345%27"
# 索引等值查询
curl "http://localhost:8080/cache/get?table=KCA2&column=AAC001&value=12345"
# 范围查询
curl "http://localhost:8080/cache/range?table=KCA2&column=AAC001&from=1000&to=2000"
或者用 Java 客户端:
CacheSQLClient client = new CacheSQLClient("cachesql.properties");
List<Map<String, Object>> rows = client.get("KCA2", "AAC001", "12345");
System.out.println(rows);
也可以内嵌使用(零 HTTP 开销)
不需要启动独立服务,直接在应用里用 Java API:
Table table = database.load("myTable", "SELECT * FROM MY_TABLE", new String[]{"id", "name"});
table.indexColumnType().put("id", String.class);
List<Row> rows = (List<Row>) table.get("id", "12345");
// 延迟 2~3 微秒
五、设计哲学
做这个项目的核心原则是:克制。
| 不做 | 理由 |
|---|---|
| JOIN | O(n²) 是数学下限,Oracle 都翻车,缓存层不该碰 |
| 分片 | 单机能装下就不分,分片的复杂度远超收益 |
| 持久化 | 数据源在关系库,缓存只做加速,丢了 reload |
| 服务端路由转发 | 多一跳延迟翻倍,不如应用端直连 |
只做好一件事:单表索引查询的极致性能。
六、适用场景
CacheSQL 不是通用数据库,它最适合:
- 政务/社保/医保系统:热点表百万级,读多写少,对查询延迟敏感
- 实时看板/报表:把统计口径的基础表加载到内存,聚合查询走索引
- API 缓存层:替代 Redis + 序列化的方案,直接 SQL 查内存表,省掉序列化开销
- 数据库减压:把高频查询挡在缓存层,后端数据库只处理写操作
不适用:数据量千万级以上、写多读少、需要 JOIN 跨表查询的场景。
七、测试覆盖
86 项自动化测试全通过,覆盖:
- B+ 树索引(15 项)
- SQL 查询引擎(11 项)
- 内存表 CRUD(17 项)
- 客户端路由(10 项)
- 读容灾/写容灾(13 项)— master 挂了自动切 slave
- ReplicationManager 缓冲重放(10 项)— pendingQueue + master 恢复后同步
- 主从复制端到端(6 项)— 两个独立 JVM 进程
- 并发安全(2 项)
- 性能基准(30 项)
八、项目信息
| 项目 | 信息 |
|---|---|
| 源码 | GitHub - yuhou25/CacheSQL |
| JDK 兼容 | 8 / 11 / 17 / 21 |
| 外部依赖 | jsqlparser 4.9(必选)+ JDBC 驱动 + Undertow 2.2(可选) |
| JAR 大小 | 80 KB(JDK 引擎)/ +1.2 MB(含 Undertow) |
| 协议 | 开源 |
欢迎参与
如果你在做类似的政务/企业级系统,也在头疼数据库读压力的问题,欢迎:
- 下载试用 —
git clone后mvn package,5 分钟就能跑起来 - 提 Issue — 遇到问题、有建议,都欢迎到 GitHub Issues 讨论
- PR — 欢迎提交代码改进,特别是其他数据库的适配、新查询类型的支持
- 评价反馈 — 如果觉得有用(或不好用),在评论区说说你的场景和感受,帮助项目改进
做开源最难的不是写代码,是知道有没有人真的在用。你的每一条反馈都很重要。