使用ShardingSphere进行分表的一些心得

使用ShardingSphere进行分表的一些心得

  • ShardingSphere简介
    • 使用心得
      • 1. 不支持一个表多个策略
      • 2. 复杂的SQL很可能会解析错误
      • 3. 对集中化配置的分布式主键配置不友好
      • 4. 使用分布式主键插入数据错误
      • 5. 项目中用PageHelper分页插件可能会有异常
      • 6. 跳过解析的功能较弱
    • 最后总结

ShardingSphere简介

引用官网的原文,https://shardingsphere.apache.org/index_zh.html,
前身是由当当的Sharding-jdbc发展而来,主要作者张亮也是elsatic-job的作者。

使用心得

通过前期的一些调研,也对其他的分布分表中间件有所了解,最终选择了shardingsphere。我们这次用的是Sharding-JDBC对原有的业务做分表,暂时没有做分库,官网上的文档比较齐全,github上也有示例,所以入门还是比较简单的,但是在实际的集成过程中还是踩到一些坑。

1. 不支持一个表多个策略

Sharding-jdbc支持的分表策略有5种,ComplexShardingStrategy、HintShardingStrategy、InlineShardingStrategy、StandardShardingStrategy、NoneShardingStrategy,每个策略对应各自的分片算法,但是一个表只能配置一个策略,并不支持多个策略混合的模式,如果某个表既想走HintShardingStrategy又想走StandardShardingStrategy,只能将服务继续分拆,颗粒度细化到走各自的策略,如果能支持类似于类似于责任链的这种多策略,那在配置的时候有更好的灵活性。

2. 复杂的SQL很可能会解析错误

Sharding-jdbc不支持复杂的聚合函数,和子查询,在使用的过程中一定要注意。对于HintShardingStrategy这种策略来说,理论上只要解析表名来指定分片策略即可,不需要解析除表名的剩余sql,但是实际上sharding-jdbc会解析整条sql,可怕的是如果sql解析后和原来的loginSql不一致,业务方并不知晓,只能加强测试。还有如果集成了sharding-jdbc,会对所有的sql进行解析,不管有没有配置对应的分表策略,如果服务中有复杂的sql,就不要去集成,只能将服务继续拆分细化。
对于Mysql会用到这样的INSET INTO ON DUPLICATE KEY UPDATE这样的sql,在ShardingSphere 4.0.0-RC1前并不支持,新版本4.0.0-RC2只支持了一部分,丢失了部分参数。
例如:

Preparing: insert into origin_pre_order (OrderID,HotelID,LinkMobile,LinkPhone,LinkName,
CusCategoryID,MemMobile,SrcID,AssType,KeepTime,
ArrDate,ArrTime,DepDate,CreateDate,ModifyDate,OrderStatus,
CardNo,BookerID,MemName,BookerLevel,RoomInfo,RoomCnt,
Days,RcpType,CompanyName,SyncTime) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) 
ON DUPLICATE KEY UPDATE HotelID = IF(ModifyDate <= ?, ? , HotelID),...

Sharding-jdbc在解析的时候会丢掉IF后面的参数。

3. 对集中化配置的分布式主键配置不友好

分表之后会用到分布式主键,Sharding-jdbc内置了UUID和Snowflake,官网说后续会提供Leaf方面的集成。Snowflake算法需要workedId字段,如果在各自的服务中配置,需要间隔开配置不同的值,如果对于如果配置是集中化管理的,这个workedId的配置会固定,生成的主键可能会冲突。好在提供了ShardingKeyGenerator接口,可以实现这个接口同时按照SPI接口规范,Sharding-jdbc会在初始化的时候反射SPI接口实例化实现类。
我们就是按照这个规范去这样实现的,参照Leaf用zk注册顺序的方式生成workedId,集成配置zk的地址就行了。
具体参照下图
使用ShardingSphere进行分表的一些心得_第1张图片

4. 使用分布式主键插入数据错误

在用分布式主键插入数据的时候,如果数据里有某一列空值,id会补到这一列去,目前没有去跟踪源码了,项目中我们换了一下sql,用非空的形式

		<trim prefix="(" suffix=")" suffixOverrides=",">
			<if test="callId != null">call_id,if>
			<if test="deviceIp != null">device_ip,if>
			<if test="deviceMac != null">device_mac,if>
			<if test="recordingAddress != null">recording_address,if>
			<if test="duration != null">duration,if>
			<if test="trunkNumber != null">trunk_number,if>
			<if test="createTime != null">create_time,if>
		trim>
		<trim prefix="values (" suffix=")" suffixOverrides=",">
			<if test="callId != null">#{callId},if>
			<if test="deviceIp != null">#{deviceIp},if>
			<if test="deviceMac != null">#{deviceMac},if>
			<if test="recordingAddress != null">#{recordingAddress},if>
			<if test="duration != null">#{duration},if>
			<if test="trunkNumber != null">#{trunkNumber},if>
			<if test="createTime != null">#{createTime},if>
		trim>

5. 项目中用PageHelper分页插件可能会有异常

这个坑刚开始踩的时候都有点懵,我们的项目中是用的PageHepler来做分页的,PageHelper里面有个机制是,当解析的sql比较复杂的时候,会加上别名,而Sharding-jdbc执行这个带有别名的sql会报错,

Error querying database. Cause: java.lang.IllegalStateException: 
Must have sharding column with subquery.
The error may involve ai.injoy.outbound.mapper.TaskRecOrderMapper.
selectFirstTaskRecOrder-Inline
The error occurred while setting parameters
SQL: select count(0) from (SELECT t.* FROM ob_task_rec_order t, 
ob_task_config c WHERE t.hotel_code = c.hotel_code AND 
c.check_out_confirm_enable = 1 AND 
date_sub(t.last_check_out_time,INTERVAL 
c.check_out_confirm_time MINUTE) < SYSDATE() 
AND t.last_check_out_time > SYSDATE() 
AND t.task_status = 0 AND t.order_status = 5 
AND t.task_result = 0 AND t.last_check_out_time like ? 
AND t.out_cnt = 0 AND t.rcp_type = 'RcpType01' 
order by t.create_time,t.task_id) tmp_count

解决办法是在另加一个XXX_COUNT的sql,不要让PageHelper给原始sql加上别名。

6. 跳过解析的功能较弱

项目当中一些比较复杂的sql可能会解析错误,没法自动路由到指定的分库或者分表,Shardingsphere提供了HintManager.setDatabaseShardingValue()方法,当指定了这个方法,

    public SQLRouteResult route(final List<Object> parameters) {
        if (null == sqlStatement) {
            sqlStatement = shardingRouter.parse(logicSQL, true);
        }
        return masterSlaveRouter.route(shardingRouter.route(logicSQL, parameters, sqlStatement));//shardingRouter有2个实现
    }

shardingRouter有2个实现,ParsingSQLRouter和DatabaseHintSQLRouter,当指定了HintManager.setDatabaseShardingValue()方法,会构造DatabaseHintSQLRouter实例,

    public RoutingResult route() {
        Collection<Comparable<?>> shardingValues = HintManager.getDatabaseShardingValues();
        Preconditions.checkState(!shardingValues.isEmpty());
        Collection<String> routingDataSources;
        routingDataSources = databaseShardingStrategy.doSharding(dataSourceNames, Collections.<RouteValue>singletonList(new ListRouteValue<>("", "", shardingValues)));
        Preconditions.checkState(!routingDataSources.isEmpty(), "no database route info");
        RoutingResult result = new RoutingResult();
        for (String each : routingDataSources) {
            result.getTableUnits().getTableUnits().add(new TableUnit(each));
        }
        return result;
    }

本质上通过HintManager固化分库的的做法来跳过sql解析和分表的,如果一个事务里包含了多个数据库操作,就需要在每个操作前后指定强制路由的逻辑,代码很不优雅。

参考文档:https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/usage/sharding/hint/

最后总结

虽然只用到了ShardingSphere里的Sharding-jdbc功能,虽然遇到了一些坑,但还是逐一解决了,目前已经满足我们的业务需要了。在使用过程中仔细阅读官方的文档和Demo是很有必要的。
整体来说,作为一些不是很复杂的sql用sharding-jdbc是很方便集成到业务系统的,而且客户端集成的好处是真正的分布式的,不同的微服务可以选择不同的策略。
值得注意的是,分表之后的运维工作尤其重要,目前我们用的是配置中心,上线新门店,需要手动去修改分表的参数。
后续关于治理和性能的监控,还需要进一步学习实践。

你可能感兴趣的:(随笔心得)