CacheSQL:一个面向政务系统的内存缓存数据库中间件

7 阅读6分钟

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 微秒

五、设计哲学

做这个项目的核心原则是:克制

不做理由
JOINO(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)
协议开源

欢迎参与

如果你在做类似的政务/企业级系统,也在头疼数据库读压力的问题,欢迎:

  1. 下载试用git clonemvn package,5 分钟就能跑起来
  2. 提 Issue — 遇到问题、有建议,都欢迎到 GitHub Issues 讨论
  3. PR — 欢迎提交代码改进,特别是其他数据库的适配、新查询类型的支持
  4. 评价反馈 — 如果觉得有用(或不好用),在评论区说说你的场景和感受,帮助项目改进

做开源最难的不是写代码,是知道有没有人真的在用。你的每一条反馈都很重要。