分佈式存在的問題
服務雪崩
多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其它的微服務,這就是所謂的“扇出”。如果扇出的鏈路上某個微服務的調用響應時間過長或者不可用,對微服務A的調用就會佔用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”.
對於高流量的應用來說,單一的後端依賴可能會導致所有服務器上的所有資源都在幾秒鐘內飽和。比失敗更糟糕的是,這些應用程序還可能導致服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導致整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關係的失敗,不能取消整個應用程序或系統。
Hystrix是什麼
Hystrix是一個用於處理分佈式系統的延遲和容錯的開源庫,在分佈式系統裏,許多依賴不可避免的會調用失敗,比如超時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分佈式系統的彈性。
“斷路器”本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控(類似熔斷保險絲),向調用方返回一個符合預期的、可處理的備選響應(FallBack),而不是長時間的等待或者拋出調用方無法處理的異常,這樣就保證了服務調用方的線程不會被長時間、不必要地佔用,從而避免了故障在分佈式系統中的蔓延,乃至雪崩。
服務熔斷
熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。
當扇出鏈路的某個微服務不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的調用,快速返回"錯誤"的響應信息。當檢測到該節點微服務調用響應正常後恢復調用鏈路。在SpringCloud框架裏熔斷機制通過Hystrix實現。Hystrix會監控微服務間調用的狀況,當失敗的調用到一定閾值,缺省是5秒內20次調用失敗就會啓動熔斷機制。熔斷機制的註解是@HystrixCommand。
作用:
服務降級,服務熔斷,服務限流,接近實時的監控
參看cloud-provider-8001,創建cloud-provider-hystrix-8001項目
pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-parent</artifactId>
<groupId>com.el.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.el.cloud</groupId>
<artifactId>cloud-provider-hystrix-8001</artifactId>
<name>cloud-provider-hystrix-8001</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- 引入自己定義的api通用包,可以使用Dept部門Entity -->
<dependency>
<groupId>com.el.cloud</groupId>
<artifactId>cloud-api</artifactId>
<version>${project.version}</version>
</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>
<!-- actuator監控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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>
</project>
yml文件:
server:
port: 8002
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路徑
type-aliases-package: com.el.consumer.cloud.entities # 所有Entity別名類所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件
spring:
application:
name: cloud-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 當前數據源操作類型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驅動包
url: jdbc:mysql://192.168.137.131:3306/cloudDB01 # 數據庫名稱
username: root
password: root
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/
instance:
instance-id: cloud-provider-hystrix-dept-8001
prefer-ip-address: true #訪問路徑可以顯示IP地址
info:
app.name: elitesland-cloud
company.name: www.baidu.com
build.artifactId: $project.artifactId$
build.version: $project.version$
修改DeptController:
package com.el.cloud.controller;
import com.el.cloud.service.DeptService;
import com.el.consumer.cloud.entities.Dept;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Auther: roman.zhang
* @Date: 2019/3/19 10:55
* @Version:V1.0
* @Description:DeptController
*/
//@Controller
@RestController
public class DeptController {
@Autowired
private DeptService service;
@RequestMapping(value="/dept/get/{id}",method=RequestMethod.GET)
@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){
return new Dept().setDeptno(id).setDname("該ID:"+id+"沒有沒有對應的信息,null--@HystrixCommand")
.setDb_source("no this database in MySQL");
}
}
修改主啓動類DeptProvider_hystrix_8001_App
package com.el.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* Hello world!
*
*/
@SpringBootApplication
@EnableEurekaClient //本服務啓動後會自動註冊進eureka服務中
@EnableCircuitBreaker//對hystrixR熔斷機制的支持
public class DeptProvider_hystrix_8001_App
{
public static void main( String[] args )
{
SpringApplication.run(DeptProvider_hystrix_8001_App.class,args);
}
}
測試:
1.3個eureka先啓動
2.啓動DeptProvider_hystrix_8001_App
2.啓動消費者
總結:
如果對應的ID:112,數據庫裏面沒有這個記錄,我們報錯後統一返回。
服務降級
整體資源快不夠了,忍痛將某些服務先關掉,待渡過難關,再開啓回來。服務降級處理是在客戶端實現完成的,與服務端沒有關係
步驟:
1.修改cloud-api項目:根據已經有的DeptClientService接口新建一個實現了FallbackFactory接口的類DeptClientServiceFallbackFactory
package com.el.consumer.cloud.service.impl;
import com.el.consumer.cloud.entities.Dept;
import com.el.consumer.cloud.service.DeptClientService;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Auther: roman.zhang
* @Date: 2019/3/20 9:48
* @Version:V1.0
* @Description:DeptClientServiceFallbackFactory
*/
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {
@Override
public DeptClientService create(Throwable throwable)
{
return new DeptClientService() {
@Override
public Dept get(long id)
{
return new Dept().setDeptno(id)
.setDname("該ID:"+id+"沒有沒有對應的信息,Consumer客戶端提供的降級信息,此刻服務Provider已經關閉")
.setDb_source("no this database in MySQL");
}
@Override
public List<Dept> list()
{
return null;
}
@Override
public boolean add(Dept dept)
{
return false;
}
};
}
}
修改cloud-api工程,DeptClientService接口在註解@FeignClient中添加fallbackFactory屬性值
package com.el.consumer.cloud.service;
import com.el.consumer.cloud.entities.Dept;
import com.el.consumer.cloud.service.impl.DeptClientServiceFallbackFactory;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* @Auther: roman.zhang
* @Date: 2019/3/19 16:43
* @Version:V1.0
* @Description:DeptClientService
*/
//@FeignClient(value = "cloud-dept")
@FeignClient(value = "cloud-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);
}
修改cloud-cpnsumer-feign的yml文件
server:
port: 80
feign:
hystrix:
enabled: true
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
測試:
1.3個eureka先啓動
2.啓動DeptProvider8001_App
3.啓動消費者
4.正常訪問
5.關閉服務提供者
6.
總結:
此時服務端provider已經down了,但是我們做了服務降級處理,讓客戶端在服務端不可用時也會獲得提示信息而不會掛起耗死服務器
服務監控hystrixDashboard
除了隔離依賴服務的調用以外,Hystrix還提供了準實時的調用監控(Hystrix Dashboard),Hystrix會持續地記錄所有通過Hystrix發起的請求的執行信息,並以統計報表和圖形的形式展示給用戶,包括每秒執行多少請求多少成功,多少失敗等。Netflix通過hystrix-metrics-event-stream項目實現了對以上指標的監控。Spring Cloud也提供了Hystrix Dashboard的整合,對監控內容轉化成可視化界面。
新建cloud-consumer-hystrixdashboard
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-parent</artifactId>
<groupId>com.el.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.el.cloud</groupId>
<artifactId>cloud-consumer-hystrix-dashboard</artifactId>
<dependencies>
<dependency>
<groupId>com.el.cloud</groupId>
<artifactId>cloud-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>
</project>
yml文件
server:
port: 9001
創建啓動類:
package com.el.dashboard;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* @Auther: roman.zhang
* @Date: 2019/3/20 10:31
* @Version:V1.0
* @Description:DeptConsumer_DashBoard_App
*/
@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumer_DashBoard_App {
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer_DashBoard_App.class,args);
}
}
服務提供者需添加:
<!-- actuator監控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
啓動相應的,註冊服務者,服務提供者,消費者,