保险丝过载保护代理类, 其核心思想是监控外部依赖服务的调用情况,如果外部服务调用超时或者失败超过一定 比率,则断开保险丝, 即不再调用外部服务而直接返回失败。保险丝断开状态会持续一段时间, 超时之后才重 新允许调用外部服务,此时若发现外部服务可用, 则合上保险丝,恢复到原始状态。保险丝有三个状态,正 常,生病和死亡状态,各个状态下的调用对应不同的处理逻辑。目前该算法在58大量使用,我们根据各个服务保 险丝的断开次数可以监控到客户端调用各个服务的服务质量。
保险丝算法流程如下:
实现代码如下:
/** * 保险丝过载保护代理类, 其核心思想是监控外部依赖服务的调用情况,如果外部服务调用超时或者失败超过一定比率,则断开保险丝, 即不再调用外部服务而直接返回失败。 * 保险丝断开状态会持续一段时间, 超时之后才重新允许调用外部服务, 此时若发现外部服务可用, 则合上保险丝,恢复到原始状态。 * 保险丝有三个状态,正常,生病和死亡状态,各个状态下的调用对应不同的处理逻辑。 * * @Author:caimin * @Since:2015年10月27日 * @Version: */ public class FuseAbandonProxy implements InvocationHandler { private static final Log log = LogFactory.getLog(FuseAbandonProxy.class); //超时时间 private int overtime = 0; //服务名称,主要用来记录日志 private String name = null; //真实对象 private Object proxy = null; //是否抛出拒绝异常 private boolean isThrowRefuseException=false; public FuseAbandonProxy(Object proxy, String name) { this.proxy = proxy; this.overtime = 100; this.name = name; health.set(FULL_HEALTH); } public FuseAbandonProxy(Object proxy, String name,boolean isThrowRefuseException) { this.proxy = proxy; this.overtime = 100; this.name = name; this.isThrowRefuseException=isThrowRefuseException; health.set(FULL_HEALTH); } public FuseAbandonProxy(Object proxy, int overtime, String name) { this.proxy = proxy; this.overtime = overtime; this.name = name; health.set(FULL_HEALTH); } public FuseAbandonProxy(Object proxy, int overtime, String name,boolean isThrowRefuseException) { this.proxy = proxy; this.overtime = overtime; this.name = name; this.isThrowRefuseException=isThrowRefuseException; health.set(FULL_HEALTH); } /** * 当前总调用次数 */ private final AtomicInteger curTotalCount = new AtomicInteger(0); /** * 当前的超时次数 */ private final AtomicInteger curTimeoutCount = new AtomicInteger(0); /** * 当前的错误统计总数 */ private final AtomicInteger curExceptionCount = new AtomicInteger(0); /** * 当前拒绝次数 */ private final AtomicInteger curRefusedCount = new AtomicInteger(0); /** * 当前健康值 */ private final AtomicInteger health = new AtomicInteger(0); /** * 结束沉睡时间 */ private final AtomicLong endSleepTime = new AtomicLong(); /** * 0代表正常 * 1代表生病 * 2代表死亡 */ private final AtomicInteger status = new AtomicInteger(); /** * 满分健康值 */ private final static int FULL_HEALTH = 100; /** * 及格健康值 */ private final static int WEAK_HEALTH = 60; /** * 死亡健康值 */ private final static int DEAD_HEALTH = 0; /** * 超时损耗值 */ private final static int OVERTIME_LOSS = 5; /** * 异常损耗值 */ private final static int EXCEPTION_LOSS = 5; /** * 成功奖赏值 */ private final static int SUCCESS_AWARD = 1; /** * 生病沉睡时间 */ private final static int WEAK_SLEEP_TIME = 5 * 1000; /** * 死亡沉睡时间 */ private final static int DEAD_SLEEP_TIME = 60 * 1000; /** * 死亡状态 */ private final static int DEAD_STATUS = 2; /** * 生病状态 */ private final static int WAEK_STATUS = 1; /** * 正常状态 */ private final static int HEALTH_STATUS = 0; /** * 重新计数 */ private final static int RESET_CNT = 100000; private int addAndGetHealth(int val) { int h = health.addAndGet(val); if (h < DEAD_HEALTH) { health.set(DEAD_HEALTH); return DEAD_HEALTH; } else if (h > FULL_HEALTH) { health.set(FULL_HEALTH); return FULL_HEALTH; } return h; } private void reset() { log.info("reset name:" + name + ",curTotalCount:" + curTotalCount.get() + ",curTimeoutCount:" + curTimeoutCount.get() + ",curExceptionCount:" + curExceptionCount.get() + ",curRefusedCount:" + curRefusedCount + ",health:" + health); curTotalCount.set(0); curTimeoutCount.set(0); curRefusedCount.set(0); curExceptionCount.set(0); health.set(FULL_HEALTH); status.set(HEALTH_STATUS); endSleepTime.set(0); } public void printInfo() { log.info("info name:" + name + ",curTotalCount:" + curTotalCount.get() + ",curTimeoutCount:" + curTimeoutCount.get() + ",curRefusedCount:" + curRefusedCount + ",health:" + health + ",status:" + status + ":currTime" + System.currentTimeMillis() + ",endSleepTime:" + endSleepTime); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); //错误标记,标记是否发生异常,用户回血时判断使用 boolean exceptionMark = false; curTotalCount.incrementAndGet(); if(curTotalCount.get()>RESET_CNT) { reset(); } //是否在沉睡 long currEndSleepTime = endSleepTime.get(); if (currEndSleepTime != 0) { //没有达到唤醒时间,直接拒绝服务,并且计数返回null if (startTime < currEndSleepTime) { log.warn("refuse name :" + name); curRefusedCount.addAndGet(1); if(isThrowRefuseException) { throw new RefuseException("refuse name :" + name); } return null; } else { //清除沉睡时间 endSleepTime.set(0); log.info("end sleep,name:" + name); //死亡的满血复活 if (status.get() == DEAD_STATUS) { status.set(HEALTH_STATUS); health.set(FULL_HEALTH); log.info("full alive,name:" + name); } } } try { Object result = method.invoke(this.proxy, args); return result; } catch (Exception e) { //如果报错,扣血,并且做错误标记 log.warn("exception,name:" + name); curExceptionCount.incrementAndGet(); addAndGetHealth(-EXCEPTION_LOSS); exceptionMark = true; throw e; } finally { long endTime = System.currentTimeMillis(); /** * 根据具体的超时时间和设置的超时时间做比较.得到超时倍率 * 超时倍率越大.处罚越严重. * 扣分为 log2N*超时扣分(N=超时倍率) 扣分比率最大3倍 **/ long ot=endTime-startTime; long multiple=ot>overtime?ot/overtime:0; if (multiple>0) { Double ratio=multiple>1?Math.log(multiple)/Math.log(2):multiple; ratio=ratio>6?6:ratio; log.warn("overtime,name:" + name + ",pay time" + (endTime - startTime)); curTimeoutCount.incrementAndGet(); addAndGetHealth(-OVERTIME_LOSS*ratio.intValue()); } else { if (!exceptionMark) addAndGetHealth(SUCCESS_AWARD); } //如果生病严重,则沉睡 int currHealth = health.get(); if (currHealth <= DEAD_HEALTH && status.get() != DEAD_STATUS) { endSleepTime.set(endTime + DEAD_SLEEP_TIME); status.set(DEAD_STATUS); log.error("dead name:" + name + ",currHealth:" + currHealth + ",curTimeoutCount:" + curTimeoutCount.get() + ",curExceptionCount:"+curExceptionCount.get()+ ",refuseCount:"+curRefusedCount.get()+ ",curTotalCount" + curTotalCount.get()); } //正常状态的需要设置为生病状态并沉睡 else if (currHealth <= WEAK_HEALTH &&(status.get() == HEALTH_STATUS)) { endSleepTime.set(endTime + WEAK_SLEEP_TIME); status.set(WAEK_STATUS); log.error("weak name:" + name + ",currHealth:" + currHealth + ",curTimeoutCount:" + curTimeoutCount.get() + ",curExceptionCount:"+curExceptionCount.get()+ ",refuseCount:"+curRefusedCount.get()+ ",curTotalCount:" + curTotalCount.get()); } //如果生命回到健康,才设置为正常状态 else if (currHealth >=FULL_HEALTH && status.get() != HEALTH_STATUS) { log.warn("live name:" + name + ",currHealth:" + currHealth + ",curTimeoutCount:" + curTimeoutCount.get() + ",curExceptionCount:"+curExceptionCount.get()+ ",refuseCount:"+curRefusedCount.get()+ ",curTotalCount:" + curTotalCount.get()); status.set(HEALTH_STATUS); } } } }