一.布景:

配运平台组的快递订单履约中心(cp-eofc)及物流平台履约中心(jdl-uep-ofc)体系都运用了ShardingSphere生态的sharding-jdbc作为分库分表中间件, 整个集群采用只分库不分表的规划,共16个MYSQL实例,每个实例有32个库,集群共512个库.

当每添加一台客户端主机,一个MYSQl实例最少要添加32个衔接(一般都会运用衔接池,依据装备的最大衔接数,这个衔接数可能会放大5~10倍).而且一般一个体系都会分为web,provider,worker等多个运用,这些运用共用一套数据源.跟着运用机器数的添加,MYSQL实例的衔接数会很快达到上限,这就对体系的扩容形成了阻碍,无法横向的添加机器数,只能纵向的进步机器的装备来应对流量的添加.

作为京东物流的中心体系,事务添加迅速,体系所承接的流量也是逐渐添加,所以急需解决这个制约体系扩展的瓶颈点.

二.分库分表的相关概念介绍

2.1 为什么要分库分表

2.1.1 分库

跟着事务的开展,单库中的数据量不断添加,数据库的QPS会越来越高,对数据库的读写耗时也会相应的添加,这时单库的读写功能必然会成为体系的瓶颈点.这时能够通过将单个数据库拆分为多个数据库的办法,来分担数据库的压力,提高功能.一起多个数据库散布在不同的机器上也进步了数据库的可用性.

2.1.2 分表

跟着单表数据量的添加,关于数据的查询和更新,即使在数据库底层有必定的优化,可是跟着质变必定会引起质变,导致功能急剧下降.这时能够通过火表的办法,将单表数据按必定规矩水平拆分到多个表中,减小单表的数据量,提高体系功能.

2.2 sharding-jdbc简介

ShardingSphere

是一套开源的散布式数据库中间件解决计划组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成.他们均供给标准化的数据分片、散布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的运用场景。

Sharding-JDBC

定位为轻量级Java结构,在Java的JDBC层供给的额外服务。 它运用客户端直连数据库,以jar包办法供给服务,无需额外布置和依靠,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM结构。

适用于任何依据Java的ORM结构,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接运用JDBC。依据任何第三方的数据库衔接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。

支撑恣意完成JDBC规范的数据库。现在支撑MySQL,Oracle,SQLServer和PostgreSQL。

咱们先看下ShardingSphere官网给出的依据Spring命名空间的规矩装备示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:sharding="http://shardingsphere.io/schema/shardingsphere/sharding" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://shardingsphere.io/schema/shardingsphere/sharding 
                        http://shardingsphere.io/schema/shardingsphere/sharding/sharding.xsd 
                        ">
    <!-数据源ds0->
    <bean id="ds0" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/ds0" />
        <property name="username" value="root" />
        <property name="password" value="" />
    </bean>
    <!-数据源ds1->
    <bean id="ds1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/ds1" />
        <property name="username" value="root" />
        <property name="password" value="" />
    </bean>
    <!-分片战略->
    <sharding:inline-strategy id="databaseStrategy" sharding-column="user_id" algorithm-expression="ds$->{user_id % 2}" />
    <sharding:inline-strategy id="orderTableStrategy" sharding-column="order_id" algorithm-expression="t_order$->{order_id % 2}" />
    <sharding:inline-strategy id="orderItemTableStrategy" sharding-column="order_id" algorithm-expression="t_order_item$->{order_id % 2}" />
    <!-sharding数据源装备->
    <sharding:data-source id="shardingDataSource">
        <sharding:sharding-rule data-source-names="ds0,ds1">
            <sharding:table-rules>
                <sharding:table-rule logic-table="t_order" actual-data-nodes="ds$->{0..1}.t_order$->{0..1}" database-strategy-ref="databaseStrategy" table-strategy-ref="orderTableStrategy" />
                <sharding:table-rule logic-table="t_order_item" actual-data-nodes="ds$->{0..1}.t_order_item$->{0..1}" database-strategy-ref="databaseStrategy" table-strategy-ref="orderItemTableStrategy" />
            </sharding:table-rules>
        </sharding:sharding-rule>
    </sharding:data-source>
</beans>

装备总结:

1.需要装备多个数据源ds0,ds1;

2.分片战略中装备分片键(sharding-column)和分片表达式(algorithm-expression)需符合groovy语法;

3.在sharding数据源中sharding:table-rule标签中装备逻辑表名(logic-table),库分片战略(database-strategy-ref)和表分片战略(table-strategy-ref),actual-data-node特色由数据源名 + 表名组成,以小数点分隔,用于广播表;

三.问题剖析与解决计划

3.1 问题剖析

正如文章开头说到的现在咱们的MYSQL集群架构如下,16个MYSQL实例,每个实例有32个库,集群共512个库.当客户端主机发动后与MYSQL_0实例中的32个库衔接,别离会树立32个数据源,衔接池装备的最大衔接数为5,也就是说极端状况下一个客户端与一个MYSQL实例最多会树立32*5=160个衔接数.关于物流的一些中心体系在大促时扩容上百台是很常见的,所以很快单个实例的最大衔接数就会触达上限.

现在客户端衔接衔接数据库集群办法如图所示:

sharding-jdbc分库连接数优化 | 京东物流技术团队

3.2 可行计划

咱们的方针就是降低单个MYSQL实例的衔接数,其中咱们共探讨了几种计划如下:

3.2.1 单实例不分库只分表

这样一个客户端与单个数据库实例只需通过一个衔接池衔接,大大降低了衔接数.但这种计划改变了现有的分片规矩,需要新建一套数据库集群,依据新规矩同步历史数据和增量数据,还有新旧数据验证,但难度和风险最高的仍是线上切换过程,可能会形成数据不一致,且一旦出问题回滚计划也会非常复杂.

3.2.2 运用支撑弹性扩展的数据库

运用京东的jed,tidb等支撑弹性扩展的数据库,将数据同步到新库中,这类数据库的优势是开发人员只需重视事务,不需要再去处理数据库衔接这些底层细节.

3.2.3 运用sharding-proxy

Sharding-Proxy的定位是透明化的数据库代理,咱们能够在服务器上布置一套Sharding-Proxy,客户端只需衔接proxy服务,再由proxy服务器衔接MYSQL集群,这样MYSQL集群的衔接数只与proxy服务器的数量有关,与客户端解耦.

3.2.4 通过改造sharding-jdbc

理论上咱们只要获取数据库实例上某个库的衔接,咱们就能够通过”库名.表名”的办法拜访这台实例上其他库中的数据(当然前提是用户要具有要拜访库的权限),咱们是否能够通过改造sharding-jdbc来完成这种拜访办法?

以上几种计划,3.2.1和3.2.2都需要新建数据库,同步历史和增量数据,还涉及线上切换数据源,3.2.3需要布置一套proxy服务,而且为了高可用必定要以集群办法布置,这三种计划作业量和风险都较高,咱们依据成本最小准则,最终挑选改造sharding-jdbc的计划.

3.3 探究sharding-jdbc

3.3.1 作业流程

sharding-jdbc的作业流程能够分为以下过程:

  • sql解析-词法解析和语法解析;

  • sql路由-依据解析上下文匹配数据库和表的分片战略,并生成路由路径;

  • sql改写-将逻辑SQL改写为在实在数据库中能够正确履行的SQL;

  • sql履行-运用多线程并发履行sql;

  • 成果归并-将从各个数据节点获取的多数据成果集,组合成为一个成果集并正确的回来至请求客户端;

显然数据库和表的分片是在sql路由阶段处理,所以咱们以sql路由逻辑为进口剖析下源码.

3.3.2 源码剖析

ShardingStandardRoutingEngine类中的route办法为核算路由的进口,回来的成果是数据库和表的分片调集:

sharding-jdbc分库连接数优化 | 京东物流技术团队

route办法中的中心逻辑在该类的route0办法中,其中routeDataSources办法担任database路由,routeTables办法担任table路由,实践路由核算在StandardShardingStrategy的doSharding办法中,咱们持续深化.

sharding-jdbc分库连接数优化 | 京东物流技术团队

在StandardShardingStrategy类中有两个成员特色,preciseShardingAlgorithm(精准分片算法),rangeShardingAlgorithm(范围分片算法),因为咱们的sql都只指定分片键精准查询,运用的都是preciseShardingAlgorithm核算出的成果,PreciseShardingAlgorithm是个接口,那咱们就能够完成这个接口来自界说分片算法.

sharding-jdbc分库连接数优化 | 京东物流技术团队

一起在sharding-sphere官网上也找到了相应的标签支撑:

sharding-jdbc分库连接数优化 | 京东物流技术团队

所以咱们只需要自己完成PreciseShardingAlgorithm接口并装备在标签内即可完成自界说分片战略.

3.4 改造过程

3.4.1 库分片改造

现在运用装备了ds_0ds_511共512个数据源,咱们只需装备ds_0ds_15共16个数据源,每个数据源装备的是单个实例上的第一个库.

关于分片规矩,咱们能够依然运用sharding:inline-strategy标签,只需对Groovy表达式进行重写,分片键为order_code,之前分片算法为(Math.abs(order_code.hashCode()) % 512)即用order_code列的哈希值对512取模得到0511,咱们只需要将成果再整除32即可得到016,即表达式改写为(Math.abs(order_code.hashCode()) % 512).intdiv(32).

改造前分库规矩装备:

sharding-jdbc分库连接数优化 | 京东物流技术团队

改造后分库规矩装备:

sharding-jdbc分库连接数优化 | 京东物流技术团队

3.4.2 表分片改造

完成PreciseShardingAlgorithm接口,重写表分片算法,使核算成果回来”实践库名+表名”的办法;

例如:查询DB_31库上t_order表的user_id=35711的数据,数据库分片算法回来的数据源为”DB_0″,表分片算法回来”DB_31.t_order”;

自界说表分片算法:

sharding-jdbc分库连接数优化 | 京东物流技术团队

在xml中界说sharding:standard-strategy标签,其特色precise-algorithm-ref装备为咱们自界说的分表算法.

sharding-jdbc分库连接数优化 | 京东物流技术团队

3.4.3 数据库衔接池参数调整

改造前是一个库对应一个数据源衔接池,改造后一个实例上的32个库共用一个数据源衔接池,那么衔接池的最大衔接数,最小空闲衔接数等参数需要相应的做调整.这个需要依据事务流量做合理的评估,当然最严谨的仍是要以压测成果作为依据.

改造后客户端衔接集群的办法如图:

sharding-jdbc分库连接数优化 | 京东物流技术团队

优化前后数据库集群衔接数对比:

sharding-jdbc分库连接数优化 | 京东物流技术团队

四.小插曲

在改写库分片规矩的Groovy表达式时,整除32直接在原有表达式上装备”/32″即Math.abs(order_code.hashCode()) % 512 / 32 ,在调试中发现履行sql会报”no database route info”错误信息,通过debug发现sharding-jdbc核算分片规矩时会呈现小数(例如:ds_14.6857),导致找不到数据源,这是因为Groovy没有供给专用的整数除法运算符,所以要用.intdiv()办法,最终表达式改写为(Math.abs(order_code.hashCode()) % 512).intdiv(32).

五.总结

本文介绍了分库分表的概念及优势,以及sharding-jdbc分库分表中间件,探究了sharding-jdbc的路由规矩的履行流程.当然在体系规划之初,关于数据库的分库分表,到底需不需要做?是多分库好仍是多分表好?并没有一个放之四海而皆准的法则,需结合体系的特色(例如qps,tps,单表数据量,磁盘标准,数据保留时间,事务增量,数据冷热计划等因素)来决议计划权衡,有利有弊才需决议计划,有取有舍才需权衡.

作者:京东物流 张仲良

来源:京东云开发者社区自猿其说Tech