io.seata.core.rpc.ChannelManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of seata-core Show documentation
Show all versions of seata-core Show documentation
core for Seata built with Maven
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.seata.core.rpc;
import java.net.SocketAddress;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import io.seata.common.Constants;
import io.seata.common.exception.FrameworkException;
import io.seata.common.util.StringUtils;
import io.seata.core.protocol.IncompatibleVersionException;
import io.seata.core.protocol.RegisterRMRequest;
import io.seata.core.protocol.RegisterTMRequest;
import io.seata.core.protocol.Version;
import io.netty.channel.Channel;
import io.seata.core.rpc.netty.NettyPoolKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The type channel manager.
*
* @author jimin.jm @alibaba-inc.com
* @date 2018 /12/07
*/
public class ChannelManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class);
private static final ConcurrentMap IDENTIFIED_CHANNELS
= new ConcurrentHashMap();
/**
* resourceId -> applicationId -> ip -> port -> RpcContext
*/
private static final ConcurrentMap>>>
RM_CHANNELS = new ConcurrentHashMap>>>();
/**
* ip+appname,port
*/
private static final ConcurrentMap> TM_CHANNELS
= new ConcurrentHashMap>();
/**
* Is registered boolean.
*
* @param channel the channel
* @return the boolean
*/
public static boolean isRegistered(Channel channel) {
return IDENTIFIED_CHANNELS.containsKey(channel);
}
/**
* Gets get role from channel.
*
* @param channel the channel
* @return the get role from channel
*/
public static NettyPoolKey.TransactionRole getRoleFromChannel(Channel channel) {
if (IDENTIFIED_CHANNELS.containsKey(channel)) {
return IDENTIFIED_CHANNELS.get(channel).getClientRole();
}
return null;
}
/**
* Gets get context from identified.
*
* @param channel the channel
* @return the get context from identified
*/
public static RpcContext getContextFromIdentified(Channel channel) {
return IDENTIFIED_CHANNELS.get(channel);
}
private static String buildClientId(String applicationId, Channel channel) {
return applicationId + Constants.CLIENT_ID_SPLIT_CHAR + getAddressFromChannel(channel);
}
private static String[] readClientId(String clientId) {
return clientId.split(Constants.CLIENT_ID_SPLIT_CHAR);
}
private static RpcContext buildChannelHolder(NettyPoolKey.TransactionRole clientRole, String version, String applicationId,
String txServiceGroup, String dbkeys, Channel channel) {
RpcContext holder = new RpcContext();
holder.setClientRole(clientRole);
holder.setVersion(version);
holder.setClientId(buildClientId(applicationId, channel));
holder.setApplicationId(applicationId);
holder.setTransactionServiceGroup(txServiceGroup);
holder.addResources(dbKeytoSet(dbkeys));
holder.setChannel(channel);
return holder;
}
/**
* Register tm channel.
*
* @param request the request
* @param channel the channel
* @throws IncompatibleVersionException the incompatible version exception
*/
public static void registerTMChannel(RegisterTMRequest request, Channel channel)
throws IncompatibleVersionException {
Version.checkVersion(request.getVersion());
RpcContext rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.TMROLE, request.getVersion(),
request.getApplicationId(),
request.getTransactionServiceGroup(),
null, channel);
rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS);
String clientIdentified = rpcContext.getApplicationId() + Constants.CLIENT_ID_SPLIT_CHAR
+ getClientIpFromChannel(channel);
TM_CHANNELS.putIfAbsent(clientIdentified, new ConcurrentHashMap());
ConcurrentMap clientIdentifiedMap = TM_CHANNELS.get(clientIdentified);
rpcContext.holdInClientChannels(clientIdentifiedMap);
}
/**
* Register rm channel.
*
* @param resourceManagerRequest the resource manager request
* @param channel the channel
* @throws IncompatibleVersionException the incompatible version exception
*/
public static void registerRMChannel(RegisterRMRequest resourceManagerRequest, Channel channel)
throws IncompatibleVersionException {
Version.checkVersion(resourceManagerRequest.getVersion());
Set dbkeySet = dbKeytoSet(resourceManagerRequest.getResourceIds());
RpcContext rpcContext;
if (!IDENTIFIED_CHANNELS.containsKey(channel)) {
rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.RMROLE, resourceManagerRequest.getVersion(),
resourceManagerRequest.getApplicationId(), resourceManagerRequest.getTransactionServiceGroup(),
resourceManagerRequest.getResourceIds(), channel);
rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS);
} else {
rpcContext = IDENTIFIED_CHANNELS.get(channel);
rpcContext.addResources(dbkeySet);
}
if (null == dbkeySet || dbkeySet.isEmpty()) { return; }
for (String resourceId : dbkeySet) {
RM_CHANNELS.putIfAbsent(resourceId,
new ConcurrentHashMap>>());
ConcurrentMap>> applicationIdMap
= RM_CHANNELS.get(resourceId);
applicationIdMap.putIfAbsent(resourceManagerRequest.getApplicationId(),
new ConcurrentHashMap>());
ConcurrentMap> clientIpMap = applicationIdMap.get(
resourceManagerRequest.getApplicationId());
String clientIp = getClientIpFromChannel(channel);
clientIpMap.putIfAbsent(clientIp, new ConcurrentHashMap());
ConcurrentMap portMap = clientIpMap.get(clientIp);
rpcContext.holdInResourceManagerChannels(resourceId, portMap);
updateChannelsResource(resourceId, clientIp, resourceManagerRequest.getApplicationId());
}
}
private static void updateChannelsResource(String resourceId, String clientIp, String applicationId) {
ConcurrentMap sourcePortMap = RM_CHANNELS.get(resourceId).get(applicationId).get(clientIp);
for (ConcurrentMap.Entry>>> rmChannelEntry : RM_CHANNELS.entrySet()) {
if (rmChannelEntry.getKey().equals(resourceId)) { continue; }
ConcurrentMap>> applicationIdMap = rmChannelEntry.getValue();
if (!applicationIdMap.containsKey(applicationId)) { continue; }
ConcurrentMap> clientIpMap = applicationIdMap.get(applicationId);
if (!clientIpMap.containsKey(clientIp)) { continue; }
ConcurrentMap portMap = clientIpMap.get(clientIp);
for (ConcurrentMap.Entry portMapEntry : portMap.entrySet()) {
Integer port = portMapEntry.getKey();
if (!sourcePortMap.containsKey(port)) {
RpcContext rpcContext = portMapEntry.getValue();
sourcePortMap.put(port, rpcContext);
rpcContext.holdInResourceManagerChannels(resourceId, port);
}
}
}
}
private static String getAddressFromChannel(Channel channel) {
SocketAddress socketAddress = channel.remoteAddress();
String address = socketAddress.toString();
if (socketAddress.toString().indexOf(Constants.ENDPOINT_BEGIN_CHAR) == 0) {
address = socketAddress.toString().substring(Constants.ENDPOINT_BEGIN_CHAR.length());
}
return address;
}
private static String getClientIpFromChannel(Channel channel) {
String address = getAddressFromChannel(channel);
String clientIp = address;
if (clientIp.contains(Constants.IP_PORT_SPLIT_CHAR)) {
clientIp = clientIp.substring(0, clientIp.lastIndexOf(Constants.IP_PORT_SPLIT_CHAR));
}
return clientIp;
}
private static Integer getClientPortFromChannel(Channel channel) {
String address = getAddressFromChannel(channel);
Integer port = 0;
try {
if (address.contains(Constants.IP_PORT_SPLIT_CHAR)) {
port = Integer.parseInt(address.substring(address.lastIndexOf(Constants.IP_PORT_SPLIT_CHAR) + 1));
}
} catch (NumberFormatException exx) {
LOGGER.error(exx.getMessage());
}
return port;
}
private static Set dbKeytoSet(String dbkey) {
if (StringUtils.isNullOrEmpty(dbkey)) {
return null;
}
Set set = new HashSet();
for (String s : dbkey.split(Constants.DBKEYS_SPLIT_CHAR)) {
set.add(s);
}
return set;
}
/**
* Release rpc context.
*
* @param channel the channel
*/
public static void releaseRpcContext(Channel channel) {
if (IDENTIFIED_CHANNELS.containsKey(channel)) {
RpcContext rpcContext = getContextFromIdentified(channel);
rpcContext.release();
}
}
/**
* Gets get same income client channel.
*
* @param channel the channel
* @return the get same income client channel
*/
public static Channel getSameClientChannel(Channel channel) {
if (channel.isActive()) {
return channel;
}
RpcContext rpcContext = getContextFromIdentified(channel);
if (null == rpcContext) {
LOGGER.error("rpcContext is null,channel:" + channel + ",active:" + channel.isActive());
return null;
}
if (rpcContext.getChannel().isActive()) {
//recheck
return rpcContext.getChannel();
}
Integer clientPort = getClientPortFromChannel(channel);
NettyPoolKey.TransactionRole clientRole = rpcContext.getClientRole();
if (clientRole == NettyPoolKey.TransactionRole.TMROLE) {
String clientIdentified = rpcContext.getApplicationId() + Constants.CLIENT_ID_SPLIT_CHAR
+ getClientIpFromChannel(channel);
if (!TM_CHANNELS.containsKey(clientIdentified)) {
return null;
}
ConcurrentMap clientRpcMap = TM_CHANNELS.get(clientIdentified);
return getChannelFromSameClientMap(clientRpcMap, clientPort);
} else if (clientRole == NettyPoolKey.TransactionRole.RMROLE) {
for (Map clientRmMap : rpcContext.getClientRMHolderMap().values()) {
Channel sameClientChannel = getChannelFromSameClientMap(clientRmMap, clientPort);
if (null != sameClientChannel) {
return sameClientChannel;
}
}
}
return null;
}
private static Channel getChannelFromSameClientMap(Map clientChannelMap, int exclusivePort) {
if (null != clientChannelMap && !clientChannelMap.isEmpty()) {
for (ConcurrentMap.Entry entry : clientChannelMap.entrySet()) {
if (entry.getKey().intValue() == exclusivePort) {
clientChannelMap.remove(entry.getKey());
continue;
}
Channel channel = entry.getValue().getChannel();
if (channel.isActive()) { return channel; }
clientChannelMap.remove(entry.getKey());
}
}
return null;
}
/**
* Gets get channel.
*
* @param resourceId Resource ID
* @param clientId Client ID - ApplicationId:IP:Port
* @return Corresponding channel, NULL if not found.
*/
public static Channel getChannel(String resourceId, String clientId) {
Channel resultChannel = null;
String[] clientIdInfo = readClientId(clientId);
if (clientIdInfo == null || clientIdInfo.length != 3) {
throw new FrameworkException("Invalid Client ID: " + clientId);
}
String targetApplicationId = clientIdInfo[0];
String targetIP = clientIdInfo[1];
int targetPort = Integer.parseInt(clientIdInfo[2]);
ConcurrentMap>> applicationIdMap = RM_CHANNELS.get(resourceId);
if (targetApplicationId == null || applicationIdMap == null || applicationIdMap.isEmpty()) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("No channel is available for resource[" + resourceId + "]");
}
return null;
}
ConcurrentMap> ipMap = applicationIdMap.get(targetApplicationId);
if (null != ipMap && !ipMap.isEmpty()) {
// Firstly, try to find the original channel through which the branch was registered.
ConcurrentMap portMapOnTargetIP = ipMap.get(targetIP);
if (portMapOnTargetIP != null && !portMapOnTargetIP.isEmpty()) {
RpcContext exactRpcContext = portMapOnTargetIP.get(targetPort);
if (exactRpcContext != null) {
Channel channel = exactRpcContext.getChannel();
if (channel.isActive()) {
resultChannel = channel;
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Just got exactly the one " + channel + " for " + clientId);
}
} else {
if (portMapOnTargetIP.remove(targetPort, exactRpcContext)) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Removed inactive " + channel);
}
}
}
}
// The original channel was broken, try another one.
if (resultChannel == null) {
for (ConcurrentMap.Entry portMapOnTargetIPEntry : portMapOnTargetIP
.entrySet()) {
Channel channel = portMapOnTargetIPEntry.getValue().getChannel();
if (channel.isActive()) {
resultChannel = channel;
if (LOGGER.isInfoEnabled()) {
LOGGER.info(
"Choose " + channel + " on the same IP[" + targetIP + "] as alternative of "
+ clientId);
}
break;
} else {
if (portMapOnTargetIP.remove(portMapOnTargetIPEntry.getKey(),
portMapOnTargetIPEntry.getValue())) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Removed inactive " + channel);
}
}
}
}
}
}
// No channel on the this app node, try another one.
if (resultChannel == null) {
for (ConcurrentMap.Entry> ipMapEntry : ipMap
.entrySet()) {
if (ipMapEntry.getKey().equals(targetIP)) { continue; }
ConcurrentMap portMapOnOtherIP = ipMapEntry.getValue();
if (portMapOnOtherIP == null || portMapOnOtherIP.isEmpty()) {
continue;
}
for (ConcurrentMap.Entry portMapOnOtherIPEntry : portMapOnOtherIP.entrySet()) {
Channel channel = portMapOnOtherIPEntry.getValue().getChannel();
if (channel.isActive()) {
resultChannel = channel;
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Choose " + channel + " on the same application[" + targetApplicationId
+ "] as alternative of "
+ clientId);
}
break;
} else {
if (portMapOnOtherIP.remove(portMapOnOtherIPEntry.getKey(),
portMapOnOtherIPEntry.getValue())) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Removed inactive " + channel);
}
}
}
}
if (resultChannel != null) { break; }
}
}
}
if (resultChannel == null) {
resultChannel = tryOtherApp(applicationIdMap, targetApplicationId);
if (resultChannel == null) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("No channel is available for resource[" + resourceId
+ "] as alternative of "
+ clientId);
}
} else {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Choose " + resultChannel + " on the same resource[" + resourceId
+ "] as alternative of "
+ clientId);
}
}
}
return resultChannel;
}
private static Channel tryOtherApp(ConcurrentMap>> applicationIdMap, String myApplicationId) {
Channel chosenChannel = null;
for (ConcurrentMap.Entry>> applicationIdMapEntry : applicationIdMap
.entrySet()) {
if (applicationIdMapEntry.getKey().equals(myApplicationId)) {
continue;
}
ConcurrentMap> targetIPMap = applicationIdMapEntry.getValue();
if (targetIPMap == null || targetIPMap.isEmpty()) {
continue;
}
for (ConcurrentMap.Entry> targetIPMapEntry : targetIPMap
.entrySet()) {
ConcurrentMap portMap = targetIPMapEntry.getValue();
if (portMap == null || portMap.isEmpty()) {
continue;
}
for (ConcurrentMap.Entry portMapEntry : portMap.entrySet()) {
Channel channel = portMapEntry.getValue().getChannel();
if (channel.isActive()) {
chosenChannel = channel;
break;
} else {
if (portMap.remove(portMapEntry.getKey(), portMapEntry.getValue())) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Removed inactive " + channel);
}
}
}
}
if (chosenChannel != null) { break; }
}
if (chosenChannel != null) { break; }
}
return chosenChannel;
}
}