Spring WebFlux 适配达梦新方案|dm‑r2dbc:Netty 异步传输的实验性 R2DBC 驱动
仓库地址:https://github.com/jinyuttt/dm‑r2dbc.git ⚠️重要提示:本项目属于实验性技术研究项目,禁止直接用于生产环境,存在反射、许可协议相关风险,请务必仔细阅读免责声明。
一、背景痛点:达梦 R2DBC 现状
达梦数据库 DM8 作为主流国产数据库,在 Spring WebFlux 响应式项目中适配一直比较尴尬。
达梦官方提供了 R2DBC 驱动,但底层是JDBC 桥接模式 :仅仅封装一层 R2DBC SPI 接口,网络 IO 仍然使用传统阻塞java.net.Socket。本质上属于 "伪响应式",所有数据库 IO 依旧是阻塞调用,靠boundedElastic线程池隔离阻塞逻辑。高并发场景很容易出现线程池饥饿、吞吐量瓶颈,大字段 CLOB/BLOB 处理也容易踩坑。
想要做真正全链路非阻塞 R2DBC,正常有两条路:
- 完全逆向达梦私有 TCP 二进制协议,从零实现 R2DBC 驱动。工作量巨大,协议版本一变就要大规模改代码,还有逆向带来的法律风险。
- 复用官方 JDBC 成熟的协议编解码,只替换底层网络传输层。也就是开源项目
dm‑r2dbc选择的混合架构方案。
二、dm‑r2dbc 项目简介
dm‑r2dbc 是面向达梦 DM8 的实验性 R2DBC 驱动,混合架构:Netty 异步网络传输层 + JDBC 驱动协议编解码复用。
核心思路:不去逆向达梦私有协议二进制报文,直接复用达梦 JDBC 驱动内部已经成熟的编解码逻辑,通过反射替换 JDBC 内部默认阻塞 Socket 传输实现,替换为 Netty NIO 异步通道,实现真正异步非阻塞网络 IO。
版本信息
- 项目名称:dm‑r2dbc
- 当前版本:
1.0‑SNAPSHOT - 许可证:Apache‑2.0
- 目标 JDBC 驱动版本:DmJdbcDriver8
8.1.3.62 - JDK 要求:JDK17+
- 数据库:DM8(8.1.3.x)
- 测试状态:12 项单元测试全部通过(连接、查询、DML、CLOB/BLOB、事务、自增主键、关键字、存储过程)
三、整体架构设计
整体分层:应用层 → R2DBC SPI → dm‑r2dbc 驱动适配层 → JDBC 协议编解码复用层 → Netty 异步传输层 → DM8 数据库服务端 (5236 端口)。
- R2DBC API 适配层 :实现
ConnectionFactoryProvider、DmConnection、DmStatement、DmCallableStatement,对外暴露标准 R2DBC 接口; - 协议编解码复用层 :直接调用达梦 JDBC 驱动
DmDriver、DmdbConnection、PreparedStatement 完成 SQL 解析、报文编解码,不自己解析二进制协议; - 反射注入传输层 :通过反射找到 JDBC 驱动内部通信管理器的传输对象字段,替换为自研
DmNettyTransport; - Netty 网络层 :实现 JDBC 驱动定义的传输接口,使用 Netty NIO 完成 TCP 收发;
DmNettyResponsePipeline处理服务端响应报文缓存。
线程模型关键点:
- Netty EventLoop 线程:只做 TCP 读写,绝不执行任何阻塞业务逻辑;
- JDBC 所有阻塞调用(连接创建、SQL 执行、编解码)全部调度到
Schedulers.boundedElastic()弹性线程池,隔离阻塞;- 上层应用 WebFlux 线程消费 Mono/Flux 结果。
⚠️当前 1.0‑SNAPSHOT 版本限制:DmNettyResponsePipeline内部使用wait/notifyAll等待响应,虽然阻塞被放到 boundedElastic 线程,但高并发场景会占用弹性线程资源。v1.1 规划迭代串行请求‑应答匹配模型,消除该阻塞点。
四、快速上手示例
注意:达梦官方 JDBC 驱动无法从公共 Maven 仓库下载,需要手动将
DmJdbcDriver8.jar安装到本地 Maven 仓库或者放入项目 lib 目录。
1.Maven 依赖
<dependency>
<groupId>org.dameng</groupId>
<artifactId>dm‑r2dbc</artifactId>
<version>1.0‑SNAPSHOT</version>
</dependency>
同时需要准备达梦 JDBC 驱动 8.1.3.62。
2. 连接字符串格式
r2dbc:dm://SYSDBA:SYSDBA@127.0.0.1:5236
3.Spring Boot WebFlux 配置(application.yml)
spring:
r2dbc:
url: r2dbc:dm://127.0.0.1:5236
username: SYSDBA
password: SYSDBA
4. 编程式使用示例
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import reactor.core.publisher.Mono;
import static io.r2dbc.spi.ConnectionFactoryOptions.*;
public class Demo {
public static void main(String[] args) {
ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
.option(DRIVER, "dm")
.option(HOST, "127.0.0.1")
.option(PORT, 5236)
.option(USER, "SYSDBA")
.option(PASSWORD, "SYSDBA")
.build();
ConnectionFactory factory = ConnectionFactories.get(options);
Mono.from(factory.create())
.flatMap(conn ->
Mono.from(conn.createStatement("SELECT ID,NAME FROM USERS WHERE ID = ?")
.bind(0,1L)
.execute())
.flatMapMany(result -> result.map((row,meta)->{
Long id = row.get("ID",Long.class);
String name = row.get("NAME",String.class);
return id + " | " + name;
}))
.then(Mono.from(conn.close()))
)
.subscribe(System.out::println);
}
}
再次强调:实验性项目,不要直接上生产。如果业务需要生产级 R2DBC 能力,优先使用达梦官方驱动,或者等待官方推出原生异步 R2DBC 实现。
GitHub 仓库:https://github.com/jinyuttt/dm‑r2dbc.git 完整技术设计文档仓库内可查看。