0. 前言
使用RestTemplate發(fā)送請求
了解SpringCloud的作用
搭建Eureka注冊中心
了解Robbin負載均衡
了解Hystrix熔斷器
1. 系統(tǒng)架構(gòu)
graph LR;
1[集中式架構(gòu)] --> 2[垂直拆分]
2 --> 3[分布式服務(wù)]
3 --> 4[SOA面向服務(wù)架構(gòu)]
4 --> 5[微服務(wù)架構(gòu)]
前面在介紹dubbo的時候提過微服務(wù)的概念了http://www.itdecent.cn/p/310509f4790c
spring cloud則是spring framework在微服務(wù)上的設(shè)計
微服務(wù)的特點:
單一職責(zé):微服務(wù)中每一個服務(wù)都對應(yīng)唯一的業(yè)務(wù)能力,做到單一職責(zé)
微:微服務(wù)的服務(wù)拆分粒度很小,例如一個用戶管理就可以作為一個服務(wù)。每個服務(wù)雖小,但“五臟俱全”。
面向服務(wù):面向服務(wù)是說每個服務(wù)都要對外暴露Rest風(fēng)格服務(wù)接口API。并不關(guān)心服務(wù)的技術(shù)實現(xiàn),做到與平臺
和語言無關(guān),也不限定用什么技術(shù)實現(xiàn),只要提供Rest的接口即可。
自治:自治是說服務(wù)間互相獨立,互不干擾
團隊獨立:每個服務(wù)都是一個獨立的開發(fā)團隊,人數(shù)不能過多。
技術(shù)獨立:因為是面向服務(wù),提供Rest接口,使用什么技術(shù)沒有別人干涉
前后端分離:采用前后端分離開發(fā),提供統(tǒng)一Rest接口,后端不用再為PC、移動端開發(fā)不同接口
數(shù)據(jù)庫分離:每個服務(wù)都使用自己的數(shù)據(jù)源
部署獨立,服務(wù)間雖然有調(diào)用,但要做到服務(wù)重啟不影響其它服務(wù)。有利于持續(xù)集成和持續(xù)交付。每個服務(wù)都是獨立的組件,可復(fù)用,可替換,降低耦合,易維護
微服務(wù)架構(gòu)與SOA都是對系統(tǒng)進行拆分;微服務(wù)架構(gòu)基于SOA思想,可以把微服務(wù)當(dāng)做去除了ESB的SOA。ESB是SOA架構(gòu)中的中心總線,設(shè)計圖形應(yīng)該是星形的,而微服務(wù)是去中心化的分布式軟件架構(gòu)。兩者比較類似,但其實也有一些差別:
| 功能 | SOA | 微服務(wù) |
|---|---|---|
| 組件大小 | 大塊業(yè)務(wù)邏輯 | 單獨任務(wù)或小塊業(yè)務(wù)邏輯 |
| 耦合 | 通常松耦合 | 總是松耦合 |
| 管理 | 著重中央管理 | 著重分散管理 |
| 目標(biāo) | 確保應(yīng)用能夠交互操作 | 易維護、易擴展、更輕量級的交互 |
2. 遠程調(diào)用
無論是微服務(wù)還是SOA,都面臨著服務(wù)間的遠程調(diào)用。那么服務(wù)間的遠程調(diào)用方式有哪些呢?
常見的遠程調(diào)用方式有以下2種:
RPC:Remote Produce Call遠程過程調(diào)用,RPC****基于****Socket****,工作在會話層。自定義數(shù)據(jù)格式,速度快,效率高。早期的webservice,現(xiàn)在熱門的dubbo,都是RPC的典型代表
Http:http其實是一種網(wǎng)絡(luò)傳輸協(xié)議,基于****TCP****,工作在應(yīng)用層,規(guī)定了數(shù)據(jù)傳輸?shù)母袷?/strong>?,F(xiàn)在客戶端瀏覽器與服務(wù)端通信基本都是采用Http協(xié)議,也可以用來進行遠程服務(wù)調(diào)用。缺點是消息封裝臃腫,優(yōu)勢是對服務(wù)的提供和調(diào)用方?jīng)]有任何技術(shù)限定,自由靈活,更符合微服務(wù)理念?,F(xiàn)在熱門的Rest風(fēng)格,就可以通過http協(xié)議來實現(xiàn)。
區(qū)別:RPC的機制是根據(jù)語言的API(language API)來定義的,而不是根據(jù)基于網(wǎng)絡(luò)的應(yīng)用來定義的。
如果你們公司全部采用Java技術(shù)棧,那么使用Dubbo作為微服務(wù)架構(gòu)是一個不錯的選擇。
相反,如果公司的技術(shù)棧多樣化,而且你更青睞Spring家族,那么Spring Cloud搭建微服務(wù)是不二之選。在我們的項目中,會選擇Spring Cloud套件,因此會使用Http方式來實現(xiàn)服務(wù)間調(diào)用。
關(guān)于RPC的概念稍微更陌生,可以參考下
我個人理解為,rpc多用在系統(tǒng)之間的組件的交互,例如分布式的組件之間的交互。
基于RPC的API更加適用行為(也就是命令和過程),基于REST的API更加適用于構(gòu)建模型(也就是資源和實體),處理CRUD。
- REST使用HTTP的方法,例如:GET,POST,PUT,DELETE,OPTIONS還有比較不常用的PATCH方法。
- RPC通常只會使用GET和POST方法,GET方法通常用來獲取信息,POST方法可以用來進行所有的行為。
3. Spring的RestTemplate
既然微服務(wù)選擇了Http,那么我們就需要考慮自己來實現(xiàn)對請求和響應(yīng)的處理。不過開源世界已經(jīng)有很多的http客戶
端工具,能夠幫助我們做這些事情,例如:
HttpClient
OKHttp
URLConnection
不過這些不同的客戶端,API各不相同。
而Spring也有對http的客戶端進行封裝,提供了工具類叫RestTemplate。Spring提供了一個RestTemplate模板工具類,對基于Http的客戶端進行了封裝,并且實現(xiàn)了對象與json的序列化和反序列化,非常方便。RestTemplate并沒有限定Http的客戶端類型,而是進行了抽象,目前常用的3種都有支持,其中默認的為JDK原生的URLConnection。
4. Spring Cloud簡介
4.1 簡介
Spring Cloud是Spring旗下的項目之一,官網(wǎng)地址:http://projects.spring.io/spring-cloud/
Spring最擅長的就是集成,把世界上最好的框架拿過來,集成到自己的項目中。
Spring Cloud也是一樣,它將現(xiàn)在非常流行的一些技術(shù)整合到一起,實現(xiàn)了諸如:配置管理,服務(wù)發(fā)現(xiàn),智能路由,負載均衡,熔斷器,控制總線,集群狀態(tài)等功能;協(xié)調(diào)分布式環(huán)境中各個系統(tǒng),為各類服務(wù)提供模板性配置。其主要
涉及的組件包括:
Eureka:注冊中心
Zuul、Gateway:服務(wù)網(wǎng)關(guān)
Ribbon:負載均衡
Feign:服務(wù)調(diào)用
Hystrix或Resilience4j:熔斷器
以上只是其中一部分,架構(gòu)圖:

4.2 版本
Spring Cloud不是一個組件,而是許多組件的集合;
SpringCloud則是通過希臘英文字母的方式,在發(fā)布的版本時是以倫敦地鐵站作為版本命名,并按地鐵站名稱的首字母A-Z依次命名。
- 第一代版本: Angle;
- 第二代版本: Brixton;
- 第三代版本: Camden;
- 第四代版本: Dalston;
- 第五代版本: Edgware;
- 第六代版本: Finchley;
- 第七代版本: GreenWich;
- 第八代版本: Hoxton;

項目中使用新的Hoxton版本
5. 模擬微服務(wù)場景
5.1 創(chuàng)建父工程
微服務(wù)中需要同時創(chuàng)建多個項目,但為了學(xué)習(xí),先創(chuàng)建一個父工程,然后后續(xù)的工程都以這個工程為父,實現(xiàn)maven的聚合。這樣可以在一個窗口看到所有工程,方便學(xué)習(xí)。在實際開發(fā)中,每個微服務(wù)可獨立一個工程。
創(chuàng)建一個spring-cloud-parent的module并修改pom.xml,這里指定了子工程的spring cloud、通用mapper、和mysql connector版本
<groupId>org.example</groupId>
<artifactId>spring-cloud-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<mapper.starter.version>2.1.5</mapper.starter.version>
<mysql.version>5.1.46</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 通用Mapper啟動器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!-- mysql驅(qū)動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
5.2 創(chuàng)建user-service工程
在父工程下右鍵new一個子模塊user-service,并添加依賴
<parent>
<artifactId>spring-cloud-parent</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 通用Mapper啟動器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<!-- mysql驅(qū)動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
然后我們復(fù)用之前的代碼并刪掉一些不必要的文件和參數(shù)http://www.itdecent.cn/p/d8dd982ba754

application.yml,綁定localhost的9091端口
jdbc:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test
username: root
password: root
#激活配置文件;需要制定其他的配置文件名稱
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test
username: root
password: root
redis:
host: localhost
port: 6379
#日志記錄級別
logging:
level:
org.example: debug
org.springframework: info
#tomcat端口
server:
port: 9091
# mybatis配置
mybatis:
# 實體類別名包路徑
type-aliases-package: org.example.pojo
# 映射文件路徑
# mapper-locations: classpath:mappers/*.xml
configuration:
# 控制臺輸出執(zhí)行sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
自動生成后我想運行啟動類,發(fā)現(xiàn)運行不了,出現(xiàn)以下錯誤
java: 程序包org.springframework.boot不存在
以為是沒設(shè)置source root和resource root和test root,設(shè)置完后也沒有用。
發(fā)現(xiàn)右邊欄也沒有maven工具欄,問題出在這個項目并沒有被idea識別為maven項目,右鍵我們的pom.xml,點擊 mark as maven project,然后就能識別為maven項目了
5.3 創(chuàng)建服務(wù)調(diào)用者consumer-demo
同樣的在parent下創(chuàng)建,修改pom.xml,添加依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
因為是消費端,不需要直接和數(shù)據(jù)庫打交道,就不用mybatis和通用mapper了
修改啟動類,注冊一個RestTemplate
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
創(chuàng)建實體類pojo.User.java
package org.example.pojo;
import lombok.Data;
import java.util.Date;
/**
* @ClassName User
* @Description TODO
* @Author Patrick Star
* @Date 2020/12/16 2:22 下午
*/
@Data
public class User {
private Long id;
private String userName;
// 密碼
private String password;
// 姓名
private String name;
// 年齡
private Integer age;
// 性別,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 創(chuàng)建時間
private Date created;
// 更新時間
private Date updated;
// 備注
private String note;
}
增加一個controller,直接調(diào)用RestTemplate,遠程訪問user-service的服務(wù)接口
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
public RestTemplate restTemplate;
@GetMapping("{id}")
public User queryById(@PathVariable Long id){
String url = "localhost:9091/user/"+id;
return restTemplate.getForObject(url,User.class);
}
}
此時,consumer端沒有配置端口,默認就是8080
瀏覽器輸入http://localhost:8080/consumer/1 得到查詢結(jié)果

5.4 思考問題
簡單回顧一下,剛才我們寫了什么:我們沒有使用spring cloud 是吧,只是單純的用spring boot把消費端與生產(chǎn)端又做了一遍,這個我們之前都謝過了。
user-service:對外提供了查詢用戶的接口
consumer-demo:通過RestTemplate訪問 http://locahost:9091/user/{id} 接口,查詢用戶數(shù)據(jù)
存在什么問題?
在consumer中,我們把url地址硬編碼到了代碼中,不方便后期維護
consumer需要記憶user-service的地址,如果出現(xiàn)變更,可能得不到通知,地址將失效
consumer不清楚user-service的狀態(tài),服務(wù)宕機也不知道
user-service只有1臺服務(wù),不具備高可用性
即便user-service形成集群,consumer還需自己實現(xiàn)負載均衡
其實上面說的問題,概括一下就是分布式服務(wù)必然要面臨的問題:
-
服務(wù)管理
? - 如何自動注冊和發(fā)現(xiàn)
? - 如何實現(xiàn)狀態(tài)監(jiān)管
? - 如何實現(xiàn)動態(tài)路由
服務(wù)如何實現(xiàn)負載均衡
服務(wù)如何解決容災(zāi)問題
服務(wù)如何實現(xiàn)統(tǒng)一配置
以上的問題,都將在SpringCloud中得到答案。
6. Eureka注冊中心
首先我們來解決第一問題,服務(wù)的管理。
6.1 問題分析
在剛才的案例中,user-service對外提供服務(wù),需要對外暴露自己的地址。而consumer-demo(調(diào)用者)需要記錄
服務(wù)提供者的地址。將來地址出現(xiàn)變更,還需要及時更新。這在服務(wù)較少的時候并不覺得有什么,但是在現(xiàn)在日益復(fù)
雜的互聯(lián)網(wǎng)環(huán)境,一個項目可能會拆分出十幾,甚至幾十個微服務(wù)。此時如果還人為管理地址,不僅開發(fā)困難,將來
測試、發(fā)布上線都會非常麻煩,這與DevOps的思想是背道而馳的。
DevOps的思想是系統(tǒng)可以通過一組過程、方法或系統(tǒng);提高應(yīng)用發(fā)布和運維的效率,降低管理成本。
網(wǎng)約車
這就好比是 網(wǎng)約車出現(xiàn)以前,人們出門叫車只能叫出租車。一些私家車想做出租卻沒有資格,被稱為黑車。而很多
人想要約車,但是無奈出租車太少,不方便。私家車很多卻不敢攔,而且滿大街的車,誰知道哪個才是愿意載人的。
一個想要,一個愿意給,就是缺少引子,缺乏管理啊。
此時滴滴這樣的網(wǎng)約車平臺出現(xiàn)了,所有想載客的私家車全部到滴滴注冊,記錄你的車型(服務(wù)類型),身份信息
(聯(lián)系方式)。這樣提供服務(wù)的私家車,在滴滴那里都能找到,一目了然。
此時要叫車的人,只需要打開APP,輸入你的目的地,選擇車型(服務(wù)類型),滴滴自動安排一個符合需求的車到你
面前,為你服務(wù),完美!
Eureka做什么?
Eureka就好比是滴滴,負責(zé)管理、記錄服務(wù)提供者的信息。服務(wù)調(diào)用者無需自己尋找服務(wù),而是把自己的需求告訴
Eureka,然后Eureka會把符合你需求的服務(wù)告訴你。
同時,服務(wù)提供方與Eureka之間通過 “心跳” 機制進行監(jiān)控,當(dāng)某個服務(wù)提供方出現(xiàn)問題,Eureka自然會把它從服務(wù)
列表中剔除。
這就實現(xiàn)了服務(wù)的自動注冊、發(fā)現(xiàn)、狀態(tài)監(jiān)控。
6.2 原理
基本架構(gòu)

Eureka:就是服務(wù)注冊中心(可以是一個集群),對外暴露自己的地址
提供者:啟動后向Eureka注冊自己信息(地址,提供什么服務(wù))
消費者:向Eureka訂閱服務(wù),Eureka會將對應(yīng)服務(wù)的所有提供者地址列表發(fā)送給消費者,并且定期更新
心跳(續(xù)約):提供者定期通過http方式向Eureka刷新自己的狀態(tài)
6.3 搭建eureka-server工程
創(chuàng)建工程
創(chuàng)建eureka-server,依舊是父工程下,添加依賴
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
編寫啟動類
//聲明當(dāng)前應(yīng)用為eureka服務(wù)
@EnableEurekaServer
@SpringBootApplication
public class EurelaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurelaServerApplication.class);
}
}
編寫配置文件application.yml
server:
port: 10086
spring:
application:
name: eureka-server #應(yīng)用名稱,會在Eureka中作為服務(wù)而ID標(biāo)識(serverId)
eureka:
client:
service-url: #EurekaServer的地址,現(xiàn)在是自己的地址,如果是集群,需要寫其它Server地址
defaultZone: http:127.0.0.1:10086/eureka
register-with-eureka: false # 不注冊自己
fetch-registry: false #不拉取
啟動服務(wù)
啟動 eureka-server 訪問:http://127.0.0.1:10086

6.4 服務(wù)注冊
注冊服務(wù),就是在服務(wù)上添加Eureka的客戶端依賴,客戶端代碼會自動把服務(wù)注冊到EurekaServer中。
添加依賴
我們在user-service中添加Eureka客戶端依賴:
<!-- Eureka客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改啟動類
在啟動類上開啟Eureka客戶端功能,通過添加 @EnableDiscoveryClient 來開啟Eureka客戶端功能
//聲明當(dāng)前應(yīng)用為eureka服務(wù)
@EnableEurekaServer
@EnableEurekaClient
@SpringBootApplication
public class EurelaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurelaServerApplication.class);
}
}
修改user-service配置文件
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test
username: root
password: root
redis:
host: localhost
port: 6379
application:
name: user-service
#日志記錄級別
logging:
level:
org.example: debug
org.springframework: info
#tomcat端口
server:
port: 9091
# mybatis配置
mybatis:
# 實體類別名包路徑
type-aliases-package: org.example.pojo
# 映射文件路徑
# mapper-locations: classpath:mappers/*.xml
configuration:
# 控制臺輸出執(zhí)行sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
注意:
這里我們添加了spring.application.name屬性來指定應(yīng)用名稱,將來會作為服務(wù)的id使用。
測試
重啟 user-service 項目,訪問Eureka監(jiān)控頁面,可以看到user-service已經(jīng)注冊成功了

6.5 服務(wù)發(fā)現(xiàn)
接下來我們修改 consumer-demo ,嘗試從EurekaServer獲取服務(wù)。
方法與服務(wù)提供者類似,只需要在項目中添加EurekaClient依賴,就可以通過服務(wù)名稱來獲取信息了!
添加依賴
修改consumer-demo的pom.xml
<!-- Eureka客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改啟動類
@SpringBootApplication
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
新增配置文件
server:
port: 8080
spring:
application:
name: connsumer-demo #應(yīng)用名稱
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
修改controller
package org.example.controller;
import org.example.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @ClassName ConsumerController
* @Description TODO
* @Author Patrick Star
* @Date 2020/12/16 2:24 下午
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
public RestTemplate restTemplate;
// 新增discoveryClient,注意包名
// import org.springframework.cloud.client.discovery.DiscoveryClient;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
public User queryById(@PathVariable Long id) {
String url = "http://localhost:9091/user/" + id;
// 獲取eureka中注冊的user-service實例列表
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = serviceInstanceList.get(0);
url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/"+id;
return restTemplate.getForObject(url, User.class);
}
}
Debug跟蹤運行
重啟 consumer-demo 項目;然后再瀏覽器中再次訪問 http://localhost:8080/consumer/1 ;在代碼中debug跟進查
看最終拼接要訪問的URL:

6.6 Eureka詳解
基礎(chǔ)架構(gòu)
Eureka架構(gòu)中的三個核心角色:
服務(wù)注冊中心
Eureka的服務(wù)端應(yīng)用,提供服務(wù)注冊和發(fā)現(xiàn)功能,就是剛剛我們建立的eureka-server
服務(wù)提供者
提供服務(wù)的應(yīng)用,可以是SpringBoot應(yīng)用,也可以是其它任意技術(shù)實現(xiàn),只要對外提供的是Rest風(fēng)格服務(wù)即可。本例中就是我們實現(xiàn)的user-service
服務(wù)消費者
消費應(yīng)用從注冊中心獲取服務(wù)列表,從而得知每個服務(wù)方的信息,知道去哪里調(diào)用服務(wù)方。本例中就是我們實現(xiàn)的consumer-demo
高可用的Eureka Server
Eureka Server即服務(wù)的注冊中心,在剛才的案例中,我們只有一個EurekaServer,事實上EurekaServer也可以是一個集群,形成高可用的Eureka中心。
服務(wù)同步
多個Eureka Server之間也會互相注冊為服務(wù),當(dāng)服務(wù)提供者注冊到Eureka Server集群中的某個節(jié)點時,該節(jié)點會把服務(wù)的信息同步給集群中的每個節(jié)點,從而實現(xiàn)數(shù)據(jù)同步。因此,無論客戶端訪問到Eureka Server集群中的任意一個節(jié)點,都可以獲取到完整的服務(wù)列表信息。
而作為客戶端,需要把信息注冊到每個Eureka中:

如果有三個Eureka,則每一個EurekaServer都需要注冊到其它幾個Eureka服務(wù)中,例如:有三個分別為10086、10087、10088,則:
10086要注冊到10087和10088上
10087要注冊到10086和10088上
10088要注冊到10086和10087上
搭建單機Eureka Server集群
在單機配置兩個Eureka Server,端口分別為 10086 10010
1)修改原來Eureka配置文件
server:
port: ${port:10086}
spring:
application:
name: eureka-server #應(yīng)用名稱,會在Eureka中作為服務(wù)而ID標(biāo)識(serverId)
eureka:
client:
service-url:
# eureka 服務(wù)地址,如果是集群的話;需要指定其它集群eureka地址
defaultZone: http://127.0.0.1:10086/eureka
所謂的高可用注冊中心,其實就是把EurekaServer自己也作為一個服務(wù),注冊到其它EurekaServer上,這樣多個
EurekaServer之間就能互相發(fā)現(xiàn)對方,從而形成集群。因此我們做了以下修改:
注意把register-with-eureka和fetch-registry修改為true或者注釋掉
在上述配置文件中的${}表示在jvm啟動時候若能找到對應(yīng)port或者defaultZone參數(shù)則使用,若無則使用后面
的默認值
把service-url的值改成了另外一臺EurekaServer的地址,而不是自己
2)另外一臺啟動的時候指定端口port和defaultZone配置
點擊Edit Configurations...

修改原來的啟動配置;在如下界面中的 VM options 中設(shè)置 -DdefaultZone=http://127.0.0.1:10010/eureka

將它復(fù)制一份命名為10010,再進行配置-Dport=10010 -DdefaultZone=http://127.0.0.1:10086/eureka

3)客戶端注冊服務(wù)到集群
因為EurekaServer不止一個,因此 user-service 項目注冊服務(wù)或者consumer-demo獲取服務(wù)的時候,service-url參數(shù)需要修改為如下:
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka, http://localhost:10010/eureka
4)查看集群
啟動兩臺server查看集群

為了方便后面的演示,在將集群改為單個服務(wù)
Erueka客戶端
服務(wù)提供者要向EurekaServer注冊服務(wù),并且完成服務(wù)續(xù)約等工作。
服務(wù)注冊
服務(wù)提供者在啟動時,會檢測配置屬性中的: eureka.client.register-with-erueka=true 參數(shù)是否正確,事實上
默認就是true。如果值確實為true,則會向EurekaServer發(fā)起一個Rest請求,并攜帶自己的元數(shù)據(jù)信息,Eureka
Server會把這些信息保存到一個雙層Map結(jié)構(gòu)中。
第一層Map的Key就是服務(wù)id,一般是配置中的 spring.application.name 屬性
第二層Map的key是服務(wù)的實例id。一般host+ serviceId + port,例如: localhost:user-service:8081
值則是服務(wù)的實例對象,也就是說一個服務(wù),可以同時啟動多個不同實例,形成集群。
默認注冊時使用的是主機名或者localhost,如果想用ip進行注冊,可以在 user-service 中添加配置如下:
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka, http://localhost:10010/eureka
instance:
ip-address: 127.0.0.1 # ip地址
prefer-ip-address: true # 更傾向于使用ip,而不是host名
修改完后先后重啟 user-service 和 consumer-demo ;在調(diào)用服務(wù)的時候就已經(jīng)變成ip地址;需要注意的是:不是在
eureka中的控制臺服務(wù)實例狀態(tài)顯示。
服務(wù)續(xù)約
在注冊服務(wù)完成以后,服務(wù)提供者會維持一個心跳(定時向EurekaServer發(fā)起Rest請求),告訴EurekaServer:“我
還活著”。這個我們稱為服務(wù)的續(xù)約(renew);
有兩個重要參數(shù)可以修改服務(wù)續(xù)約的行為;可以在 user-service 中添加如下配置項:
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka, http://localhost:10010/eureka
instance:
# 更傾向使用ip地址,而不是host名
prefer-ip-address: true
# ip地址
ip-address: 127.0.0.1
# 續(xù)約間隔,默認30秒
lease-renewal-interval-in-seconds: 5
# 服務(wù)失效時間,默認90秒
lease-expiration-duration-in-seconds: 5
``lease-renewal-interval-in-second`:服務(wù)續(xù)約(renew)的間隔,默認為30秒
lease-expiration-duration-in-seconds:服務(wù)失效時間,默認值90秒
也就是說,默認情況下每隔30秒服務(wù)會向注冊中心發(fā)送一次心跳,證明自己還活著。如果超過90秒沒有發(fā)送心跳,
EurekaServer就會認為該服務(wù)宕機,會定時(eureka.server.eviction-interval-timer-in-ms設(shè)定的時間)從服務(wù)列表
中移除,這兩個值在生產(chǎn)環(huán)境不要修改,默認即可。
獲取服務(wù)列表
當(dāng)服務(wù)消費者啟動時,會檢測eureka.client.fetch-registry=true 參數(shù)的值,如果為true,則會從Eureka Server服務(wù)的列表拉取只讀備份,然后緩存在本地。并且 每隔30秒 會重新拉取并更新數(shù)據(jù)??梢栽?consumer-demo項目中通過下面的參數(shù)來修改:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
registry-fetch-interval-seconds: 30
失效剔除和自我保護
如下的配置都是在Eureka Server服務(wù)端進行:
服務(wù)下線
當(dāng)服務(wù)進行正常關(guān)閉操作時,它會觸發(fā)一個服務(wù)下線的REST請求給Eureka Server,告訴服務(wù)注冊中心:“我要下線
了”。服務(wù)中心接受到請求之后,將該服務(wù)置為下線狀態(tài)。
失效剔除
有時我們的服務(wù)可能由于內(nèi)存溢出或網(wǎng)絡(luò)故障等原因使得服務(wù)不能正常的工作,而服務(wù)注冊中心并未收到“服務(wù)下
線”的請求。相對于服務(wù)提供者的“服務(wù)續(xù)約”操作,服務(wù)注冊中心在啟動時會創(chuàng)建一個定時任務(wù),默認每隔一段時間
(默認為60秒)將當(dāng)前清單中超時(默認為90秒)沒有續(xù)約的服務(wù)剔除,這個操作被稱為失效剔除。
可以通過 eureka.server.eviction-interval-timer-in-ms 參數(shù)對其進行修改,單位是毫秒。
自我保護
我們關(guān)停一個服務(wù),很可能會在Eureka面板看到一條警告:

這是觸發(fā)了Eureka的自我保護機制。當(dāng)服務(wù)未按時進行心跳續(xù)約時,Eureka會統(tǒng)計服務(wù)實例最近15分鐘心跳續(xù)約的比例是否低于了85%。
在生產(chǎn)環(huán)境下,因為網(wǎng)絡(luò)延遲等原因,心跳失敗實例的比例很有可能超標(biāo),但是此時就把服務(wù)剔除列表并不妥當(dāng),因為服務(wù)可能沒有宕機。Eureka在這段時間內(nèi)不會剔除任何服務(wù)實例,直到網(wǎng)絡(luò)恢復(fù)正常。
生產(chǎn)環(huán)境下這很有效,保證了大多數(shù)服務(wù)依然可用,不過也有可能獲取到失敗的服務(wù)實例,因此服務(wù)調(diào)用者必須做好服務(wù)的失敗容錯。
可以通過下面的配置來關(guān)停自我保護:
eureka:
client:
service-url:
# eureka 服務(wù)地址,如果是集群的話;需要指定其它集群eureka地址
defaultZone: ${defaultZone:http://127.0.0.1:10010/eureka}
server:
enable-self-preservation: false #關(guān)閉自我保護模式,默認為打開
7. 負載均衡Ribbon
7.1 什么是ribbon
在剛才的案例中,我們啟動了一個 user-service ,然后通過DiscoveryClient來獲取服務(wù)實例信息,然后獲取ip和端口來訪問。
但是實際環(huán)境中,往往會開啟很多個 user-service 的集群。此時獲取的服務(wù)列表中就會有多個,到底該訪問哪一個
呢?
一般這種情況下就需要編寫負載均衡算法,在多個實例列表中進行選擇。不過Eureka中已經(jīng)集成了負載均衡組件:Ribbon,簡單修改代碼即可使用。
什么是Ribbon:
Ribbon 是一個基于 HTTP 和 TCP 的 客服端負載均衡工具,它是基于 Netflix Ribbon 實現(xiàn)的。
它不像 Spring Cloud 服務(wù)注冊中心、配置中心、API 網(wǎng)關(guān)那樣獨立部署,但是它幾乎存在于每個 Spring Cloud 微服務(wù)中。包括 Feign 提供的聲明式服務(wù)調(diào)用也是基于該 Ribbon 實現(xiàn)的。
Ribbon 默認提供很多種負載均衡算法,例如輪詢、隨機等等。甚至包含自定義的負載均衡算法。
7.2 啟動兩個user-service實例
我們和上面一樣,修改兩個user-service的端口,一個為9091,一個為9092

7.3 開啟負載均衡
因為Eureka中已經(jīng)集成了Ribbon,所以我們無需引入新的依賴。修改consumer的啟動類。
給RestTemplate添加@LoadBalanced 注解:
@SpringBootApplication
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
修改ConsumerController調(diào)用方式,不再手動獲取ip和端口,而是直接通過服務(wù)名稱調(diào)用;
@GetMapping("{id}")
public User queryById(@PathVariable Long id) {
String url = "http://user-service/user/" + id;
// 獲取eureka中注冊的user-service實例列表
// List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("user-service");
// ServiceInstance serviceInstance = serviceInstanceList.get(0);
//
// url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/"+id;
return restTemplate.getForObject(url, User.class);
}
訪問http://localhost:8080/consumer/1
查看userService 9091和9092的控制臺,只有9091的端口打印了日志信息
Ribbon默認的負載均衡策略是輪詢。SpringBoot也幫提供了修改負載均衡規(guī)則的配置入口在consumer?
demo的配置文件中添加如下,就變成隨機的了:
在配置文件中添加如下
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
格式是: {服務(wù)名稱}.ribbon.NFLoadBalancerRuleClassName
反復(fù)請求測試一下,發(fā)現(xiàn)9091和9092的user-service都能收到信息打印日志
7.3 源碼跟蹤
為什么只輸入了service名稱就可以訪問了呢?之前還要獲取ip和端口。
顯然是有組件根據(jù)service名稱,獲取到了服務(wù)實例的ip和端口。因為 consumer-demo 使用的是RestTemplate,
spring的負載均衡自動配置類LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig會自動配置
負載均衡攔截器(在spring-cloud-commons-**.jar包中的spring.factories中定義的自動配置類), 它就是
LoadBalancerInterceptor ,這個類會在對RestTemplate的請求進行攔截,然后從Eureka根據(jù)服務(wù)id獲取服務(wù)列
表,隨后利用負載均衡算法得到真實的服務(wù)地址信息,替換服務(wù)id。
我們進行源碼跟蹤,查找LoadBalancerInterceptor并在serviceName上打上斷點

跟進excute方法

可以看到獲取了9092端口的服務(wù)

賊跟進下一次,發(fā)現(xiàn)后去的是9091
多次訪問 consumer-demo 的請求地址;然后跟進代碼,點左邊欄的小箭頭(resume program)可以快速調(diào)試,發(fā)現(xiàn)其果然實現(xiàn)了負載均衡。
8. 熔斷器Hystrix
8.1 簡介
Hystrix是Netflflix開源的一個延遲和容錯庫,用于隔離訪問遠程服務(wù)、第三方庫,防止出現(xiàn)級聯(lián)失敗。Hystrix 在英文里面的意思是 豪豬,它的logo 的圖是一頭豪豬,它在微服務(wù)系統(tǒng)中是一款提供保護機制的組件,和eureka一樣也是由netflflix公司開發(fā)。
8.2 雪崩
微服務(wù)中,服務(wù)間調(diào)用關(guān)系錯綜復(fù)雜,一個請求,可能需要調(diào)用多個微服務(wù)接口才能實現(xiàn),會形成非常復(fù)雜的調(diào)用鏈路:

如圖,一次業(yè)務(wù)請求,需要調(diào)用A、P、H、I四個服務(wù),這四個服務(wù)又可能調(diào)用其它服務(wù)。
如果此時,某個服務(wù)出現(xiàn)異常:

例如: 微服務(wù)I 發(fā)生異常,請求阻塞,用戶請求就不會得到響應(yīng),則tomcat的這個線程不會釋放,于是越來越多的
用戶請求到來,越來越多的線程會阻塞:

服務(wù)器支持的線程和并發(fā)數(shù)有限,請求一直阻塞,會導(dǎo)致服務(wù)器資源耗盡,從而導(dǎo)致所有其它服務(wù)都不可用,形成雪崩效應(yīng)。
這就好比,一個汽車生產(chǎn)線,生產(chǎn)不同的汽車,需要使用不同的零件,如果某個零件因為種種原因無法使用,那么就會造成整臺車無法裝配,陷入等待零件的狀態(tài),直到零件到位,才能繼續(xù)組裝。 此時如果有很多個車型都需要這個零件,那么整個工廠都將陷入等待的狀態(tài),導(dǎo)致所有生產(chǎn)都陷入癱瘓。一個零件的波及范圍不斷擴大。
Hystrix解決雪崩問題的手段主要是服務(wù)降級,包括:
線程隔離
服務(wù)熔斷
8.3 線程隔離和服務(wù)降級
原理
線程隔離示意圖

Hystrix為每個依賴服務(wù)調(diào)用分配一個小的線程池,如果線程池已滿調(diào)用將被立即拒絕,默認不采用排隊,加速失敗判定時間。
用戶的請求將不再直接訪問服務(wù),而是通過線程池中的空閑線程來訪問服務(wù),如果線程池已滿,或者請求超時,則會進行降級處理,什么是服務(wù)降級?
服務(wù)降級:優(yōu)先保證核心服務(wù),而非核心服務(wù)不可用或弱可用。
用戶的請求故障時,不會被阻塞,更不會無休止的等待或者看到系統(tǒng)崩潰,至少可以看到一個執(zhí)行結(jié)果(例如返回友好的提示信息) 。
服務(wù)降級雖然會導(dǎo)致請求失敗,但是不會導(dǎo)致阻塞,而且最多會影響這個依賴服務(wù)對應(yīng)的線程池中的資源,對其它服務(wù)沒有響應(yīng)。
觸發(fā)Hystrix服務(wù)降級的情況:
線程池已滿
請求超時
實現(xiàn)線程隔離
1)引入依賴
在 consumer-demo 消費端系統(tǒng)的pom.xml文件添加如下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2)開啟熔斷
在啟動類 ConsumerApplication 上添加注解:@EnableCircuitBreaker
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker //線程隔離
public class Application {
... ...
}
可以看到,我們類上的注解越來越多,在微服務(wù)中,經(jīng)常會引入上面的三個注解,于是Spring就提供了一個組合注解:@SpringCloudApplication,來看看他的實現(xiàn)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
正好包括了上面三個,因此我們用組合注解代替上面是三個,
@SpringCloudApplication
public class Application {
... ...
}
3)編寫降級邏輯
當(dāng)目標(biāo)服務(wù)的調(diào)用出現(xiàn)故障,我們希望快速失敗,給用戶一個友好提示。因此需要提前編寫好失敗時的降級處理邏
輯,要使用HystrixCommand來完成。
改造 ConsumerController.java處理器類
package org.example.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.example.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @ClassName ConsumerController
* @Description TODO
* @Author Patrick Star
* @Date 2020/12/16 2:24 下午
*/
@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {
@Autowired
public RestTemplate restTemplate;
// 新增discoveryClient,注意包名
// import org.springframework.cloud.client.discovery.DiscoveryClient;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallback")
public String queryById(@PathVariable Long id) {// 返回值為String
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);// User.class改為String.class
}
// 添加fallback函數(shù)
public String queryByIdFallback(Long id){
log.error("查詢用戶信息失敗。id:{}", id);
return "對不起,網(wǎng)絡(luò)太擁擠了!";
}
}
要注意;因為熔斷的降級邏輯方法必須跟正常邏輯方法保證:相同的參數(shù)列表和返回值聲明。
失敗邏輯中返回User對象沒有太大意義,一般會返回友好提示。所以把queryById的方法改造為返回String,
反正也是Json數(shù)據(jù)。這樣失敗邏輯中返回一個錯誤說明,會比較方便。
@HystrixCommand(fallbackMethod = "queryByIdFallBack"):用來聲明一個降級邏輯的方法
測試:當(dāng) user-service 正常提供服務(wù)時,訪問與以前一致。但是當(dāng)將 user-service 停機時,會發(fā)現(xiàn)頁面返回了降級處理信息:

4)默認的fallback
剛才把fallback寫在了某個業(yè)務(wù)方法上,如果這樣的方法很多,那豈不是要寫很多。所以可以把Fallback配置加在類上,實現(xiàn)默認fallback;
再次改造 ConsumerController.java
package org.example.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.example.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @ClassName ConsumerController
* @Description TODO
* @Author Patrick Star
* @Date 2020/12/16 2:24 下午
*/
@RestController
@RequestMapping("/consumer")
@Slf4j
@DefaultProperties(defaultFallback = "defaultFallback") //設(shè)置默認fallback
public class ConsumerController {
@Autowired
public RestTemplate restTemplate;
// 新增discoveryClient,注意包名
// import org.springframework.cloud.client.discovery.DiscoveryClient;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
// @HystrixCommand(fallbackMethod = "queryByIdFallback")
@HystrixCommand
public String queryById(@PathVariable Long id) {
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
// 添加fallback函數(shù)
public String queryByIdFallback(Long id) {
log.error("查詢用戶信息失敗。id:{}", id);
return "對不起,網(wǎng)絡(luò)太擁擠了!";
}
// 默認fallback
public String defaultFallback() {
return "默認提示:對不起,網(wǎng)絡(luò)太擁擠了!";
}
}
@DefaultProperties(defaultFallback = "defaultFallBack"):在類上指明統(tǒng)一的失敗降級方法;該類中所有方法返回類型要與處理失敗的方法的返回類型一致。
5)超時設(shè)置
在之前的案例中,請求在超過1秒后都會返回錯誤信息,這是因為Hystrix的默認超時時長為1,我們可以通過配置修改這個值;consumer-demo的application.yml 添加如下配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
這個配置會作用于全局所有方法。
為了觸發(fā)超時,可以在 user-service的UserService.java 的方法中讓其休眠2秒;
//根據(jù)id查詢
public User queryById(Long id) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return userMapper.selectByPrimaryKey(id);
}
測試一下:

可以發(fā)現(xiàn),請求的時長已經(jīng)到了2s+,證明配置生效了。如果把修改時間修改到2秒以下,又可以正常訪問
8.4 服務(wù)熔斷
原理
在服務(wù)熔斷中,使用的熔斷器,也叫斷路器,其英文單詞為:Circuit Breaker
熔斷機制與家里使用的電路熔斷原理類似;當(dāng)如果電路發(fā)生短路的時候能立刻熔斷電路,避免發(fā)生災(zāi)難。在分布式系統(tǒng)中應(yīng)用服務(wù)熔斷后;服務(wù)調(diào)用方可以自己進行判斷哪些服務(wù)反應(yīng)慢或存在大量超時,可以針對這些服務(wù)進行主動熔斷,防止整個系統(tǒng)被拖垮。
Hystrix的服務(wù)熔斷機制,可以實現(xiàn)彈性容錯;當(dāng)服務(wù)請求情況好轉(zhuǎn)之后,可以自動重連。通過斷路的方式,將后續(xù)請求直接拒絕,一段時間(默認5秒)之后允許部分請求通過,如果調(diào)用成功則回到斷路器關(guān)閉狀態(tài),否則繼續(xù)打開,拒絕請求的服務(wù)。
Hystrix的熔斷狀態(tài)機模型:

狀態(tài)機有3個狀態(tài):
Closed:關(guān)閉狀態(tài)(斷路器關(guān)閉),所有請求都正常訪問。
Open:打開狀態(tài)(斷路器打開),所有請求都會被降級。Hystrix會對請求情況計數(shù),當(dāng)一定時間內(nèi)失敗請求百分比達到閾值,則觸發(fā)熔斷,斷路器會完全打開。默認失敗比例的閾值是50%,請求次數(shù)最少不低于20次。
Half Open:半開狀態(tài),不是永久的,斷路器打開后會進入休眠時間(默認是5S)。隨后斷路器會自動進入半開狀態(tài)。此時會釋放部分請求通過,若這些請求都是健康的,則會關(guān)閉斷路器,否則繼續(xù)保持打開,再次進行休眠計時
實現(xiàn)熔斷
為了能夠精確控制請求的成功或失敗,在 consumer-demo 的處理器業(yè)務(wù)方法中加入一段邏輯;
修改 consumer-demo的ConsumerController.java
@GetMapping("{id}")
@HystrixCommand
public String queryById(@PathVariable Long id) {
if(id == 1){ throw new RuntimeException("太忙了"); }
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
這樣如果參數(shù)是id為1,一定失敗,其它情況都成功。(不要忘了清空user-service中的休眠邏輯)
我們準(zhǔn)備兩個請求窗口:
一個請求:http://localhost:8080/consumer/1,注定失敗
一個請求:http://localhost:8080/consumer/2,肯定成功
當(dāng)我們瘋狂訪問id為1的請求時(超過20次),就會觸發(fā)熔斷。斷路器會打開,一切請求都會被降級處理。
此時你訪問id為2的請求,會發(fā)現(xiàn)返回的也是失敗,而且失敗時間很短,只有20毫秒左右;因進入半開狀態(tài)之后2是可
以的。
不過,默認的熔斷觸發(fā)要求較高,休眠時間窗較短,為了測試方便,我們可以通過配置修改熔斷策略:
上述的配置項可以參考 HystrixCommandProperties 類中。