【Spring Cloud】Eureka、Ribbon、Hystrix

0. 前言

  1. 使用RestTemplate發(fā)送請求

  2. 了解SpringCloud的作用

  3. 搭建Eureka注冊中心

  4. 了解Robbin負載均衡

  5. 了解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 框架?

rpc與rest區(qū)別于聯(lián)系

我個人理解為,rpc多用在系統(tǒng)之間的組件的交互,例如分布式的組件之間的交互。

如何理解RPC和REST

基于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 類中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容