多位微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其他微服務,這就是所謂的”扇處”。如果扇處的鏈路上某個微服務的調用響應時間過長或者不可用,對微服務A的調用就會佔用越來越多的系統資源,進而引起系統崩潰—所謂的”雪崩效應”。
對於高流量的應用來說,單一的後端依賴可能會導致所有服務器上面的所有資源在幾秒鐘內飽和。比失敗更糟糕的是這些應用程序還可能導致服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導致整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關係的失敗,不能取消整個應用程序或系統。
“Hystrix”是一個用於處理分佈式系統的延遲和容錯的開源庫,在分佈式系統裏,許多依賴不可避免的會調用失敗,比如超時、異常等。Hystrix能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分佈式系統的彈性。
“斷路器”本身是一種開關裝置,當某個服務單元發生故障後,通過斷路器的故障監控(類似熔斷保險絲),向調用方返回一個符合預期的、可處理的備選響應”FallBack”,而不是長時間的等待或者拋出調用方無法解決的異常。這樣就保證了服務調用方的線程不會被長時間、不必要的佔用,從而避免了故障在分佈式系統中的蔓延,乃至雪崩。
Hystrix官網文檔:https://github.com/Netflix/Hystrix/wiki
【1】服務熔斷
熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。
當扇出鏈路的某個微服務不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的調用,快速返回“錯誤”的響應信息。當檢測到該節點微服務調用響應正常後恢復鏈路。在SpringCloud框架裏熔斷機制通過Hystrix實現。Hystrix會監控微服務調用的狀況,當失敗的調用到一定閾值就會啓動熔斷機制(默認是5秒內20次調用失敗就會啓動熔斷機制)。
熔斷機制的註解:@HystrixCommand。
① 參考/microservicecloud-provider-dept-8001創建/microservicecloud-provider-dept-hystrix-8001
基礎代碼延用上篇博文Feign負載均衡與Ribbon代碼
② 添加Hystrix依賴
<dependencies>
<!-- 引入自己定義的api通用包,可以使用Dept部門Entity -->
<dependency>
<groupId>com.web.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- actuator監控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- 將微服務provider側註冊進eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 修改後立即生效,熱部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
③ yml文件配置
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路徑
type-aliases-package: com.web.springcloud.entities # 所有Entity別名類所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件
spring:
application:
name: microservicecloud-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 當前數據源操作類型
driver-class-name: com.mysql.jdbc.Driver # mysql驅動包
# driver-class-name: org.gjt.mm.mysql.Driver # mysql驅動包
url: jdbc:mysql://localhost:3306/clouddb01 # 數據庫名稱
username: root
password: 123456
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: true
testOnReturn: false
poolPreparedStatements: true
#配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# dbcp2:
# min-idle: 5 # 數據庫連接池的最小維持連接數
# initial-size: 5 # 初始化連接數
# max-total: 5 # 最大連接數
# max-wait-millis: 200 # 等待連接獲取的最大超時時間
eureka:
client: #客戶端註冊進eureka服務列表內
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
# defaultZone: http://localhost:7001/eureka/
instance:
instance-id: microservicecloud-dept8001-hystrix # 自定義服務實例Id
prefer-ip-address: true #訪問路徑可以顯示IP地址
# http://192.168.2.100:8001/info優化顯示
info:
app.name: web-microservicecloud
company.name: www.web.com
build.artifactId: $project.artifactId$
build.version: $project.version$
項目結構如下圖:
④ 修改DeptController
@RestController
public class DeptController
{
@Autowired
private DeptService service = null;
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
//一旦調用服務方法失敗並拋出了錯誤信息後,會自動調用@HystrixCommand標註好的fallbackMethod中的指定方法
@HystrixCommand(fallbackMethod = "processHystrix_Get")
public Dept get(@PathVariable("id") Long id)
{
Dept dept = this.service.get(id);
if (null == dept) {
throw new RuntimeException("該ID:" + id + "沒有沒有對應的信息");
}
return dept;
}
public Dept processHystrix_Get(@PathVariable("id") Long id)
{
Dept dept = new Dept();
dept.setDeptno(id);
dept.setDname("該ID:" + id + "沒有沒有對應的信息,null--@HystrixCommand");
dept.setDb_source("no this database in MySQL");
return dept;
}
}
⑤ 主程序類添加熔斷支持
@SpringBootApplication
@EnableEurekaClient //本服務啓動後會自動註冊進eureka服務中
@EnableDiscoveryClient//服務發現
@EnableCircuitBreaker //對Hystrix熔斷機制支持
public class DeptProvider8001_Hystrix_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptProvider8001_Hystrix_App.class, args);
}
}
⑥ 測試
首先啓動三個服務中心7001 7002 7003然後啓動/microservicecloud-provider-dept-hystrix-8001,接着啓動/microservicecloud-consumer-dept-80,瀏覽器使用消費者請求一個數據庫中不存在的記錄。
【2】服務降級
一句話簡要描述:整體資源快不夠了,忍痛將某些服務先關掉,待渡過難關,再開啓回來。
服務降級處理是在客戶端(消費者)完成的與服務端沒有關係。
參考【1】服務熔斷的處理方式,會發現這種方式是很可怕的。第一如果有多個方法,對應的FallBack方法也會隨之膨脹;第二異常處理與業務邏輯高耦合,完全不符合Spring AOP面向切面的思想。
上面說了,服務降級是在客戶端(消費者)處理的,又需要與業務解耦,使用面向切面的思想。再次參考@FeignClient註解源碼:
/**
* Annotation for interfaces declaring that a REST client with that interface should be
* created (e.g. for autowiring into another component). If ribbon is available it will be
* used to load balance the backend requests, and the load balancer can be configured
* using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
*
* @author Spencer Gibb
* @author Venil Noronha
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead
*/
@Deprecated
String serviceId() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";
/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* Whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;
/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] configuration() default {};
/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
*/
Class<?> fallback() default void.class;
/**注意這裏註釋!!!!*/
/**
* Define a fallback factory for the specified Feign client interface.
* //爲指定的 Feign客戶端接口定義一個fallback factory。
* The fallback factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}.
* // 該工程必須產生一個標註了@FeignClient註解接口的實現類的實例。
* The fallback factory must be a valid spring bean.
*// 該工廠必須是一個可用的Spring bean--要註冊進容器
* @see feign.hystrix.FallbackFactory for details.
*/
Class<?> fallbackFactory() default void.class;
/**
* Path prefix to be used by all method-level mappings. Can be used with or without
* <code>@RibbonClient</code>.
*/
String path() default "";
/**
* Whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;
}
解決方案如下:
① 修改/microservicecloud-api工程,根據已有的DeptClientService新建一個實現了FallbackFactory接口的類DeptClientServiceFallbackFactory
DeptClientServiceFallbackFactory實例如下:
@Component // 不要忘記添加,不要忘記添加
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService>
{
@Override
public DeptClientService create(Throwable throwable)
{
return new DeptClientService() {
@Override
public Dept get(long id)
{
Dept dept = new Dept();
dept.setDeptno(id);
dept.setDname("該ID:" + id + "沒有沒有對應的信息,Consumer客戶端提供的降級信息,此刻服務Provider已經關閉");
dept.setDb_source("no this database in MySQL");
return dept;
}
@Override
public List<Dept> list()
{
return null;
}
@Override
public boolean add(Dept dept)
{
return false;
}
};
}
}
② 修改DeptClientService上的@FeignClient
@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory=DeptClientServiceFallbackFactory.class)
public interface DeptClientService
{
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
public Dept get(@PathVariable("id") long id);
@RequestMapping(value = "/dept/list", method = RequestMethod.GET)
public List<Dept> list();
@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(Dept dept);
}
③ 修改/microservicecloud-consumer-dept-feign工程的yml
server:
port: 80
spring:
application:
name: microservicecloud-consumer
feign:
hystrix:
enabled: true
eureka:
instance:
prefer-ip-address: true # 註冊服務的時候使用服務的ip地址
client:
register-with-eureka: false # 不向服務註冊中心註冊自己
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
# defaultZone: http://localhost:7001/eureka/
④ 測試
首先啓動三個服務中心7001 7002 7003,然後啓動服務提供者/microservicecloud-provider-dept-8001,最後啓動服務消費者/microservicecloud-consumer-dept-feign。
正常訪問:http://localhost/consumer/dept/get/1
將服務提供者關掉,然後再測試消費者:
可以看到,此時我的服務提供者provider已經down了,但是我們做了服務降級處理,讓客戶端在服務不可用的時候也會獲得信息提示而不會掛起耗死服務器。
【3】服務監控Hystrix Dashboard
除了隔離依賴服務的調用以外,Hystrix還提供了準實時的調用監控(Hystrix Dashboard),Hystrix會持續的記錄所有通過Hystrix發起的請求的執行信息,並以統計報表和圖形的形式展示給用戶。其中包括每秒執行多少請求,多少成功,多少失敗等。
Netflix通過hystrix-metrics-event-stream項目實現了對以上指標的監控。SpringCloud也提供了Hystrix Dashboard的整合,對監控內容轉化成可視化界面。
① 新建module microservicecloud-consumer-hystrix-dashboard
pom文件如下:
<dependencies>
<!-- 自己定義的api -->
<dependency>
<groupId>com.web.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 修改後立即生效,熱部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- Ribbon相關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- feign相關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!-- hystrix和 hystrix-dashboard相關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
② yml文件如下
server:
port: 9001
③ 主啓動類如下:
@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumer_DashBoard_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer_DashBoard_App.class, args);
}
}
④ 服務提供者8001 8002 8003添加監控依賴
<!-- actuator監控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
⑤ 啓動/microservicecloud-consumer-hystrix-dashboard
別亂猜,這貨不是熊,這是豪豬!!!
⑥ 啓動三個服務中心和/microservicecloud-provider-dept-hystrix-8001
訪問http://localhost:8001/hystrix.stream:
可以看到該頁面在不斷獲取數據!!!
⑦ 使用Hystrix Dashboard監控
頁面如下填寫,分別是監控URL,延遲時間和自定義名字。
⑧ 如何查看?
一圈–實心圓:共有兩種含義。它通過顏色的變化代表了實例的健康程度,它的健康度從綠色<黃色<橙色<紅色遞減。
該實心圓除了顏色的變化之外,它的大小也會根據實例的請求流量發生變化。流量越大該實心圓也就越大。所以通過該實心圓的展示,就可以在大量的實例中快速的發現故障實例和高壓力實例。
曲線:用來記錄2分鐘內流量的相對變化,可以通過它來觀察到流量的上升和下降趨勢。
多次刷新http://localhost:8001/dept/get/1請求,然後查看Hystrix Dashboard如下:
連續發出六次請求http://localhost:8001/dept/get/1,如下圖:
參數詳細說明如下: