package com.dianping.pigeon.remoting.invoker.client;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import com.dianping.pigeon.log.Logger;
import com.dianping.pigeon.log.LoggerLoader;
import com.dianping.pigeon.monitor.Monitor;
import com.dianping.pigeon.monitor.MonitorLoader;
import com.dianping.pigeon.registry.RegistryManager;
import com.dianping.pigeon.registry.util.HeartBeatSupport;
import com.dianping.pigeon.remoting.common.channel.Channel;
import com.dianping.pigeon.remoting.common.codec.SerializerType;
import com.dianping.pigeon.remoting.common.domain.InvocationRequest;
import com.dianping.pigeon.remoting.common.domain.InvocationResponse;
import com.dianping.pigeon.remoting.common.domain.generic.GenericRequest;
import com.dianping.pigeon.remoting.common.util.Constants;
import com.dianping.pigeon.remoting.common.util.InvocationUtils;
import com.dianping.pigeon.remoting.invoker.Client;
import com.dianping.pigeon.remoting.invoker.concurrent.CallbackFuture;
import com.dianping.pigeon.remoting.invoker.util.InvokerUtils;
/**
* @author qi.yin
* 2016/09/21 下午7:58.
*/
public class HeartbeatTask implements Runnable {
private static final Logger logger = LoggerLoader.getLogger(HeartbeatTask.class);
private static AtomicLong heartBeatSeq = new AtomicLong();
private static final Monitor monitor = MonitorLoader.getMonitor();
private final Client client;
private final ClientConfig clientConfig;
private final HeartbeatStats heartbeatStats = new HeartbeatStats();
public HeartbeatTask(ClientConfig clientConfig, Client client) {
this.clientConfig = clientConfig;
this.client = client;
}
@Override
public void run() {
if (isSend(client.getAddress())) {
boolean allFailed = heartbeatChannel();
notifyClientStateChanged(allFailed);
}
}
private boolean heartbeatChannel() {
List<Channel> channels = this.client.getChannels();
boolean allFailed = true;
if (channels != null) {
for (int index = 0; index < channels.size(); index++) {
Channel channel = channels.get(index);
if (channel != null) {
try {
if (channel.isAvaliable()) {
boolean isSuccess = sendHeartBeat(client, channel);
if (isSuccess) {
allFailed = false;
}
}
} catch (Exception e) {
logger.warn("[run] heartbeat failed. Channel" + channel, e);
}
}
}
}
return allFailed;
}
private void notifyClientStateChanged(boolean allFailed) {
if (allFailed) {
heartbeatStats.incFailedCount();
} else {
heartbeatStats.incSuccessCount();
}
if (heartbeatStats.getFailedCount() >= clientConfig.getDeadThreshold()) {
if (clientConfig.isHeartbeatAutoPickOff()) {
if (client.isActive()) {
client.setActive(false);
disConnectChannel(client);
logger.info("[notifyClientStateChanged]heartbeat" + client + ", inactive addresses:" + client.getAddress());
monitor.logEvent("PigeonCall.heartbeat", "Deactivate", client.getAddress());
}
}
heartbeatStats.resetStats();
} else if (heartbeatStats.getSuccessCount() >= clientConfig.getHealthThreshold()) {
if (!client.isActive()) {
client.setActive(true);
logger.info("[notifyClientStateChanged]heartbeat" + client + ", active addresses:" + client.getAddress());
monitor.logEvent("PigeonCall.heartbeat", "Activate", client.getAddress());
}
heartbeatStats.resetStats();
}
}
private void disConnectChannel(Client client) {
List<Channel> channels = client.getChannels();
if (channels != null) {
for (Channel channel : channels) {
if (channel != null && channel.isAvaliable()) {
channel.disConnect();
logger.info("[disConnectChannel] close avaliable channel. address : " + channel.getRemoteAddressString());
}
}
}
}
public boolean sendHeartBeat(Client client, Channel channel) {
boolean isSuccess = true;
String address = channel.getRemoteAddressString();
InvocationRequest request = createHeartRequest(address);
try {
InvocationResponse response = null;
CallbackFuture future = new CallbackFuture();
InvokerUtils.sendRequest(client, channel, request, future);
response = future.getResponse(clientConfig.getHeartbeatTimeout());
if (response != null && !(response.getReturn() instanceof Exception)) {
isSuccess = true;
} else {
logger.info("[heartbeat] send heartbeat to server[" + address + "] failed.");
isSuccess = false;
}
} catch (Throwable e) {
logger.info("[heartbeat] send heartbeat to server[" + address + "] failed", e);
isSuccess = false;
}
return isSuccess;
}
private boolean isSend(String address) {
boolean supported = true;
byte heartBeatSupport = RegistryManager.getInstance().getServerHeartBeatSupportFromCache(address);
switch (HeartBeatSupport.findByValue(heartBeatSupport)) {
case NoSupport:
case ScannerOnly:
supported = false;
break;
case P2POnly:
case BothSupport:
default:
supported = true;
break;
}
return supported;
}
private boolean supported(String address) {
boolean supported = false;
try {
supported = RegistryManager.getInstance().isSupportNewProtocol(address);
} catch (Throwable t) {
supported = Constants.isSupportedNewProtocol();
logger.warn("get protocol support failed, set support to: " + supported);
}
return supported;
}
private InvocationRequest createHeartRequest0(String address) {
InvocationRequest request = InvocationUtils.newRequest(Constants.HEART_TASK_SERVICE + address, Constants.HEART_TASK_METHOD,
null, SerializerType.HESSIAN.getCode(), Constants.MESSAGE_TYPE_HEART, clientConfig.getHeartbeatTimeout(), null);
request.setSequence(generateHeartSeq());
request.setCreateMillisTime(System.currentTimeMillis());
request.setCallType(Constants.CALLTYPE_REPLY);
return request;
}
private InvocationRequest createHeartRequest_(String address) {
InvocationRequest request = new GenericRequest(Constants.HEART_TASK_SERVICE + address, Constants.HEART_TASK_METHOD,
null, SerializerType.THRIFT.getCode(), Constants.MESSAGE_TYPE_HEART, clientConfig.getHeartbeatTimeout());
request.setSequence(generateHeartSeq());
request.setCreateMillisTime(System.currentTimeMillis());
request.setCallType(Constants.CALLTYPE_REPLY);
return request;
}
private InvocationRequest createHeartRequest(String address) {
if (supported(address)) {
return createHeartRequest_(address);
} else {
return createHeartRequest0(address);
}
}
private long generateHeartSeq() {
return heartBeatSeq.getAndIncrement();
}
class HeartbeatStats {
private AtomicLong failedCount;
private AtomicLong successCount;
public HeartbeatStats() {
this(0L, 0L);
}
public HeartbeatStats(long failedCount, long successCount) {
this.failedCount = new AtomicLong(failedCount);
this.successCount = new AtomicLong(successCount);
}
public long getFailedCount() {
return failedCount.get();
}
public void incFailedCount() {
resetSuccessCount();
failedCount.incrementAndGet();
}
public void resetFailedCount() {
failedCount.set(0L);
}
public long getSuccessCount() {
return successCount.get();
}
public void incSuccessCount() {
resetFailedCount();
successCount.incrementAndGet();
}
public void resetSuccessCount() {
successCount.set(0L);
}
public void resetStats() {
resetSuccessCount();
resetFailedCount();
}
}
}