package com.networknt.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.networknt.consul.client.ConsulClient; import com.networknt.utility.ConcurrentHashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * consul heart beat manager. passing status service id is registered here, * and this class will set passing status for serviceId(in fact it is corresponding checkId of serviceId), * then the heart beat process is done. * * Switcher is used to enable heart beat or disable heart beat. * * @author zhanglei * */ public class ConsulHeartbeatManager { private static final Logger logger = LoggerFactory.getLogger(ConsulHeartbeatManager.class); private ConsulClient client; // all serviceIds that need heart beats. private ConcurrentHashSet<String> serviceIds = new ConcurrentHashSet<String>(); private ThreadPoolExecutor jobExecutor; private ScheduledExecutorService heartbeatExecutor; // last heart beat switcher status private boolean lastHeartBeatSwitcherStatus = false; private volatile boolean currentHeartBeatSwitcherStatus = false; // switcher check times private int switcherCheckTimes = 0; public ConsulHeartbeatManager(ConsulClient 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() { // Because consul check set pass triggers consul // server write operation,frequently heart beat will impact consul // performance,so heart beat takes long cycle and switcher check takes short cycle. // multiple check on switcher and then send one heart beat to consul server. // TODO change to switcher listener approach. try { boolean switcherStatus = isHeartbeatOpen(); if (isSwitcherChange(switcherStatus)) { // heart beat switcher status changed processHeartbeat(switcherStatus); } else {// heart beat switcher status not changed. if (switcherStatus) {// switcher is on, check MAX_SWITCHER_CHECK_TIMES and then send a heart beat switcherCheckTimes++; if (switcherCheckTimes >= ConsulConstants.MAX_SWITCHER_CHECK_TIMES) { processHeartbeat(true); switcherCheckTimes = 0; } } } } catch (Exception e) { logger.error("consul heartbeat executor err:", e); } } }, ConsulConstants.SWITCHER_CHECK_CIRCLE, ConsulConstants.SWITCHER_CHECK_CIRCLE, TimeUnit.MILLISECONDS); } /** * check heart beat switcher status, if switcher is changed, then change lastHeartBeatSwitcherStatus * to the latest status. * * @param switcherStatus * @return */ private boolean isSwitcherChange(boolean switcherStatus) { boolean ret = false; if (switcherStatus != lastHeartBeatSwitcherStatus) { ret = true; lastHeartBeatSwitcherStatus = switcherStatus; logger.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) { logger.error("execute heartbeat job fail! serviceId:" + serviceId + " is rejected"); } } } public void close() { heartbeatExecutor.shutdown(); jobExecutor.shutdown(); logger.info("Consul heartbeatManager closed."); } /** * Add consul serviceId,added serviceId will set passing status to keep sending heart beat. * * @param serviceId service Id */ public void addHeartbeatServcieId(String serviceId) { serviceIds.add(serviceId); } /** * remove serviceId,corresponding serviceId won't send heart beat * * @param serviceId service Id */ public void removeHeartbeatServiceId(String serviceId) { serviceIds.remove(serviceId); } // check if heart beat switcher is on 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) { logger.error( "consul heartbeat-set check pass error!serviceId:" + serviceId, e); } } } public void setClient(ConsulClient client) { this.client = client; } }