基於Maven的Hystrix 熔斷機制

GitHub關於hystrix文檔:https://github.com/Netflix/Hystrix/wiki


1. 首先介紹一下hystrix是什麼?


簡單來說,hystrix就是用來容錯(潛在的錯誤、或者致命的錯誤)的。


2. 那麼,不容錯會怎麼樣?


曬下官網的圖:


正常情況下訪問:每個節點都很正常


如果I掛掉了,執行速度很慢或者執行出現異常,類似於RunTimeException

隨着請求不斷的往I上面發送,造成服務不可用


wiki原文:

3. 看來容錯是很有必要的,接下來看點乾貨


這裏有官網的一個列子:https://github.com/Netflix/Hystrix/tree/master/hystrix-examples,可以下下來看看,很

簡單,秒懂!我只是把官網的例子稍作修改。

3.1. 首先在pom.xml裏面加入Hystrix相關代碼


<hystrix.version>1.5.12</hystrix.version>

<dependency>
	<groupId>com.netflix.hystrix</groupId>
	<artifactId>hystrix-core</artifactId>
	<version>${hystrix.version}</version>
</dependency>


3.2. 編寫熔斷代碼


我是基於AOP註解方式實現熔斷方法的


熔斷命令的註解類

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HystrixCommand {

    /**
     * 當錯誤次數達到閾值或者執行超時,直接執行下面代碼
     * 如果不填fallbackMethod則執行默認熔斷方法
     * 如果填寫fallbackMethod則熔斷方法必須在配置註解的同一個類裏面,否則拋出MethodNotFoundException
     * [熔斷方法傳參]
     * 1. 不傳參則直接執行fallbackMethod熔斷方法
     * 2. 傳參則必須和配置註解方法傳參類型保持一致, 否則會執行錯誤
     * 參考:HttpHystrixAspect.java
     * @return
     */
    public String fallbackMethod() default "";
    
}

AOP環繞通知

import java.lang.reflect.Method;

import javax.el.MethodNotFoundException;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixRequestLog;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

@Component
@Aspect
public class HttpHystrixAspect {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 對HTTP請求進行AOP操作
     */
    @Around("execution (public String com.xxx.ga.service.impl.*.*(..)) &&  @annotation(hystrixCommand)")
    public Object aroundHttpRequest(ProceedingJoinPoint pjp, com.iboxpay.ga.annotation.HystrixCommand hystrixCommand) throws Exception, Throwable{
        Object result = null;
        // 執行類名
        String targetName = pjp.getTarget().getClass().getSimpleName();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 執行方法名
        String methodName = signature.getMethod().getName();
        // 初始化熔斷器上下文
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            result = new HttpHystrix(pjp, targetName, methodName, hystrixCommand).execute();
        } finally {
            logger.info("Request => " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
            context.shutdown();
        }
        return result;
    }

    public class HttpHystrix extends HystrixCommand<Object> {

        private final ProceedingJoinPoint pjp;
        
        // 類名
        private final String className; 
        
        // 方法名
        private final String methodName;
        
        // 註解
        private final com.iboxpay.ga.annotation.HystrixCommand hystrixCommand;

        /**
         * @param pjp        
         * @param serviceId  類名+方法名
         */
        protected HttpHystrix(ProceedingJoinPoint pjp, String className, String methodName, com.iboxpay.ga.annotation.HystrixCommand hystrixCommand) {
            // Hystrix uses the command group key to group together commands such as for reporting,
            // alerting, dashboards, or team/library ownership.
            // 同一個groupKey共用同一個線程池
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(className))
                    .andCommandKey(HystrixCommandKey.Factory.asKey(methodName))
                    // 超時時間
                    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)));
            this.pjp = pjp;
            this.className = className;
            this.methodName = methodName;
            this.hystrixCommand = hystrixCommand;
        }
        
        @Override
        protected Object run() throws Exception {
            try {
                return pjp.proceed();
            } catch (Throwable e) {
                throw new Exception(e);
            }
        }

        /**
         * 熔斷措施
         * 當錯誤次數達到閾值或者執行超時,直接執行下面代碼
         */
        @Override
        protected Object getFallback() {
            logger.info("[{}] 錯誤次數達到閾值或者執行超時, 進行熔斷措施", className + "_" + methodName);
            // 熔斷方法名稱
            String fallbackMethod = hystrixCommand.fallbackMethod();
            // 未聲明瞭熔斷機制,默認熔斷方法
            if(StringUtils.isEmpty(fallbackMethod)){
                return "返回失敗";
            }
            Method methods[] = pjp.getTarget().getClass().getMethods();
            Method method = null;
            for(Method m : methods){
                if(m.getName().equals(fallbackMethod)){
                    method = m;
                    break;
                }
            }
            // 未在類中找到申明的fallbackMethod方法
            if(method == null){
                throw new MethodNotFoundException();
            }
            // 熔斷方法傳入參數
            Class<?> clazzs[] = method.getParameterTypes();
            // 傳入參數爲空,直接執行方法
            if(clazzs.length == 0){
                try {
                    return method.invoke(pjp.getTarget());
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage());
                }
            }
            // 傳入參數不爲空,則傳入AOP攔截方法參數
            try {
                return method.invoke(pjp.getTarget(), pjp.getArgs());
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }

    }
}

service方法配置HystrixCommand註解

@Override
    @HystrixCommand(fallbackMethod = "hiError")
    public String testHystrix(int outtimeRate, int runtimeRate) {
        /* 模擬數據操作耗時 */
        try {
            Thread.sleep((int) (Math.random() * 10) + 2);
        } catch (InterruptedException e) {
            // do nothing
        }
        /* 執行失敗比率 */
        if (Math.random() > (double) runtimeRate / 100) {
            throw new RuntimeException("運行異常");
        }
        /* 執行超時比率 */
        if (Math.random() > (double) outtimeRate / 100) {
            try {
                Thread.sleep(Integer.parseInt(timeOut) + 5);
            } catch (Exception e) {
                // do nothing
            }
        }
        return "{'status': 'SUCCESS'}";
    }

    public String hiError(int n, int m) {
        logger.info("熔斷措施:hi,sorry,error!");
        return "hi,sorry,error!";
    }

這裏省略掉了controller調用service的方法...


至此,Hystrix代碼全部結束,testHystrix方法模擬生產環境運行超時、異常情況。


4. 接下來講一下如何安裝hystrix dashboard監控


dashboard下載地址:http://search.maven.org/remotecontent?filepath=com/netflix/hystrix/hystrix-dashboard/1.5.4/hystrix-dashboard-1.5.4.war

下載後直接丟到tomcat或者jetty裏面運行就可以了。

運行後訪問:http://localhost:7979/hystrix-dashboard/(我的端口號是7979)看到下面界面就說明安裝成功了!

接下來pom.xml文件增加

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-metrics-event-stream</artifactId>
    <version>${hystrix.version}</version>
</dependency>


因爲我是基於springboot實現的的,所以直接添加Configuration即可,傳統的web項目在web.xml增加對應的servlet和url-mapping就可以了

@Configuration
public class HystrixConfig {

    @Bean
    public HystrixMetricsStreamServlet hystrixMetricsStreamServlet() {
        return new HystrixMetricsStreamServlet();
    }

    @Bean
    public ServletRegistrationBean registration(HystrixMetricsStreamServlet servlet) {
        ServletRegistrationBean registrationBean = new ServletRegistrationBean();
        registrationBean.setServlet(servlet);
        registrationBean.setEnabled(true); //是否啓用該registrationBean
        registrationBean.addUrlMappings("/hystrix.stream");
        return registrationBean;
    }

}

在Hystrix Dashboard管理頁面輸入路徑


就可以看到監控圖表了





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章