package com.weibo.api.motan.registry.consul;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.weibo.api.motan.registry.consul.client.MotanConsulClient;
import com.weibo.api.motan.util.ConcurrentHashSet;
import com.weibo.api.motan.util.LoggerUtil;
import com.weibo.api.motan.util.MotanSwitcherUtil;
/**
* consul 心跳管理类。 rpc服务把需要设置passing状态的serviceid注册到此类,
* 此类会定时对注册的serviceid设置passing状态(实际是对servcieid对应对checkid设置passing状态),
* 从而完成servivce的心跳。
* 开关开启后会进行心跳,开关关闭则停止心跳。
*
* @author zhanglei
*
*/
public class ConsulHeartbeatManager {
private MotanConsulClient client;
// 所有需要进行心跳的serviceid.
private ConcurrentHashSet<String> serviceIds = new ConcurrentHashSet<String>();
private ThreadPoolExecutor jobExecutor;
private ScheduledExecutorService heartbeatExecutor;
// 上一次心跳开关的状态
private boolean lastHeartBeatSwitcherStatus = false;
private volatile boolean currentHeartBeatSwitcherStatus = false;
// 开关检查次数。
private int switcherCheckTimes = 0;
public ConsulHeartbeatManager(MotanConsulClient client) {
this.client = client;
heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(
10000);
jobExecutor = new ThreadPoolExecutor(5, 30, 30 * 1000,
TimeUnit.MILLISECONDS, workQueue);
}
public void start() {
heartbeatExecutor.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
// 由于consul的check set pass会导致consul
// server的写磁盘操作,过于频繁的心跳会导致consul
// 性能问题,只能将心跳方式改为较长的周期进行一次探测。又因为想在关闭心跳开关后尽快感知
// 就将心跳改为以较小周期检测心跳开关是否变动,连续检测多次后给consul server发送一次心跳。
// TODO 改为开关listener方式。
try {
boolean switcherStatus = isHeartbeatOpen();
if (isSwitcherChange(switcherStatus)) { // 心跳开关状态变更
processHeartbeat(switcherStatus);
} else {// 心跳开关状态未变更
if (switcherStatus) {// 开关为开启状态,则连续检测超过MAX_SWITCHER_CHECK_TIMES次发送一次心跳
switcherCheckTimes++;
if (switcherCheckTimes >= ConsulConstants.MAX_SWITCHER_CHECK_TIMES) {
processHeartbeat(true);
switcherCheckTimes = 0;
}
}
}
} catch (Exception e) {
LoggerUtil.error("consul heartbeat executor err:",
e);
}
}
}, ConsulConstants.SWITCHER_CHECK_CIRCLE,
ConsulConstants.SWITCHER_CHECK_CIRCLE, TimeUnit.MILLISECONDS);
}
/**
* 判断心跳开关状态是否改变,如果心跳开关改变则更新lastHeartBeatSwitcherStatus为最新状态
*
* @param switcherStatus
* @return
*/
private boolean isSwitcherChange(boolean switcherStatus) {
boolean ret = false;
if (switcherStatus != lastHeartBeatSwitcherStatus) {
ret = true;
lastHeartBeatSwitcherStatus = switcherStatus;
LoggerUtil.info("heartbeat switcher change to " + switcherStatus);
}
return ret;
}
protected void processHeartbeat(boolean isPass) {
for (String serviceid : serviceIds) {
try {
jobExecutor.execute(new HeartbeatJob(serviceid, isPass));
} catch (RejectedExecutionException ree) {
LoggerUtil.error("execute heartbeat job fail! serviceid:"
+ serviceid + " is rejected");
}
}
}
public void close() {
heartbeatExecutor.shutdown();
jobExecutor.shutdown();
LoggerUtil.info("Consul heartbeatManager closed.");
}
/**
* 添加consul serviceid,添加后的serviceid会通过定时设置passing状态保持心跳。
*
* @param serviceid
*/
public void addHeartbeatServcieId(String serviceid) {
serviceIds.add(serviceid);
}
/**
* 移除serviceid,对应的serviceid不会在进行心跳。
*
* @param serviceid
*/
public void removeHeartbeatServiceId(String serviceid) {
serviceIds.remove(serviceid);
}
// 检查心跳开关是否打开
private boolean isHeartbeatOpen() {
return currentHeartBeatSwitcherStatus;
}
public void setHeartbeatOpen(boolean open) {
currentHeartBeatSwitcherStatus = open;
}
class HeartbeatJob implements Runnable {
private String serviceid;
private boolean isPass;
public HeartbeatJob(String serviceid, boolean isPass) {
super();
this.serviceid = serviceid;
this.isPass = isPass;
}
@Override
public void run() {
try {
if (isPass) {
client.checkPass(serviceid);
} else {
client.checkFail(serviceid);
}
} catch (Exception e) {
LoggerUtil.error(
"consul heartbeat-set check pass error!serviceid:"
+ serviceid, e);
}
}
}
public void setClient(MotanConsulClient client) {
this.client = client;
}
}