SpringCloud之Hystrix Turbine的簡介與使用
Spring Cloud Hystrix Turbine
Hystrix Dashboard 前面已經(jīng)知道了,它的主要功能是可以對某一項微服務進行監(jiān)控,但真實情況下,不可能只對一個微服務進行監(jiān)控,我們有很多微服務,所以我們需要對很多微服務進行監(jiān)控,這個時候就需要使用到turbine [ˈtɜːbaɪn] 來完成;
單個hystrix服務的監(jiān)控(如下圖):
多個hystrix服務的監(jiān)控(如下圖):
具體步驟:
首先準備一個turbine模塊
1、創(chuàng)建一個
34-sprinGCloud-service-turbine項目,該項目依然是一個Springboot項目;
2、添加依賴:
- <!-- spring-cloud-starter-netflix-turbine -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
- </dependency>
3、配置文件:
- server.port=3722
#不向注冊中心注冊自己
- eureka.client.register-with-eureka=false
#eureka注冊中心的連接地址
- eureka.client.service-url.defaultZone=http://192.168.10.128:8761/eureka,http://192.168.10.128:8762/eureka,http://192.168.10.128:8763/eureka
#配置turbine
- turbine.app-config=34-SPRINGCLOUD-SERVICE-PORTAL,34-SPRINGCLOUD-SERVICE-PORTAL-2
#需要有這個,沒有的話聚合不了多個項目
- turbine.cluster-name-expression="default"
4、在main方法的入口類上添加注解:
- @EnableTurbine //開啟turbine
- @SpringBootApplication
- public class TurbineApplication {
- public static void main(String[] args) {
- SpringApplication.run(TurbineApplication.class, args);
- }
- }
接下來為了能對多個使用了hystrix的項目進行監(jiān)控,我們再準備一份項目,并且里面使用了hystrix:
34-sprinGCloud-service-portal-2(可以拷貝一份34-sprinGCloud-service-portal項目來制作)
注意點就是要先訪問一下各個使用了hystrix的優(yōu)熔斷功能的接口,然后再測試turbine,否則你不訪問的話,測試是不會有數(shù)據(jù)的;
- http://localhost:8080/cloud/goodsFeignHystrix
- http://localhost:8081/cloud/goodsLimit
- http://localhost:8080/actuator/hystrix.stream
- http://localhost:8081/actuator/hystrix.stream
- http://localhost:3722/turbine.stream
Spring Cloud Zuul
通過前面內容的學習,我們已經(jīng)可以基本搭建出一套簡略版的微服務架構了,我們有注冊中心 Eureka,可以將服務注冊到該注冊中心中,我們有 Ribbon 或Feign 可以實現(xiàn)對服務負載均衡地調用,我們有 Hystrix 可以實現(xiàn)服務的熔斷;
我們來看一下下面的微服務架構圖:
在上面的架構圖中,我們的服務包括:內部服務 Service A 和內部服務 ServiceB,這兩個服務都是集群部署,每個服務部署了 3 個實例,他們都會通過 EurekaServer 注冊中心注冊與訂閱服務,而 Open Service 是一個對外的服務,也是集群部署,外部調用方通過負載均衡設備調用 Open Service 服務,比如負載均衡使用 Nginx、LVS、HAProxy,這樣的實現(xiàn)是否合理,或者是否有更好的實現(xiàn)方式呢?接下來我們主要圍繞該問題展開討論。
1、如果我們的微服務中有很多個獨立服務都要對外提供服務,那么我們要如何去管理這些接口?特別是當項目非常龐大的情況下要如何管理?
2、在微服務中,一個獨立的系統(tǒng)被拆分成了很多個獨立的服務,為了確保安全,權限管理也是一個不可回避的問題,如果在每一個服務上都添加上相同的權限驗證代碼來確保系統(tǒng)不被非法訪問,那么工作量也就太大了,而且維護也非常不方便。
為了解決上述問題,微服務架構中提出了API網(wǎng)關的概念,它就像一個安檢站一樣,所有外部的請求都需要經(jīng)過它的調度與過濾,然后 API 網(wǎng)關來實現(xiàn)請求路由、負載均衡、權限驗證等功能;
那么 Spring Cloud 這個一站式的微服務開發(fā)框架基于 Netflix Zuul 實現(xiàn)了Spring Cloud Zuul,采用 Spring Cloud Zuul 即可實現(xiàn)一套 API 網(wǎng)關服務;
Zuul
Zuul包含了對請求的路由和過濾兩個最主要的功能:
其中路由功能負責將外部請求轉發(fā)到具體的微服務實例上,是實現(xiàn)外部訪問統(tǒng)一入口的基礎,過濾功能則負責對請求的處理過程進行干預,是實現(xiàn)請求校驗、服務聚合等功能的基礎;
Zuul和Eureka進行整合,將Zuul自身注冊為Eureka服務治理下的應用,同時從Eureka中獲得其他微服務的信息,也即以后的訪問微服務都是通過Zuul跳轉后獲得。
路由功能:
項目加入依賴:
- <!--spring-cloud-starter-netflix-eureka-client-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <!-- spring-cloud-starter-netflix-zuul -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
- </dependency>
由于Zuul最終會注冊進eureka,所以我們此處也依賴了eureka;
配置文件:
- server.port=80
#是eureka注冊中心首頁的Application這一欄
- spring.application.name=34-sprinGCloud-service-zuul
#每間隔2s,向服務端發(fā)送一次心跳,證明自己依然"存活"
- eureka.instance.lease-renewal-interval-in-seconds=2
#告訴服務端,如果我10s之內沒有給你發(fā)心跳,就代表我故障了,將我踢出掉
- eureka.instance.lease-expiration-duration-in-seconds=10
#告訴服務端,服務實例以IP作為鏈接,而不是取機器名
- eureka.instance.prefer-ip-address=true
#告訴服務端,服務實例的id,id必須要唯一,是eureka注冊中心首頁的Status這一欄
- eureka.instance.instance-id=34-sprinGCloud-service-zuul
#eureka注冊中心的連接地址
- eureka.client.service-url.defaultZone=http://192.168.10.128:8761/eureka,http://192.168.10.128:8762/eureka,http://192.168.10.128:8763/eureka
啟動類上:
- @EnableZuulProxy
- @SpringBootApplication
- public class ZuulApplication {
- public static void main(String[] args) {
- SpringApplication.run(ZuulApplication.class, args);
- }
- }
這樣簡單的zuul就搭建好了, 啟動項目我們即可通過zuul然后加上對應的微服務名字訪問微服務,比如:
- http://localhost/34-sprinGCloud-service-portal/cloud/goodsFeign
- http://localhost:80/
這個是zuul本身的
- 34-sprinGCloud-service-portal
這個是要調用的項目名稱
- /cloud/goodsFeign
這個是被調用的contrller上的接口路徑;
在實際開發(fā)當中我們肯定不會通過微服務名去調用,比如我要調用消費者可能只要一個/cloud/goodsFeign就好了,而不是
/34-sprinGCloud-service-portal/cloud/goodsFeign
加入以下配置即可:
#配置路由規(guī)則
- zuul.routes.portal.service-id=34-sprinGCloud-service-portal
- zuul.routes.portal.path=/portal/**
然后:
- http://localhost/portal/cloud/goodsFeignHystrix
- / **代表是所有(多個)層級 /cloud/goodsFeignHystrix
- / * 是代表一層;
- 如果是/ * 的話 /api/goods 就不會被路由;
此時我們能通過自定義的規(guī)則進行訪問,但是我們現(xiàn)在依然能用之前的微服務名調用,這是不合理的,第一是有多重地址了, 第二一般微服務名這種最好不要暴露在外,所以我們一般會禁用微服務名方式調用。
加入配置:
- zuul.ignored-services=34-sprinGCloud-service-portal
這里能發(fā)現(xiàn)我們不能通過微服務名來調用了, 不過這個配置如果一個一個通過微服務名來配置難免有點復雜,所以一般這樣配置來禁用所有:
- zuul.ignored-services=*
可能有時候我們的接口調用需要一定的規(guī)范,比如調用微服務的API URL前綴需要加上/api 對于這種情況,zuul也考慮到了并給出了解決方案:
- zuul.prefix=/api
比如:
http://localhost/api/portal/cloud/goodsFeignHystrix
通配符
過濾器:
限流、權限驗證、記錄日志
過濾器 (filter) 是zuul的核心組件,zuul大部分功能都是通過過濾器來實現(xiàn)的。 zuul中定義了4種標準過濾器類型,這些過濾器類型對應于請求的典型生命周期。
- PRE:這種過濾器在請求被路由之前調用。可利用這種過濾器實現(xiàn)身份驗證、在 集群中選擇請求的微服務、記錄調試信息等。
- ROUTING:這種過濾器將請求路由到微服務。這種過濾器用于構建發(fā)送給微服 務的請求,并使用 Apache HttpClient或 Netfilx Ribbon請求微服務
- POST:這種過濾器在路由到微服務以后執(zhí)行。這種過濾器可用來為響應添加標準 的 HTTP Header、收集統(tǒng)計信息和指標、將響應從微服務發(fā)送給客戶端等。
- ERROR:在其他階段發(fā)生錯誤時執(zhí)行該過濾器。
如果要編寫一個過濾器,則需繼承ZuulFilter類實現(xiàn)其中的方法:
- @Component
- public class LogFilter extends ZuulFilter {
- @Override
- public String filterType() {
- return FilterConstants.ROUTE_TYPE;
- }
- @Override
- public int filterOrder() {
- return FilterConstants.PRE_DECORATION_FILTER_ORDER;
- }
- @Override
- public boolean shouldFilter() {
- return true;
- }
- @Override
- public Object run() throws ZuulException {
- RequestContext currentContext = RequestContext.getCurrentContext();
- HttpServletRequest request = currentContext.getRequest();
- String remoteAddr = request.getServerName();
- System.out.println("訪問地址:"+request.getRequestURI());
- return null;
- }
- }
由代碼可知,自定義的 zuul Filter需實現(xiàn)以下幾個方法。
filterType: 返回過濾器的類型。
有 pre、 route、 post、 error等幾種取值,分別對應上文的幾種過濾器。
詳細可以參考
com.netflix.zuul.ZuulFilter.filterType()中的注釋;
filter0rder:返回一個 int值來指定過濾器的執(zhí)行順序,不同的過濾器允許返回相同的數(shù)字。
shouldFilter:返回一個 boolean值來判斷該過濾器是否要執(zhí)行, true表示執(zhí)行, false表示不執(zhí)行。
run:過濾器的具體邏輯;
zuul過濾器的禁用
Spring Cloud默認為Zuul編寫并啟用了一些過濾器,例如DebugFilter、FormBodyWrapperFilter等,這些過濾器都存放在spring-cloud-netflix-zuul這個jar包里,一些場景下,想要禁用掉部分過濾器,該怎么辦呢? 只需在application.properties里設置zuul...disable=true 例如,要禁用上面我們寫的過濾器,這樣配置就行了:
- zuul.LogFilter.route.disable=true
Zuul 的異常處理
Spring Cloud Zuul 對異常的處理是非常方便的,但是由于 Spring Cloud 處于迅速發(fā)展中,各個版本之間有所差異,本案例是以 Finchley.RELEASE 版本為例, 來說明 Spring Cloud Zuul 中的異常處理問題。
首先我們來看一張官方給出的 Zuul 請求的生命周期圖:
1.正常情況下所有的請求都是按照 pre、route、post 的順序來執(zhí)行,然后由 post 返回 response
2.在 pre 階段,如果有自定義的過濾器則執(zhí)行自定義的過濾器
3.pre、routing、post 的任意一個階段如果拋異常了,則執(zhí)行 error 過濾器
我們可以統(tǒng)一處理異常:
怎么實現(xiàn),步驟:
1、禁用 zuul 默認的異常處理 SendErrorFilter 過濾器,然后自定義我們自己的Errorfilter 過濾器
- zuul.SendErrorFilter.error.disable=true
- @Component
- public class ErrorFilter extends ZuulFilter {
- @Override
- public String filterType() {
- return "error";
- }
- @Override
- public int filterOrder() {
- return 1;
- }
- @Override
- public boolean shouldFilter() {
- return true;
- }
- @Override
- public Object run() throws ZuulException {
- try {
- RequestContext context = RequestContext.getCurrentContext();
- ZuulException exception = (ZuulException)context.getThrowable();
- System.out.println("進入系統(tǒng)異常攔截" + exception.getMessage());
- HttpServletResponse response = context.getResponse();
- response.setContentType("application/json; charset=utf8");
- response.setStatus(exception.nStatusCode);
- PrintWriter writer = null;
- try {
- writer = response.getWriter();
- writer.print("{code:"+ exception.nStatusCode +",message:\""+
- exception.getMessage() +"\"}");
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if(writer!=null){
- writer.close();
- }
- }
- } catch (Exception e) {
- ReflectionUtils.rethrowRuntimeException(e);
- }
- return null;
- }
- }
Zuul的熔斷降級
zuul是一個代理服務,但如果被代理的服務突然斷了,這個時候zuul上面會有出錯信息,例如,停止了被調用的微服務;
一般服務方自己會進行服務的熔斷降級,但對于zuul本身,也應該進行zuul的降級處理;
我們需要有一個zuul的降級,實現(xiàn)如下:
- @Component
- public class ProviderFallback implements FallbackProvider {
- @Override
- public String getRoute() {
- return "*";
- }
- @Override
- public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
- return new ClientHttpResponse() {
- @Override
- public HttpHeaders getHeaders() {
- HttpHeaders headers = new HttpHeaders();
- headers.set("Content-Type", "text/html; charset=UTF-8");
- return headers;
- }
- @Override
- public InputStream getBody() throws IOException {
- // 響應體
- return new ByteArrayInputStream("服務正在維護,請稍后再試.".getBytes());
- }
- @Override
- public HttpStatus getStatusCode() throws IOException {
- return HttpStatus.BAD_REQUEST;
- }
- @Override
- public int getRawStatusCode() throws IOException {
- return HttpStatus.BAD_REQUEST.value();
- }
- @Override
- public String getStatusText() throws IOException {
- return HttpStatus.BAD_REQUEST.getReasonPhrase();
- }
- @Override
- public void close() {
- }
- };
- }
- }






























