所有性能、安全、一致性细节都被设计为「不需要业务侧改一行代码」。MySQL 客户端走 MySQL 路径,PostgreSQL 客户端走 PG 路径,两者共享同一份连接池、缓存、失效语义。
同一个 listen 既可对外说 MySQL 也可对外说 PostgreSQL;启动时代理拨测后端(MySQL Initial Handshake / PG SSLRequest)自动识别要走哪条路径,也可用 backend.protocol 显式覆盖。
命中走 write_all 字节回放,不解码任何列类型;JSON / GEOMETRY / DECIMAL / DATETIME 全部字节级一致,无 round-trip 损耗。
用 sqlparser 解析每条 SQL 抽出受影响表集合;INSERT/UPDATE/DELETE 落在表 T,则所有命中过 T 的缓存条目(含所有参数变体)原子失效。
按凭据隔离的空闲连接池,跨客户端会话复用已认证的后端连接。release 时跑 DISCARD ALL(PG)或 COM_RESET_CONNECTION(MySQL)清场。
结构化的每 SQL / 缓存事件日志由单独 writer 线程批量落盘,调用线程只入队;磁盘 I/O 不会阻塞查询热路径。
消除 ~40ms 的 Nagle + delayed-ACK 死等。这一个开关是把端到端从 40–80ms 拉回 1–2ms 的关键,不是噱头。
一条到 broadcast-host 的 WebSocket 长连接同时承担两件事:(1) 失效广播——多副本之间互通本地 DML 触发的 invalidate_table;(2) 连通性兜底——重连连续 N 次失败 → 强制 CACHE OFF(直通后端),恢复时先清空本地 cache 再翻回 CACHE ON,避免 OFF 期间外部写入让旧快照被读到。单节点保持关闭即可。
客户端 ⇄ 代理 ⇄ 后端三段,每段都按各自协议走原生 wire;代理在中间做缓存判定、表级失效与连接池路由。
同一个后端 DB 前部署多个 database-cache-proxy 副本时,每个副本各自维护独立的内存缓存。一个副本上的 INSERT/UPDATE/DELETE 会通过 broadcast-host 中转扇出,让其它同 group 的副本就地清掉对应的缓存项——避免读副本拿到脏数据。
不需要外部 KV / Redis,cache 在 proxy 进程内的 DashMap;命中走字节回放,零序列化开销。
本地 DML 触发 invalidate_table(t) 后异步 publish;不阻塞 SQL 路径,同 group 节点秒级同步。
中转挂了 SQL 流量不受影响,自动重连;期间退化为"每副本独立 cache",恢复后立刻回到集群一致状态。
加副本 = 起一个新的 proxy 容器、配同样的 group;自动加入失效广播网络,无需配置中心。
无论 MySQL 还是 PG,每条 SQL 都要走完这 6 步;缓存命中只走前 3 步,命中即回字节。
两条路径在缓存、连接池、鉴权三处保持完全等价的行为;差异仅在各自 wire 协议的细节里。
| 客户端鉴权插件 | mysql_clear_password |
| 客户端默认插件兜底 | AuthSwitchRequest |
| 后端鉴权方式 | native_password / caching_sha2 (fast) |
| 缓存命中包 | COM_QUERY 全响应字节 |
| 包重写 | sequence_id 重新打包 |
| 缓存条件 | 单语句 · 无 MORE_RESULTS · 无 ERR |
| 连接 release 命令 | COM_RESET_CONNECTION |
| 实现 | 自实现 wire 客户端 · 无 mysql_async |
| 客户端鉴权方式 | AuthenticationCleartextPassword |
| 支持模式 | simple query · extended query |
| 后端鉴权方式 | SCRAM-SHA-256 / MD5 / cleartext |
| 缓存命中包 (simple) | RowDescription + DataRow + CmdComplete |
| 缓存命中包 (extended) | 同上 + 重发 ReadyForQuery |
| 缓存条件 | 单语句 SELECT · ReadyForQuery 'I' |
| 连接 release 命令 | DISCARD ALL |
| 实现 | 自实现 wire 客户端 · zero-copy 批量转发 |
| 直连后端 | ~1–2 ms / query |
| 未优化的代理 | 40–80 ms / query |
| 开 TCP_NODELAY 后 | ~1–2 ms / query |
| 缓存命中 | < 1 ms / query |
| 命中 vs 后端 | ~10–40× 快 |
| TCP Nagle 移除 | 双向 setnodelay |
| PG 扩展查询缓冲 | 单 Vec · 0 alloc |
| 命中 CPU | 仅一次 write_all |
| 日志 | 异步 writer 线程 |
| 认证开销 | 池化后摊销为 0 |
凭据不进配置 — 代理用客户端发来的 user/password/database 直接打上游。
proxy:
listen: "0.0.0.0:3307"
backend:
host: "127.0.0.1"
port: 3306
protocol: "mysql" # or "postgresql"
# 共享池:MySQL / PG 路径都消费这份配置
pool:
enabled: true
max_per_key: 16
idle_ttl_secs: 300
reset_query: "DISCARD ALL" # MySQL 自动映射为 COM_RESET_CONNECTION
cache:
enabled: true
ttl_secs: 300
max_entries: 10000
# 可选:集群模式(一条 ws 连接同时做失效广播 + 连通性判定)
# 重连连续失败 → CACHE OFF;恢复 → 清缓存 → CACHE ON
cluster:
enabled: false
url: "ws://gy.duguying.net:9940"
group: "default"
ping_interval_secs: 15
reconnect_delay_secs: 5
reconnect_failures_for_cache_off: 3
log:
level: "info"
stdout_level: "warn"
path: "log/database-cache-proxy.log"
file_async: true