背景
基于既有项目修改shardingshpere分库分表踩坑记(二),在测试过程中又发现一种情况。在同一个事务中,对config表(非分库分表可以编辑,分库分表可以编辑,业务关联查询)进行保存和查询,会大概率出现报错。查找报错原因是查不到记录。
伪代码如下:
@Transactional
public void doSomething(){
Config config = new Config();
saveConfig();
config = getConfig();
//doingOthers
}
正常情况下,同一事务中,保存之后是可以查到记录的。
但这里有点特殊,踩坑记(二)中,将config表配置成只更新主库。而保存之后查询走的是单播路由,查的不一定是主库,也可能查其他库,这时由于事务还没提交。所以其他库是查不到这条记录的。所以这里只能特殊处理下。
方案
使用LocalThread缓存下。判断若当前线程有对config表进行编辑的话,单播路由只从主库查。
修改
- 增加单表修改线程副本类
public class ThreadLocalUtils {
public static TransmittableThreadLocal<Boolean> singleTableAlterThreadLocal = new TransmittableThreadLocal();
}
- 修改ShardingJDBCAop类,将单表编辑状态初始为false。
@Around("round()")
public Object round(ProceedingJoinPoint point) throws Throwable {
HintManager hintManager = HintManager.getInstance();
//单表编辑状态设为false
ThreadLocalUtils.singleTableAlterThreadLocal.set(false);
try {
String region = getRegion();
shardingRuleConfigurationProperties.getBindingTables().stream().map(s -> s.split(",")).flatMap(Arrays::stream).forEach(
table -> {
hintManager.addDatabaseShardingValue(table, region );
hintManager.addTableShardingValue(table, region );
});
return point.proceed();
} finally {
hintManager.close();
}
}
- 修改数据库路由类DatabaseBroadcastRoutingEngine,若为单表修改的话,线程副本状态设为true。
public RoutingResult route() {
RoutingResult result = new RoutingResult();
if(CollectionUtils.isNotEmpty(logicTables) && shardingShpereExtConfig != null && CollectionUtils.isNotEmpty(shardingShpereExtConfig.getSingleTables())){
List<String> singleTables = shardingShpereExtConfig.getSingleTables();
for (String table : logicTables){
if(singleTables.contains(table.toLowerCase())){
//单表编辑状态设为true
ThreadLocalUtils.singleTableAlterThreadLocal.set(true);
result.getRoutingUnits().add(new RoutingUnit(this.shardingRule.getShardingDataSourceNames().getDataSourceNames().iterator().next()));
return result;
}
}
}
Iterator var2 = this.shardingRule.getShardingDataSourceNames().getDataSourceNames().iterator();
while(var2.hasNext()) {
String each = (String)var2.next();
result.getRoutingUnits().add(new RoutingUnit(each));
}
return result;
}
- 重写单表路由类UnicastRoutingEngine
package org.apache.shardingsphere.core.route.type.unicast;
import com.augurit.farmhouse.common.config.ShardingShpereExtConfig;
import com.augurit.farmhouse.common.sharding.util.CollectionUtils;
import com.augurit.farmhouse.common.sharding.util.SpringContextUtils;
import com.augurit.farmhouse.common.sharding.util.ThreadLocalUtils;
import com.google.common.collect.Sets;
import java.beans.ConstructorProperties;
import java.util.*;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.shardingsphere.core.config.ShardingConfigurationException;
import org.apache.shardingsphere.core.route.type.RoutingEngine;
import org.apache.shardingsphere.core.route.type.RoutingResult;
import org.apache.shardingsphere.core.route.type.RoutingUnit;
import org.apache.shardingsphere.core.route.type.TableUnit;
import org.apache.shardingsphere.core.rule.DataNode;
import org.apache.shardingsphere.core.rule.ShardingRule;
import org.apache.shardingsphere.core.rule.TableRule;
public final class UnicastRoutingEngine implements RoutingEngine {
private final ShardingRule shardingRule;
private final Collection<String> logicTables;
private final ShardingShpereExtConfig shardingShpereExtConfig;
public RoutingResult route() {
RoutingResult result = new RoutingResult();
ArrayList tableUnits;
//是否单表查询
boolean singleTable =false;
if(CollectionUtils.isNotEmpty(logicTables) && shardingShpereExtConfig != null && CollectionUtils.isNotEmpty(shardingShpereExtConfig.getSingleTables())){
List<String> singleTables = shardingShpereExtConfig.getSingleTables();
singleTable = logicTables.stream().anyMatch(i->singleTables.contains(i.toLowerCase()));
}
//若是的话,判断当前线程的单表编辑状态,是否编辑过,若是的话则直接查主库
if(singleTable && BooleanUtils.isTrue(ThreadLocalUtils.singleTableAlterThreadLocal.get())){
result.getRoutingUnits().add(new RoutingUnit(this.shardingRule.getShardingDataSourceNames().getDataSourceNames().iterator().next()));
return result;
//若不是的话,走原逻辑
}else if (this.shardingRule.isAllBroadcastTables(this.logicTables)) {
tableUnits = new ArrayList(this.logicTables.size());
Iterator var3 = this.logicTables.iterator();
while(var3.hasNext()) {
String each = (String)var3.next();
tableUnits.add(new TableUnit(each, each));
}
RoutingUnit routingUnit = new RoutingUnit(this.shardingRule.getShardingDataSourceNames().getRandomDataSourceName());
routingUnit.getTableUnits().addAll(tableUnits);
result.getRoutingUnits().add(routingUnit);
} else if (this.logicTables.isEmpty()) {
result.getRoutingUnits().add(new RoutingUnit(this.shardingRule.getShardingDataSourceNames().getRandomDataSourceName()));
} else if (1 == this.logicTables.size()) {
String logicTableName = (String)this.logicTables.iterator().next();
if (!this.shardingRule.findTableRule(logicTableName).isPresent()) {
result.getRoutingUnits().add(new RoutingUnit(this.shardingRule.getShardingDataSourceNames().getRandomDataSourceName()));
return result;
}
DataNode dataNode = this.shardingRule.getDataNode(logicTableName);
RoutingUnit routingUnit = new RoutingUnit(dataNode.getDataSourceName());
routingUnit.getTableUnits().add(new TableUnit(logicTableName, dataNode.getTableName()));
result.getRoutingUnits().add(routingUnit);
} else {
tableUnits = new ArrayList(this.logicTables.size());
Set<String> availableDatasourceNames = null;
boolean first = true;
Iterator var5 = this.logicTables.iterator();
while(var5.hasNext()) {
String each = (String)var5.next();
TableRule tableRule = this.shardingRule.getTableRule(each);
DataNode dataNode = (DataNode)tableRule.getActualDataNodes().get(0);
tableUnits.add(new TableUnit(each, dataNode.getTableName()));
Set<String> currentDataSourceNames = new HashSet(tableRule.getActualDatasourceNames().size());
Iterator var10 = tableRule.getActualDataNodes().iterator();
while(var10.hasNext()) {
DataNode eachDataNode = (DataNode)var10.next();
currentDataSourceNames.add(eachDataNode.getDataSourceName());
}
if (first) {
availableDatasourceNames = currentDataSourceNames;
first = false;
} else {
availableDatasourceNames = Sets.intersection((Set)availableDatasourceNames, currentDataSourceNames);
}
}
if (((Set)availableDatasourceNames).isEmpty()) {
throw new ShardingConfigurationException("Cannot find actual datasource intersection for logic tables: %s", new Object[]{this.logicTables});
}
RoutingUnit routingUnit = new RoutingUnit(this.shardingRule.getShardingDataSourceNames().getRandomDataSourceName((Collection)availableDatasourceNames));
routingUnit.getTableUnits().addAll(tableUnits);
result.getRoutingUnits().add(routingUnit);
}
return result;
}
@ConstructorProperties({"shardingRule", "logicTables","shardingShpereExtConfig"})
public UnicastRoutingEngine(ShardingRule shardingRule, Collection<String> logicTables,ShardingShpereExtConfig shardingShpereExtConfig) {
this.shardingRule = shardingRule;
this.logicTables = logicTables;
this.shardingShpereExtConfig = shardingShpereExtConfig;
}
@ConstructorProperties({"shardingRule", "logicTables"})
public UnicastRoutingEngine(ShardingRule shardingRule, Collection<String> logicTables) {
this.shardingRule = shardingRule;
this.logicTables = logicTables;
this.shardingShpereExtConfig = (ShardingShpereExtConfig) SpringContextUtils.getBean("shardingShpereExtConfig");
}
@ConstructorProperties({"shardingRule"})
public UnicastRoutingEngine(ShardingRule shardingRule) {
this.shardingRule = shardingRule;
this.logicTables = null;
this.shardingShpereExtConfig = (ShardingShpereExtConfig) SpringContextUtils.getBean("shardingShpereExtConfig");
}
}
结论
1、线程入口将单表编辑状态设为false
2、在数据源广播表判断若是单表编辑的话,将单表编辑状态设为true
3、单播路由判断单表编辑状态。若修改过(true)则查主库,若未修改过则采用原单播路由逻辑