09 深入专题:DBLE 与分布式中间件

09 深入专题:DBLE 与分布式中间件

这一篇聚焦爱可生开源的 DBLE 分布式中间件:分库分表策略、全局表/ER 表、分布式事务与跨节点 JOIN、连接管理、集群配置与运维要点。内容整理自爱可生开源社区《大智小技》系列(2019/2020 两册)技术文章精选。

DBLE date 时间分片如何工作?带状与环状模式区别?

以每份 86400000 毫秒(24小时整,非自然日)为单位。配置 sBeginDate、sPartionDay(每多少份一个分片)、dateFormat、sEndDate、defaultNode。带状模式(只配 sBeginDate):从起始日每 sPartionDay 份一个分片无限增长,早于 sBeginDate 且无 defaultNode 会路由失败。环状模式(配 sEndDate):以 [sBeginDate,sEndDate] 的份数为固定分片数,对分片索引求模路由,早于 sBeginDate 落 defaultNode。划分不对应自然月年,受闰秒影响。

DBLE enum 分片算法如何配置与使用?

按枚举值→分片节点映射文件直接定位目标分片。rule.xml 配:mapFile(枚举值文件路径,在 conf 内可用相对路径如 map/table_map.txt,外置需绝对路径)、type(0 时枚举值须整型,非0 可为任意字符除=和换行)、defaultNode(非必须,非负整数,值未命中 mapFile 时路由至此,不配则报错)。mapFile 格式 <枚举值>=<分片编号>,枚举值之间不能重复(否则加载出错),不同枚举值可映射到同一分片,可用 // 或 # 注释。

DBLE numberrange 范围分片原理与配置要点?

根据范围与分片的映射文件直接定位。mapFile 格式 <最小值>-<最大值>=<分片编号>,范围含端点(如 [1,100]);范围重叠时命中最先出现的那条,不同范围可映射到同一分片;分片索引为长整型。配置项只有 defaultNode 和 mapFile,defaultNode 非必须,不配时索引未命中范围会报错,配则须为非负整数并路由到该分片。DBLE 读取 mapFile 不查重、不排序、也不检查范围最小值与最大值谁大,因此运维最佳实践:手工确保范围无重叠,且按数据查询频率从高到低顺序填写。数据分布上无法保证均匀,总分片数等于各范围持有分片数量之和;扩容热范围需做局部数据迁移。

DBLE patternrange 分片算法是什么?

最终效果同 hash:先求模得逻辑分片号再映射物理分片。rule.xml 配 patternValue(逻辑分片数量)、mapFile(逻辑→物理映射,如 0=0,… 或 0-N=0 表示全映射到0号)、defaultNode。最大物理分片为每个逻辑分片指定单独物理分片;最小为所有逻辑分片指定同一物理分片。扩容时不改 patternValue 可免数据再平衡。mapFile 中 DBLE 按逻辑分片范围最小值做插入排序,需确保范围不重叠。

DBLE stringhash 分片如何工作?与 hash 分片有何关系?

当分片索引是非整型字符串时,按 hashSlice 截取部分字符,逐字符累计(累计值=累计值×31+字符 unicode 值,转长整型),再调用内置 hash 求模得逻辑分片号→物理分片号。最大物理分片让 partitionCount 数组和=2880;截取越长、内容重复率越低越均匀。hashSlice 配置如 “0:2” 取第0~2字符;“k”/“0:k”/":k" 从头取k个;"-k:0"/"-k" 从尾取k个。其余与 hash 分片机制完全相同。

DBLE 中 ER 表 JOIN 的处理方式与限制?

ER 关系指两表逻辑上的从属关系。在 schema.xml 中用 childTable 标签配合 joinKey/parentKey 声明:配置为 ER 后,子表(如订单表)的数据会按关联组织存储到父表(如用户表)对应的 DB 节点上,保证数据本地性。只有当一条查询"有且仅有存在 ER 关系的表格关联组成"时才被称为 ER 表格 JOIN,此时中间件直接把 SQL 下发到对应节点执行,避免跨库、不广播。限制:ER 关系是单向的,一个分片表只能是一个表的子表,不能有多个父表,跨父表查询会退化为跨库 JOIN;且同样建议在查询时提供能确定分片的限定条件以精确命中单节点。ER 查询本质与简单查询一样是下发给 DB 执行。

DBLE 修复的两个 PreparedStatement Bug(#1122/#1124)根因是什么?

#1122:同连接中 COM_STMT_CLOSE 销毁后再 COM_STMT_PREPARE 创建是异步操作,存在线程安全问题,改为同步操作后修复,发布于 2.19.03.0。#1124:JDBC 设 useCursorFetch=true 开启服务端游标须满足条件——SELECT 语句、fetchSize>0、useCursorFetch=true、ResultSet.TYPE_FORWARD_ONLY、ResultSet.CONCUR_READ_ONLY、Server versions≥5.0.5;DBLE 不支持 COM_STMT_FETCH 导致结果错误,需避免该组合或用服务端游标替代方案。

DBLE 如何实现读写分离?关键配置是什么?

在 schema.xml 中将表配置到单一 dataNode,dataHost 下挂 writeHost 与 readHost,并设置 balance=“3” 来开启读负载均衡。balance 控制写节点是否参与读均衡:balance=0 表示不开启读写分离;值越大读流量越倾向下发到从库。启动后通过管理端口 9066 执行 show @@datasource,观察 READ_LOAD 在 slave 节点计数器增加即证明读流量下发到从库,也可开 MySQL general log 验证。DBLE 无状态,可多开提升吞吐。

DBLE 如何快速搭建数据拆分(分库分表)环境?

先装 Java 1.8+(yum install java-1.8.0-openjdk,确认 JAVA_HOME),从 https://github.com/actiontech/dble/releases 下载解压即装。核心配置三文件:server.xml 设服务端口8066、管理端口9066,业务用户 test/password 访问逻辑库 testdb;schema.xml 定义 schema 逻辑库、dataNode 分片、dataHost 物理实例(含 balance/switchType/heartbeat);rule.xml 配分片规则如 sharding-by-mod2(class=Hash,partitionCount=2)。执行 ./bin/dble start 启动,mysql -P8066 登录,insert 按取模下发,explain 可看实际下发 SQL。

DBLE 是如何实现 MySQL Prepared Statement 协议的?

客户端 COM_STMT_PREPARE 阶段:DBLE 缓存 SQL 并伪装返回 COM_STMT_PREPARE_OK(因 SQL 不完整无法确定下发节点,JDBC 从该 response 取信息可能不准)。COM_STMT_EXECUTE 阶段:DBLE 用参数替换占位符生成具体 SQL,改用 COM_QUERY 下发后端节点,再把后端返回数据由二进制协议转换格式后返回客户端。DBLE 不支持 COM_STMT_FETCH。MySQL Prepared Statement 自 5.1 引入,好处是减少解析开销并防 SQL 注入。

DBLE 第七种分片算法 jumpStringHash 是什么?

源自 Google《A Fast, Minimal Memory, Consistent Hash Algorithm》,通过概率分布让 hash 值落在每个节点的概率均为 1/n,分布更均匀。配置 class=“jumpStringHash”,参数 partitionCount(分片数量)与 hashSlice(分片截取长度)。注意事项:分片字段值为 NULL 时数据恒落 0 号节点;当 MySQL 物理字段定义为 NOT NULL 而传入 NULL 时,报错 “Sharding column can’t be null when the table in MySQL column is not null”。

DBLE 路由函数必须实现哪些关键接口方法?

必须实现的接口:setters(如 setPartitionCount)将 rule.xml 文本属性转为对应类型;selfCheck() 自检配置,失败抛 RuntimeException 终止使用;init() 内部初始化(如建 PartitionUtil 数组加速查表);calculate(String) 返回单分片号、calculateRange(begin,end) 返回范围分片号数组;getAllProperties() 返回<配置项,值>映射,供管理端口 SHOW @@ALGORITHM WHERE SCHEMA=? AND TABLE=? 查询。内置 PartitionByLong 即采用此模式,propertiesMap 由 AbstractPartitionAlgorithm 默认提供。

DBLE/MyCat 的 hash 分片算法原理是什么?

先求模得到逻辑分片号,再查映射表直接得到物理分片。启动阶段点乘 partitionLength[] 与 partitionCount[] 得到模数(逻辑分片数量),叉乘得到逻辑分片到物理分片的映射(物理分片数=partitionCount 元素之和)。运行期提取 WHERE 中的分片索引值求模得逻辑号,查表得物理号。注意:partitionLength 与 partitionCount 的点乘须在 [1,2880] 范围内;两者对顺序敏感;分片索引须为整型(可为负)。

中间件跨库 JOIN 的原理及优化手段?

当 SQL 无法归为简单/ER 查询时,中间件从各 DB 分别取表数据,按关联列(如 ID)排序归档后对比计算关联结果。消耗包括:临时内存、CPU、连接数与网络。优化三点:1) 添加足够的限定条件减少处理数据量;2) 使用重复率低的列做关联;3) 手动调整 SQL 表顺序避免笛卡尔积(如用户表×订单表优先聚合得约2000条,而非先与商品表产生1000×1000=100万条)。中间件无法像 MySQL 优化器提前算聚合代价,按原表顺序聚合。

为何 DBLE 用 jumpStringHash 替代 MyCat 的一致性 hash?

一致性 hash(环割法)初始化生成 分片数×环割数 的字符串 hash 值排序入 Map,输入 hash 取小于它的元素对应分片。跳跃法(jumpStringHash)通过伪随机值判断落点。用 350595 条数据测试:环割法方差随分片数增多而减小,但节点少时均衡性最差,且耗时比跳跃法高约一个数量级(环割数增加耗时小幅升,跳跃法仅小幅升)。DBLE 选跳跃法性能与均衡更优;从 MyCat 迁移一致性 hash 可用自定义拆分算法实现。

分布式中间件中单表简单 SQL 是如何路由的?

带分片条件(如 where ID=1)的查询,中间件按条件解析计算目标分片,只把 SQL 下发到对应 DB。不带分片条件的查询会被广播到该表涉及的所有 DB,各自返回再整合,但必须等待最慢的那个 DB 查询结束。随着分片数上升,广播等待连接数增多,整体响应成倍下降(Matlab 模拟:单连接约5ms,100连接约15ms)。最佳实践是在 SQL 中加拆分列限定条件,降低单次涉及的后端 DB 数量。

如何开发并部署 DBLE 自定义拆分算法?

路由函数继承 AbstractPartitionAlgorithm 抽象类并实现 RuleAlgorithm 接口。加载流程:DBLE 启动读 rule.xml 的 class,通过 Java 反射从 $DBLE_HOME/lib 的 jar 加载类,按 property 的 name 调对应 setter 赋值,再调 selfCheck() 自检、init() 初始化。部署:2.18.12.0+ 建议放入 algorithm 目录(区分原生 lib),jar 配好属主权限,rule.xml 的 class 写完全限定名(如 net.john.DBLE.route.functions.NewFunction),重启生效。建议独立项目引用 DBLE 源码而非内嵌开发,便于版本管理。

DBLE 主键即拆分列的连续翻页有什么技巧?

适用场景:拆分列=物理表主键、连续翻页(仅前/后一页,首查首页)、单表 SQL、有 ORDER BY 且后缀必须为拆分列(如 ORDER BY id)。首页直接 LIMIT M,记录 minId/maxId。向后翻:WHERE id>maxId ORDER BY id LIMIT M;向前翻:WHERE id<minId ORDER BY id DESC LIMIT M。限制:翻页是广播语句,并发不超单 MySQL max_connections 的10%(如512则≤51);每页记录数 M 与逻辑分片数 dataNode 乘积 ≤8000;依赖客户端控制翻页。

DBLE 如何处理 Global 表 Left Join 拆分表?与 MyCat 区别?

MyCat 将 SQL 原样下发到所有实例,再把各节点与拆分表 Left Join 的结果简单 UNION ALL 合并。由于全局表在每个配置节点都存储相同数据,这种合并会把重复行放大,导致结果错误;即便改用 UNION(去重)也解决不了问题,因为分片本身就会让同一拆分key的数据在不同节点重复出现,单纯去重会丢数据。DBLE 作了区分:全局表只下发一个实例,拆分表全部下发,再对结果集做准确合并,从而保证正确性,但执行计划更复杂、内部实现更重。这本质是为保证数据准确性作出的取舍——全局表每节点同数据的特性,决定了不能简单合并。

如何用 mysql-utilities 对 DTLE 迁移做自动化测试?

用 MySQL 官方 mysql-utilities(mysqldiff/mysqldbcompare)。mysqldiff –server1=test:test@10.20.30.3:3306 –server2=test1:test1@10.20.30.4:3307 testdb:testdb 比对两库表结构差异(列出迁移失败的表);mysqldiff … testdb.char_columns:testdb.char_columns 比对单表建表语句。mysqldbcompare –server1=… –server2=… testdb:testdb 比对数据一致性。可减少人工逐条比对,集成成脚本做版本发布回归测试。

为什么分布式数据库用 hash 做分片?取模 hash 的优缺点?

hash 与分片神似点:固定输入→固定输出、值呈均匀分布、方便扩容。取模 hash 即分片数 N、key 取模 N,模0落分片1……适合分片键单调递增/递减、基数大重复低、随机读写场景。变种可指定每分片连续值数量(如自然数0-99放分片1,100-599放分片2)以改善范围查询效率。缺点:范围查询效率低、扩容不便(增删分片大量数据需重映射)。DBLE 建议取模基数≤2880(其约数多,便于过量分片后局部迁移)。

DBLE LOAD DATA 大规模导入功能如何实现?

DBLE 模拟 MySQL LOAD DATA 协议。ServerQueryHandler 解析到 LOAD_DATA_INFILE_SQL 调 loadDataInfileStart;若 load data local(文件不在本机)则向客户端发 RequestFilePacket 请求文件,否则本地解析。核心类 ServerLoadDataInfileHandler 处理与客户端交互,LoadDataUtil 处理与后端 MySQL 交互。接收文件默认小于 200Mb 存内存否则落本地文件,按路由把不同节点数据分别存储后,下发 LOAD DATA 命令到各后端 MySQL 完成导入。

DTLE 的 job 是如何实现数据传输与高可用的?

job 提交后形成四类结构:evaluation(调度决策机制,状态变化触发)、allocation(任务组与客户端节点映射,是高可用调度一环,可查 task 运行状态与报错,定位问题在源端或目标端)、task(分 Src/Dest,记录库表连接信息,是基本单元)、driver(具体实现,负责读 binlog 到数据回放)。整体分两部分:调度部分实现高可用与 job 转移;传输部分从源端到目标端做连接、抽取/清洗/回放。

DBLE 心跳检测的作用与模块原理?

作用三点:1) 控制多写节点高可用切换;2) 控制读负载均衡(依据最近心跳状态及 slaveThreshold 主从延迟阈值);3) 控制空闲连接数(发 PING 包,配合 dataNodeIdleCheckPeriod)。定时任务 Scheduler#init 以 dataNodeHeartbeatPeriod(默认10秒)触发,经 PhysicalDNPoolSingleWH/PhysicalDBPool→MySQLDatasource→MySQLHeartbeat→MySQLDetector 执行心跳 SQL(如 show slave status),结果通过 getStatus/getSlaveBehindMaster 影响切换与读写分离。

DBLE 是如何实现 MySQL 视图的?

MySQL 视图用 MERGE(合并算法,视图SQL与查询SQL合并)或 TEMPTABLE(临时表算法,先生成临时表再查;含 GROUP BY/DISTINCT/聚合/UNION/子查询时用,EXPLAIN 中 select_type 为 DERIVED)。DBLE 分两类:可下推——直接下推后端,DBLE 仅存视图元数据,判断依据是逻辑 schema 是否 nosharding(无表配置);不可下推——在逻辑层将视图 SQL 与查询 SQL 合并后执行,相当于 MySQL 的 MERGE 算法。可下推场景较少。

DBLE 2.19.11.0 新全局表检查如何实现?

2.19.11.0 前继承 MyCat 用 server 级 + 列 _dble_op_time,问题多(开关/结果集/导入不便)。新版引入 quartz 框架,在 schema.table 级配 globalCheck、cron、globalCheckClass。加载时每表检查作为独立 quartz 定时任务;用户可自定义 getCountSQL/getFetchCols/resultEquals/failResponse/resultResponse 方法。内置两种:CHECKSUM(各节点 checksum 值对比)和 COUNT(count 数量对比),如 <table … type=“global” globalCheck=“true” cron=“0 * * * * ?” globalCheckClass=“CHECKSUM”/>。

DBLE 的 SQL 解析基于什么技术?

SQL 解析同编译原理:词法分析器(Lexer)将字符序列转 Token;语法分析器(Parser)构建抽象语法树(AST);访问器(Visitor)遍历 AST 获取信息。DBLE 用 Druid 实现 SQL 解析(Druid 的词法/语法分析器均纯手写,效率高),也可用 ANTLR 生成解析器(需自定义规则)。DBLE 的 SQL 解析服务于后续路由、结果集处理等——理解 SQL 才知道要做什么。中间件除存储外几乎具备数据库全部功能。

DBLE 分布式时间戳方式全局序列如何工作?

基于 Zookeeper 的分布式 ID 生成器,生成 63 位正数(首位恒0):a 线程id低9位、b 5位实例id(INSTANCEID,zk 或 [0,31])、c 4位数据中心id(CLUSTERID [0,15])、d 6位自增值、e 系统时间戳低39位(可用约17年)。配置 sequence_distributed_conf.properties(INSTANCEID/CLUSTERID/START_TIME,集群各 DBLE 配不同 CLUSTERID),server.xml 设 sequenceHandlerType=3,管理端口 reload @@config_all。可转二进制按位反推组成。

分库分表应优先考虑哪些问题?业务层 vs 中间件?

先扪心自问是否真需要分库分表(可能一个索引/垂直拆分/OLAP 即可解决)。三问:拆谁(选核心业务字段做拆分列)、怎么拆(让 Query 命中尽量少分片,单条用 Hash,批量增删改/范围/小聚合用范围)、拆完业务如何处理。中间件(DBLE)对业务侵入小、提供运维观测窗口、便于"下车"回退单体;业务层拆分重构成本高。DBLE 用 2PC(MySQL 5.7+ XA)做分布式事务,无状态可多开提升性能。

主流 MySQL 中间件(ProxySQL/MyCat/DBLE/ShardingSphere/Spider)怎么选?

ProxySQL 不支持分库分表,仅代理/读写分离;MyCat 做好分库分表一件事但 Bug 多已停维护;DBLE 在 MyCat 基础修复 Bug、增强复杂查询/分布式事务/运维监控,银行有核心案例;ShardingSphere 偏业务层(ShardingJDBC),侵入业务需业务升级驱动;Spider 是 MySQL 存储引擎,SQL 兼容100%但链路迂回性能差(建议≤128分区)。综合推荐 DBLE——开源、有企业级案例、持续运维。