谈到分库分表中间件时,我们自然而然的会想到ShardingSphereJDBC。 这篇文章,我们聊聊ShardingSphereJDBC相关知识点,并实战演示一番。 1ShardingSphere生态 ApacheShardingSphere是一款分布式的数据库生态系统,它包含两大产品:ShardingSphereProxyShardingSphereJDBC 一、ShardingSphereProxy ShardingSphereProxy被定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 代理层介于应用程序与数据库间,每次请求都需要做一次转发,请求会存在额外的时延。 这种方式对于应用非常友好,应用基本零改动,和语言无关,可以通过连接共享减少连接数消耗。 二、ShardingSphereJDBC ShardingSphereJDBC是ShardingSphere的第一个产品,也是ShardingSphere的前身,我们经常简称之为:shardingjdbc。 它定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。 当我们在Proxy和JDBC两种模式选择时,可以参考下表对照: JDBC Proxy 数据库 任意 MySQLPostgreSQL 连接消耗数 高 低 异构语言 仅Java 任意 性能 损耗低 损耗略高 无中心化 是 否 静态入口 无 有 越来越多的公司都在生产环境使用了shardingjdbc,最核心的原因就是:简单(原理简单,易于实现,方便运维)。2基本原理 在后端开发中,JDBC编程是最基本的操作。不管ORM框架是Mybatis还是Hibernate,亦或是springjpa,他们的底层实现是JDBC的模型。 shardingjdbc的本质上就是实现JDBC的核心接口。 接口 实现类 DataSource ShardingDataSource Connection ShardingConnection Statement ShardingStatement PreparedStatement ShardingPreparedStatement ResultSet ShardingResultSet 虽然我们理解了shardingjdbc的本质,但是真正实现起来还有非常多的细节,下图展示了Prxoy和JDBC两种模式的核心流程。 1。SQL解析 分为词法解析和语法解析。先通过词法解析器将SQL拆分为一个个不可再分的单词。再使用语法解析器对SQL进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。 2。执行器优化 合并和优化分片条件,如OR等。 3。SQL路由 根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。 4。SQL改写 将SQL改写为在真实数据库中可以正确执行的语句。SQL改写分为正确性改写和优化改写。 5。SQL执行 通过多线程执行器异步执行。 6。结果归并 将多个执行结果集归并以便于通过统一的JDBC接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。 本文的重点在于实战层面,shardingjdbc的实现原理细节我们会在后续的文章一一给大家呈现。3实战案例 笔者曾经为武汉一家O2O公司订单服务做过分库分表架构设计,当企业用户创建一条采购订单,会生成如下记录:订单基础表tentorder:单条记录订单详情表tentorderdetail:单条记录订单明细表tentorderitem:N条记录 订单数据采用了如下的分库分表策略:订单基础表按照entid(企业用户编号)分库,订单详情表保持一致;订单明细表按照entid(企业用户编号)分库,同时也要按照entid(企业编号)分表。 首先创建4个库,分别是:ds0、ds1、ds2、ds3。 这四个分库,每个分库都包含订单基础表,订单详情表,订单明细表。但是因为明细表需要分表,所以包含多张表。 然后springboot项目中配置依赖:dependencygroupIdorg。apache。shardingspheregroupIdshardingjdbcspringbootstarterartifactIdversion4。1。1versiondependency 配置文件中配置如下: 配置数据源,上面配置数据源是:ds0、ds1、ds2、ds3;配置打印日志,也就是:sql。show,在测试环境建议打开,便于调试;配置哪些表需要分库分表,在shardingsphere。datasource。sharding。tables节点下面配置: 上图中我们看到配置分片规则包含如下两点: 1。真实节点 对于我们的应用来讲,我们查询的逻辑表是:tentorderitem。 它们在数据库中的真实形态是:tentorderitem0到tentorderitem7。 真实数据节点是指数据分片的最小单元,由数据源名称和数据表组成。 订单明细表的真实节点是:ds{0。。3}。tentorderitem{0。。7}。 2。分库分表算法 配置分库策略和分表策略,每种策略都需要配置分片字段(shardingcolumns)和分片算法。4基因法自定义复合分片算法 分片算法和阿里开源的数据库中间件cobar路由算法非常类似的。 假设现在需要将订单表平均拆分到4个分库shard0,shard1,shard2,shard3。 首先将〔01023〕平均分为4个区段:〔0255〕,〔256511〕,〔512767〕,〔7681023〕,然后对字符串(或子串,由用户自定义)做hash,hash结果对1024取模,最终得出的结果slot落入哪个区段,便路由到哪个分库。 看起来分片算法很简单,但我们需要按照订单ID查询订单信息时依然需要路由四个分片,效率不高,那么如何优化呢? 答案是:基因法自定义复合分片算法。 基因法是指在订单ID中携带企业用户编号信息,我们可以在创建订单orderid时使用雪花算法,然后将slot的值保存在10位工作机器ID里。 通过订单orderid可以反查出slot,就可以定位该用户的订单数据存储在哪个分片里。IntegergetWorkerId(LongorderId){LongworkerId(orderId12)0x03ff;returnworkerId。intValue();} 下图展示了订单ID使用雪花算法的生成过程,生成的编号会携带企业用户ID信息。 解决了分布式ID问题,接下来的一个问题:shardingjdbc可否支持按照订单ID,企业用户ID两个字段来决定分片路由吗? 答案是:自定义复合分片算法。我们只需要实现ComplexKeysShardingAlgorithm类即可。 复合分片的算法流程非常简单: 1。分片键中有主键值,则直接通过主键解析出路由分片; 2。分片键中不存在主键值,则按照其他分片字段值解析出路由分片。5扩容方案 既然做了分库分表,如何实现平滑扩容也是一个非常有趣的话题。 在数据同步之前,需要梳理迁移范围。 1。业务唯一主键; 在进行数据同步前,需要先梳理所有表的唯一业务ID,只有确定了唯一业务ID才能实现数据的同步操作。 需要注意的是:业务中是否有使用数据库自增ID做为业务ID使用的,如果有需要业务先进行改造。另外确保每个表是否都有唯一索引,一旦表中没有唯一索引,就会在数据同步过程中造成数据重复的风险,所以我们先将没有唯一索引的表根据业务场景增加唯一索引(有可能是联合唯一索引)。 2。迁移哪些表,迁移后的分库分表规则; 分表规则不同决定着rehash和数据校验的不同。需逐个表梳理是用户ID纬度分表还是非用户ID纬度分表、是否只分库不分表、是否不分库不分表等等。 接下来,进入数据同步环节。 整体方案见下图,数据同步基于binlog,独立的中间服务做同步,对业务代码无侵入。 首先需要做历史数据全量同步:也就是将旧库迁移到新库。 单独一个服务,使用游标的方式从旧库分片select语句,经过rehash后批量插入(batchinsert)到新库,需要配置jdbc连接串参数rewriteBatchedStatementstrue才能使批处理操作生效。 因为历史数据也会存在不断的更新,如果先开启历史数据全量同步,则刚同步完成的数据有可能不是最新的。 所以我们会先开启增量数据单向同步(从旧库到新库),此时只是开启积压kafka消息并不会真正消费;然后在开始历史数据全量同步,当历史全量数据同步完成后,在开启消费kafka消息进行增量数据同步(提高全量同步效率减少积压也是关键的一环),这样来保证迁移数据过程中的数据一致。 增量数据同步考虑到灰度切流稳定性、容灾和可回滚能力,采用实时双向同步方案,切流过程中一旦新库出现稳定性问题或者新库出现数据一致问题,可快速回滚切回旧库,保证数据库的稳定和数据可靠。 增量数据实时同步的大体思路: 1。过滤循环消息 需要过滤掉循环同步的binlog消息; 2。数据合并 同一条记录的多条操作只保留最后一条。为了提高性能,数据同步组件接到kafka消息后不会立刻进行数据流转,而是先存到本地阻塞队列,然后由本地定时任务每X秒将本地队列中的N条数据进行数据流转操作。此时N条数据有可能是对同一张表同一条记录的操作,所以此处只需要保留最后一条(类似于redisaof重写); 3。update转insert 数据合并时,如果数据中有insertupdate只保留最后一条update,会执行失败,所以此处需要将update转为insert语句; 4。按新表合并 将最终要提交的N条数据,按照新表进行拆分合并,这样可以直接按照新表纬度进行数据库批量操作,提高插入效率。 扩容方案文字来自《256变4096:分库分表扩容如何实现平滑数据迁移》,笔者做了些许调整。6总结 shardingjdbc的本质是实现JDBC的核心接口,架构相对简单。 实战过程中,需要配置数据源信息,逻辑表对应的真实节点和分库分表策略(分片字段和分片算法) 实现分布式主键直接路由到对应分片,则需要使用基因法自定义复合分片算法。 平滑扩容的核心是全量同步和实时双向同步,工程上有不少细节。 实战代码地址: https:github。commakemyownlifeshardingspherejdbcdemo 参考资料:256变4096:分库分表扩容如何实现平滑数据迁移?黄东旭:分布式数据库历史、发展趋势与TiDB架构 如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!