最後一個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<TMProperties> 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<Boolean> 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<String> keys = rpcClient.loadAllRemoteKey();
if (keys != null && keys.size() > 0) {
for (String key : keys) {
rpcClient.send(key, MessageCreator.newTxManager(notifyConnectParams));
}
}
return true;
}
public List<String> loadAllRemoteKey() {
List<String> 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結構