package com.hqyg.disjob.rpc.client.proxy;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import com.hqyg.disjob.common.Constants;
import com.hqyg.disjob.common.util.LoggerUtil;
import com.hqyg.disjob.rpc.codec.RpcRequest;
/**
* 服务端需要维持有所可用的客户端。以防连接断开时发送失败的消息需要随着重连后心跳包进行返回
* @author Disjob
*
*/
public class ServerLinkedService {
private final static ConcurrentHashMap<Integer, NettyChannel> poolClientLinkedMap = new ConcurrentHashMap<Integer, NettyChannel>();
/**
* key:client ip:port,values: the multi linked of client
*/
private final static ConcurrentHashMap<String, Channel> clientLinkedMap = new ConcurrentHashMap<String, Channel>();
/**
* hostPost=ip:port.for example 127.0.0.1:9501。给单根管道发送消息时用
*/
public final static ConcurrentHashMap<String, String> clientProvidersMap = new ConcurrentHashMap<String, String>();
/**
* key:ip:port 表示向某个服务端发送失败的 rpc 请求列表
*/
public final static ConcurrentHashMap<String,List<RpcRequest>> sendFailRpcRequestMap = new ConcurrentHashMap<String,List<RpcRequest>>();
public final static ConcurrentHashMap<String, ReentrantLock> segmentLockMap = new ConcurrentHashMap<String, ReentrantLock>();
public final static ConcurrentHashMap<String, Date> ipHostLastReSendDateMap = new ConcurrentHashMap<String, Date>();
/**
* 收到一个心跳消息时,看看这台有没有发送失败的消息,如果有的话,则随着心跳消息一起重发
* @param key ip:port.单根管道式一个服务端只对应一个 Channel
* @param channel
*/
public static void putChannel(String key,Channel channel){
if(!channel.isActive()){
return ;
}
String address = getRemoterAddress(channel);//key 和当前这根管道是否匹配:key=ip:port,方法返回值为:ip:port
if(key.indexOf(address)<0){
return ;
}
clientLinkedMap.put(key, channel);//(key, channels);
}
/**
* @param key compose of host:port
* @return
*/
public static Channel getChannel(String key,RpcClient rpcClient){
//1、如果这个服务端在zk 上的会话已经失效了,则不进行重连
if(!clientProvidersMap.containsKey(key)){
return null;
}
//2、会话还在,则进行重连
Channel channel = clientLinkedMap.get(key);
if ((channel==null || !channel.isActive()) && Constants.RECON_COUNT_FAIL>0){
rpcClient.connect(Constants.RECON_COUNT_FAIL);
channel = clientLinkedMap.get(key);
}
return channel;
}
public static int putNettyChannel(NettyChannel nettyChannel){
if(nettyChannel.getChannel()!=null&&nettyChannel.getChannel().isActive()){
poolClientLinkedMap.putIfAbsent(nettyChannel.getChannel().hashCode(), nettyChannel);
}
return poolClientLinkedMap.size();
}
public static NettyChannel getNettyChannel(Channel channel){
if(channel == null){
return null ;
}
return poolClientLinkedMap.get(channel.hashCode());
}
/**
* 这个方法返回的是 ip:port
* @param channel
* @return
*/
public static String getRemoterAddress(Channel channel){
InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress();
if(insocket == null){
return "";
}
return insocket.toString().substring(1);
}
/**
* 发送失败时,会将发送失败的这个消息存储到这一台服务器列表中。也就是说:每一台服务器都保存了发送 rpc 时发送失败的消息。一分钟后再重发。
*/
public static void putFailRpcRequest(String ipPort,RpcRequest rpcRequest){
if(StringUtils.isEmpty(ipPort)){
return ;
}
ReentrantLock lock = getReentrantLock(ipPort);
List<RpcRequest> rpcRequestList = sendFailRpcRequestMap.putIfAbsent(ipPort, new ArrayList<RpcRequest>());
if(rpcRequestList == null){
rpcRequestList = sendFailRpcRequestMap.get(ipPort);
}
try{
lock.lock();
rpcRequestList.add(rpcRequest);
}finally{
lock.unlock();
}
}
/**
* 发送失败时,会将发送失败的这个消息存储到这一台服务器列表中。也就是说:每一台服务器都保存了发送 rpc 时发送失败的消息。一分钟后再重发。
* bug 修复:负责调度的服务器发现job providers 有一个进程挂了,则将发送给这个进程失败的 rpc request merger 到另一个 provider 上去
*/
public static void putFailRpcRequest(String ipPort,List<RpcRequest> rpcRequests){
if(StringUtils.isEmpty(ipPort) || CollectionUtils.isEmpty(rpcRequests)){
return ;
}
ReentrantLock lock = getReentrantLock(ipPort);
List<RpcRequest> rpcRequestList = sendFailRpcRequestMap.putIfAbsent(ipPort, new ArrayList<RpcRequest>());
if(rpcRequestList == null){
rpcRequestList = sendFailRpcRequestMap.get(ipPort);
}
try{
lock.lock();
rpcRequestList.addAll(rpcRequests);
}finally{
lock.unlock();
}
}
private static ReentrantLock getReentrantLock(String ipHost){
ReentrantLock lock = segmentLockMap.putIfAbsent(ipHost, new ReentrantLock());
if(lock == null){
lock = segmentLockMap.get(ipHost);
}
return lock;
}
/**
* 跟这根管道相同一组的其他管道消息发送失败都用这根发
* @param channel
*/
public static void checkReSendRpc(Channel channel){
String ipHost = getRemoterAddress(channel);
Date lastUpdate = ipHostLastReSendDateMap.get(ipHost);
if(lastUpdate == null){
ipHostLastReSendDateMap.put(ipHost, new Date());
return ;
}
long interval = (System.currentTimeMillis()-lastUpdate.getTime()) / 1000;//单位:s
if(interval/60 < 1){
return ;
}
ipHostLastReSendDateMap.put(ipHost, new Date());
//1 分钟后重发失败的消息
List<RpcRequest> sendFailRpc = getFailRpcRequestList(ipHost);
if(sendFailRpc==null||sendFailRpc.isEmpty()){
return ;
}
LoggerUtil.warn(ipHost +" 重新发送失败的消息,个数:"+sendFailRpc.size());
for(RpcRequest rpcRequest : sendFailRpc){
ChannelFuture channelFuture = channel.writeAndFlush(rpcRequest);
channelFuture.addListener(new WriterChannelFutureListener(channel, rpcRequest));
LoggerUtil.warn(ipHost +" 重新发送失败的消息:"+rpcRequest.getData().toString());
}
}
private static List<RpcRequest> getFailRpcRequestList(String ipHost){
List<RpcRequest> rpcRequests = sendFailRpcRequestMap.get(ipHost);
if(rpcRequests == null||rpcRequests.isEmpty()){
return null;
}
ReentrantLock lock = segmentLockMap.get(ipHost);
if(lock == null){
lock = new ReentrantLock();
segmentLockMap.put(ipHost, lock);
}
List<RpcRequest> tmpRpcRequests = new ArrayList<RpcRequest>();
try{
lock.lock();
tmpRpcRequests.addAll(rpcRequests);
rpcRequests.clear();
}finally{
lock.unlock();
}
return tmpRpcRequests;
}
/**
* 将检测到一个 provider 宕机了,则将这台发送失败的 rpc 消息 合并到同一组其他的一个在线 provider 中去
* @param shutdownProvider-> ip:port
* @param onlineProvider-> ip:port
*/
public static void mergerFailRpcRequest(String shutdownProviderIp,String onlineProviderIp){
putFailRpcRequest(onlineProviderIp, getFailRpcRequestList(shutdownProviderIp));
}
}