一、为什么会用到固定路由。
当逻辑表不能再以某些字段进行分表,而需要更复杂的逻辑进行分表。或者直接不分而是表固定访问实际表时,都需要用HINT固定路由。
二、用法示例。
先要在 yml里面声明分库分表然后配置固定算法,如果不配置就没法用固定路由。
demo: GitHub - ji602089383/ShardingSphereDemo
以下是配置(一个片段,完整的在demo里)
sharding:
key-generators:
snowflake:
type: SNOWFLAKE
tables:
order:
actual-data-nodes: ds$->{0..2}.order_$->{0..2}
database-strategy:
hint:
sharding-algorithm-name: myTest
table-strategy:
hint:
sharding-algorithm-name: hint-default
key-generate-strategy:
column: id
key-generator-name: snowflake
sharding-algorithms:
hint-default: #Hint 行表达式分片算法
type: HINT_INLINE
# props:
# algorithm-expression: order #这个配置非必选 当选择这个表达式时,如果要用到ShardingValue值就必须使用 ${value}来获取。 如果不用这个表达式时,那么就是表名就是order + shardingValue值
myTest: #Hint 行表达式分片算法
type: CLASS_BASED
props:
strategy: hint
algorithmClassName: com.turing.order.Ao
配置中声明了 order表 有 ds0 ds1 ds2三个库,每个库中拥有 order_0 order_1 order_2 三个表。
分库是固定策略,使用别名myTest的算法,该算法是一个类型为自定义类的HINT算法,自定义类的路径为com.turing.order.Ao。
分表是固定策略,使用别名hint-default的算法,该算法是一个类型为行表达式的HINT算法,暂时没有配置行表达式。
代码:
OrderEntity orderEntity = new OrderEntity();
orderEntity.setId(id);
orderEntity.setOrderNo("1");
orderEntity.setCreateTime(new Date());
orderEntity.setSuppliersId(id);
// 官方推荐用 try with resource 减少没有关闭清空资源的错误
try (HintManager hintManager = HintManager.getInstance()){
hintManager.addDatabaseShardingValue("order", "C");
hintManager.addDatabaseShardingValue("order", "B");
hintManager.addDatabaseShardingValue("order", "F");
hintManager.addTableShardingValue("order", 3);
hintManager.addTableShardingValue("order", 2);
hintManager.addTableShardingValue("order", 1);
System.out.println(orderMapper.insertOrder(orderEntity));
}
System.out.println(orderEntity.getId());
上面代码表述了: 要插入一个Order数据,id由用户自己定义, 创建固定路由的管理器,并设置了本次可以找到的数据库路由有3个,对应的ShardingValue值为C,B,F。 设置了本次可以找到的数据表路由有3个,对应的ShardingValue值为3,2,1。
问题
(1)为什么我们在配置addDatabaseShardingValue和addTableShardingValue时第一个参数名为logicTable呢?
HintManager的作用就是给某张表找库的路由和表的路由,所以表为单位,假如现在这个server层,它要插入10张表数据,其中5张表需要走固定路由,那么就配置5张表的不同库路由和表路由策略。
(2)第二个参数ShadingValue有什么作用呢?
它就是我们要进行分片策略时的依据。(如果还不能理解就先简单的理解为按照id分片时的id值)
比如HINT_INLINE算法:HintInlineShardingAlgorithm类中实现了public Collection doSharding(final Collection availableTargetNames, final HintShardingValue> shardingValue)方法。
第一个参数: availableTargetNames 是说,本次我们可以用的目标库或者表的名称。它来源于我们配置文件中actual-data-nodes的值解析后得到数据。按照我们的配置文件,行表达式解析完后得到数据可以是:availableTargetNames = [ds0,ds1,ds2] 或者 availableTargetNames = [order_0,order_1,order_2] 具体是哪个取决于我们这个算法服务于库的路由还是表的路由。
第二个参数:shardingValue,它就是我们配置的 C,B,F 和 3,2,1这些数据。下面给个断点数据给大家看一下
这时别名为hint-default算法的断点,因为它服务于表的路由,所以它得到的ShardingValue是3 2 1。
这是别名为myTest算法的断点,因为它服务于库的路由,所以它得到的ShardingValue是C B F。
以上配置做好后,我们就可以开始测试了。
下面再说HINT的算法:
1、HINT_INLINE
目前HINT的算法 官方只提供了行表达式的。
官方文档中讲,它的行表达式可以不用配置,(下面的举例都是以表路由为准。)
如果不配置行表达式:库和表的路由将是addDatabaseShardingValue或者addTableShardingValue配置的全部路由。举例,按照上面的配置,如果不配置表的算法行表达式则,会路由成:order_0, order_1, order_2
如果配置了行表达式: (1)不需要shardingValue的行表达式。我暂时没有想到这种场景,感觉不用shardingValue值就很奇怪,那干嘛用这个算法呢。(2)需要用ShadingValue的表达式,那表达式中必须用 ${value}来获取ShardingValue值。举例:order_${value % 2} 那按照上面的配置最终的结果就是 order_1, order_0这两个表会被插入数据。在HintInlineShardingAlgorithm算法中,会把我们配置addTableShardingValue值(3,2,1)传入给行表达式的value,实际的计算就是 3 %2, 2%2, 1%2最终得到 1, 0。就是order_1, order_0。 然后这个比较简单的配置,根据实际场景,这个固定路由行表达会相当复杂。
2、CLASS_BASED
这个就是需要我们自己定义算法。
下面是我写非常简单的一个算法,就是固定路由到ds0上,这个是服务于库路由的算法。当然大家可以将表和库的路由规则写到一个算法里。
非常复杂的算法我就不写了,大家有兴趣可根据情况自己写写测试一下。
扩展:
hintManager.setDatabaseShardingValue(0);
这个方法的使用:
该方法调用后会将HintManager中的databaseShardingValues 和 tableShardingValues 都清空,然后设置databaseShardingOnly为true。
并且这个方法的调用后,不能再调用 addDatabaseShardingValue和addTableShardingValue 否则,会导致该方法调用的结果失效,数据被重置。
那么一起看下源码对该方法调用后怎么处理的。这里我从路由引擎开始讲。如果有兴趣的可以更深入的看。
大家去shardingsphere-shading-core依赖中的org.apache.shardingsphere.sharding.route.engine.type.standard包下ShardingStandardRoutingEngine类(标准分片路由引擎, 还有很多种引擎,至于是什么规则使用了这个引擎,可以去看org.apache.shardingsphere.sharding.route.engine.type.ShardingRouteEngineFactory#newInstance方法)。
下面理一下这个类中关键方法的调用:
(1)开始调用route
(2)然后选择数据节点,getDataNodes
(3)isRoutingByHint判定了本次路由属于HINT,并调用了routByHint方法
rountByHint里它调用了两个方法,这两个方法就是从我们再Server层设置的HintManager中获取库的ShardingValue和表的ShardingValue。
getDatabaseShadingValuesFromHint方法中,先判定了我们是采用了SQL注释Hint的方式,还是手动HintManager的方式,很显然我们采用的是HintManager,于是,判定HintManager中的databaseShadingOnly是否为true,hintManager.setDatabaseShardingValue(0)方法我们可以知道databaseShadingOnly是true,最后传递给getShadingConditions方法的参数为 集合=[0]。
getShadingConditions最后经过计算得到 一个对象ListShardingConditionValue,该对象中columnName="", tableName="order", value= [0]
同理的大家可以自己看下getTableShadingValuesFromHint()方法。
(4)进入方法rout0
第一圈中的两个数就是从HintManager得到的数据。
(5)进入routDataSources方法
这里关键。它调用了库分片策略进行分片。当我们进入org.apache.shardingsphere.sharding.route.strategy.type.hint.HintShardingStrategy#doSharding
分片策略的doSharding方法,它获取ShardingValue值,大家都知道它是一个 对象ListShardingConditionValue,该对象中columnName="", tableName="order", value= [0]。
shadingAlgorithm这个对象就是分片的算法。我们配置文件中配置的固定分片算法是CLASS_BASED类型。
于是找到org.apache.shardingsphere.sharding.algorithm.sharding.classbased.ClassBasedShardingAlgorithm类。这里的hintShardingAlgorithm其实就是我们自己实现Ao算法。于是就回到了我们自己的Ao类中。
Ao类中我们写死返回ds0。于是上面的doShading返回的结果就是 [ds0]
走到这里我们,一步一步返回数据到route0方法,并进入for,因为就一个库源,直接进入routTablesf方法查看吧。
(6) routeTables方法
它主要就是说。先获取本次选取的库中的真实的表。然后判定,表的ShardingValue是否为空,根据前面的设置我们知道HintManager中是没有设置表的SharedingValue的,所以 routedTables拿到的是全部的真实表。然后循环创建DataNode对象 就是 ds0.order_0 ds0.order_1 ds0.order_2。
开始返回数据 最终给到 最开始 route方法的数据 Collection dataNodes就是ds0.order_0 ds0.order_1 ds0.order_2 这三个数据。那后面的结果我就知道它会往这三个表中插入数据。