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

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的地址就行了。
具体参照下图
在这里插入图片描述

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加上别名。

扫描二维码关注公众号,回复: 11007556 查看本文章

最后总结

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

发布了7 篇原创文章 · 获赞 13 · 访问量 3730

猜你喜欢

转载自blog.csdn.net/xiaolong7713/article/details/96776085