最后一个TxLcnInitializer。
TMAutoCluster从名称上看是自动集群,当我们启动一个新的服务端时,不用额外的配置也不需要重新启动客户端。所有的客户端都会感知到新的服务端并且与之链接。
整体的逻辑是这样的,如果启动一个服务端A,启动时这个A会把自己的信息存入redis的hash中,hash为tm.instances,hk为host:TransactionPort,hv为HttpPort。
例:
如果服务端A host 为192.168.120.10 设置的server.port=7970,则redis 中的tm.instances 一个值为KEY 为192.168.120.10:7970,VALUE8070。然后客户端都连接这个服务端A。
过段时间后,又需要启动一个服务端B,启动B时会先去(A服务也会这样)redis上获取tm.instances上所有的值(这里只有服务端A),排除掉自己的信息后(如果有),根据redis存储的信息通过restTemplate以自身的地址信息为参数去调用服务端A,服务端A收到服务端B的请求会给与自己相连的所有的channel发送信息要求所有的客户端连接服务端B,客户端接收到服务端A的消息后会新启动netty客户端去连接服务端B。这就是自动集群的所有流程。
下面我们通过代码看下具体的实现步骤
public void init() throws Exception {
// 1. 通知 TC 建立连接
List tmList = fastStorage.findTMProperties().stream()
.filter(tmProperties ->
!tmProperties.getHost().equals(txManagerConfig.getHost()) || !tmProperties.getTransactionPort().equals(txManagerConfig.getPort()))
.collect(Collectors.toList());
for (TMProperties properties : tmList) {
NotifyConnectParams notifyConnectParams = new NotifyConnectParams();
notifyConnectParams.setHost(txManagerConfig.getHost());
notifyConnectParams.setPort(txManagerConfig.getPort());
//构造url
String url = String.format(MANAGER_REFRESH_URL, properties.getHost(), properties.getHttpPort());
try {
//调用其他服务
ResponseEntity res = restTemplate.postForEntity(url, notifyConnectParams, Boolean.class);
if (res.getStatusCode().equals(HttpStatus.OK) || res.getStatusCode().is5xxServerError()) {
log.info("manager auto refresh res->{}", res);
break;
} else {
fastStorage.removeTMProperties(properties.getHost(), properties.getTransactionPort());
}
} catch (Exception e) {
log.error("manager auto refresh error: {}", e.getMessage());
//check exception then remove.
if (e instanceof ResourceAccessException) {
ResourceAccessException resourceAccessException = (ResourceAccessException) e;
if (resourceAccessException.getCause() != null && resourceAccessException.getCause() instanceof ConnectException) {
//can't access .
fastStorage.removeTMProperties(properties.getHost(), properties.getTransactionPort());
}
}
}
}
// 2. 保存TM 到快速存储
if (StringUtils.hasText(txManagerConfig.getHost())) {
TMProperties tmProperties = new TMProperties();
tmProperties.setHttpPort(ApplicationInformation.serverPort(serverProperties));
tmProperties.setHost(txManagerConfig.getHost());
tmProperties.setTransactionPort(txManagerConfig.getPort());
fastStorage.saveTMProperties(tmProperties);
}
}
主要做了两件事
1、获取redis的值,排除自己的信息后用restTemplate去调用所有redis存储的信息地址。
2、把自己的信息保存到redis
调用代码如下
public class TxManagerController {
@Autowired
private ManagerService managerService;
@PostMapping("/refresh")
public boolean refresh(@RequestBody NotifyConnectParams notifyConnectParams) throws RpcException {
return managerService.refresh(notifyConnectParams);
}
}
public boolean refresh(NotifyConnectParams notifyConnectParams) throws RpcException {
List keys = rpcClient.loadAllRemoteKey();
if (keys != null && keys.size() > 0) {
for (String key : keys) {
rpcClient.send(key, MessageCreator.newTxManager(notifyConnectParams));
}
}
return true;
}
public List loadAllRemoteKey() {
List allKeys = new ArrayList<>();
for (Channel channel : channels) {
allKeys.add(channel.remoteAddress().toString());
}
return allKeys;
}
可以看到调用是对每一个连接的channel都会去通知
保存TM信息
public void saveTMProperties(TMProperties tmProperties) {
Objects.requireNonNull(tmProperties);
stringRedisTemplate.opsForHash().put(REDIS_TM_LIST,
tmProperties.getHost() + ":" + tmProperties.getTransactionPort(), String.valueOf(tmProperties.getHttpPort()));
}
可以看到采用的hash结构