SpringCloud(一)-Ribbon負(fù)載均衡、Hystrix熔斷機(jī)制、

接上篇文章:
http://www.itdecent.cn/p/dac81a7bde30

負(fù)載均衡Ribbon

什么是Ribbon

Ribbon是Netflix發(fā)布的負(fù)載均衡器,它有助于控制HTTP和TCP客戶端的行為,為Ribbon配置服務(wù)提供者地址列表后,Ribbon就可基于某種負(fù)載均衡算法、自動(dòng)地幫助服務(wù)消費(fèi)者去請(qǐng)求。Ribbon默認(rèn)為我們提供了很多的負(fù)載均衡算法,例如輪詢、隨機(jī)等。我們也可以自定義Ribbon的負(fù)載均衡算法

實(shí)現(xiàn)Ribbon負(fù)載均衡

我們啟動(dòng)兩個(gè)user-service實(shí)例,一個(gè)8081 一個(gè)8082
和Eureka的高可用配置方式一致,復(fù)制啟動(dòng)參數(shù),修改啟動(dòng)端口

-Dserver.port=8082
image.png

EureKa監(jiān)控面板


監(jiān)控面板

開啟負(fù)載均衡

Eureka中已經(jīng)集成了Ribbon,所以我們無需引入新的依賴,直接修改代碼
在RestTemplate的配置方法上添加@LoadBalanced注解

@SpringBootApplication
@EnableDiscoveryClient //開啟Eureka客戶端
public class ConsumerApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

修改調(diào)用方式,不再手動(dòng)獲取ip和端口,直接通過服務(wù)名稱調(diào)用

    @GetMapping("{id}")
    public User queyById(@PathVariable("id") Long id) {
        //根據(jù)服務(wù)id獲取服務(wù)實(shí)例列表
        String url = "http://user-service/user/" + id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }

源碼分析

為什么在這里我們輸入服務(wù)名稱就可以調(diào)用了呢?之前還需要獲取ip和端口
顯然有人幫我們根據(jù)service名稱,獲取到了服務(wù)實(shí)例和ip端口。它就是LoadBalanceInterceptor,這個(gè)類會(huì)在對(duì)RestTemplate的請(qǐng)求進(jìn)行攔截,然后從Eureka根據(jù)服務(wù)id獲取服務(wù)列表,隨后利用負(fù)載均衡算法得到真實(shí)的服務(wù)地址信息,替換服務(wù)id
我們來看一下源碼:

image.png
image.png
image.png

修改負(fù)載均衡規(guī)則的配置入口:consumer的微服務(wù)配置

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式:{服務(wù)名稱}.ribbon.NFLoadBalancerRuleClassName 值就是IRule的實(shí)現(xiàn)類

負(fù)載均衡策略

Ribbon默認(rèn)是采用懶加載,即第一次訪問時(shí)才會(huì)去創(chuàng)建負(fù)載均衡客戶端,往往會(huì)出現(xiàn)超時(shí),如果需要采用饑餓加載,隨項(xiàng)目啟動(dòng)創(chuàng)建,可以這樣配置

ribbon:
  eager-load:
    clients: user-service
    enabled: true

負(fù)載均衡的源碼流程

image.png

通過RestTemplate進(jìn)行請(qǐng)求時(shí),我們?cè)L問的地址是http://user-service/user/1,RibbonLoadBalancerInterceptor會(huì)攔截RestTemplate的請(qǐng)求,并獲取請(qǐng)求的host,然后根據(jù)服務(wù)id獲取服務(wù)列表,然后會(huì)根據(jù)負(fù)載均衡規(guī)則選中某個(gè)服務(wù),選中服務(wù)后會(huì)重寫URL,這樣就相當(dāng)于通過ip和端口調(diào)用某個(gè)服務(wù)

Hystrix

簡(jiǎn)介

Hystrix是Netflix公司的一款組件,是一個(gè)開源的延遲和容錯(cuò)庫(kù),用于隔離訪問遠(yuǎn)程服務(wù)、第三方庫(kù),防止出現(xiàn)級(jí)聯(lián)失敗。

雪崩問題

在微服務(wù)中,服務(wù)間調(diào)用關(guān)系錯(cuò)綜復(fù)雜,一個(gè)請(qǐng)求,可能需要調(diào)用多個(gè)微服務(wù)接口與才能實(shí)現(xiàn),會(huì)形成非常復(fù)雜的調(diào)用鏈路:


復(fù)雜的調(diào)用鏈路

在一次業(yè)務(wù)請(qǐng)求中,需要調(diào)用A、P、H、I四個(gè)服務(wù),這四個(gè)服務(wù)又可能又調(diào)用其他服務(wù),這個(gè)時(shí)候,如果某個(gè)服務(wù)出現(xiàn)異常


image.png

例如微服務(wù)I發(fā)生異常,請(qǐng)求阻塞,用戶不會(huì)得到響應(yīng),則tomcat的這個(gè)線程不會(huì)釋放,于是越來越多的請(qǐng)求進(jìn)來后,就會(huì)造成越來越多的阻塞
image.png

服務(wù)器支持的線程和并發(fā)數(shù)有限,請(qǐng)求一直阻塞,會(huì)導(dǎo)致服務(wù)器資源耗盡,從而導(dǎo)致所有其它服務(wù)都不可用,形成雪崩效應(yīng)。
Hystrix解決雪崩問題的手段主要是服務(wù)降級(jí)

  • 線程隔離
  • 服務(wù)熔斷

線程隔離,服務(wù)降級(jí)

原理

線程隔離示意圖


線程隔離

解讀:
Hystrix為每個(gè)依賴服務(wù)調(diào)用分配一個(gè)小的線程池,如果線程池已滿調(diào)用將被立即拒絕,默認(rèn)不采用排隊(duì),加速失敗判定時(shí)間
用戶的請(qǐng)求將不再直接訪問服務(wù),而是通過線程池中的空閑線程來訪問服務(wù),如果線程池已滿,或者請(qǐng)求超時(shí),則會(huì)進(jìn)行降級(jí)處理

服務(wù)降級(jí)

優(yōu)先保證核心服務(wù),而非核心服務(wù)不可用或弱可用
用戶的請(qǐng)求故障時(shí),不會(huì)被阻塞,更不會(huì)無休止的等待或者看到系統(tǒng)奔潰,至少可以看到一個(gè)執(zhí)行結(jié)果。
服務(wù)降級(jí)雖然會(huì)導(dǎo)致請(qǐng)求失敗,但是不會(huì)導(dǎo)致阻塞。而且最多會(huì)影響這個(gè)依賴服務(wù)對(duì)應(yīng)的線程池中的資源,對(duì)其它服務(wù)沒有影響。
觸發(fā)Hystrix服務(wù)降級(jí)的情況

  • 線程池已滿
  • 請(qǐng)求超時(shí)

實(shí)戰(zhàn)

引入依賴

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

開啟熔斷

在啟動(dòng)類上添加注解 @EnableCircuitBreaker

@SpringBootApplication
@EnableDiscoveryClient //開啟Eureka客戶端
@EnableCircuitBreaker //開啟熔斷
public class ConsumerApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

我們?cè)陬惿系淖⒔庠絹碓蕉?,其?@SpringBootApplication @EnableDiscoveryClient @EnableCircuitBreaker 可以合并為一個(gè)注解
@SpringCloudApplication

編寫降級(jí)邏輯

當(dāng)目標(biāo)服務(wù)的調(diào)用出現(xiàn)故障,我們希望快速失敗,給用戶一個(gè)友好提示。這樣我們就需要提前編寫好失敗的降級(jí)處理邏輯。使用HystrixCommond來完成

  @GetMapping("{id}")
    @HystrixCommand(fallbackMethod = "queryByIdFallBack")
    public User queyById(@PathVariable("id") Long id) {
        //根據(jù)服務(wù)id獲取服務(wù)實(shí)例列表
        String url = "http://user-service/user/" + id;
        System.out.println("url=========" + url);
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }

    public String queryByIdFallBack(Long id) {
        log.error("查詢用戶信息失敗id:{}", id);
        return "網(wǎng)絡(luò)錯(cuò)誤";
    }
  • @HystrixCommand(fallbackMethod="queryByIdFallBack"):用來聲明一個(gè)降級(jí)邏輯的方法

需要注意的是,熔斷的降級(jí)邏輯方法必須跟正常邏輯方法保證:相同的參數(shù)列表和返回值聲明。
測(cè)試:
當(dāng)user-service正常提供服務(wù)時(shí),訪問服務(wù)與之前一致


image.png

我們將user-service停掉后,發(fā)現(xiàn)頁面返回了降級(jí)處理信息


image.png

默認(rèn)的Fallback

我們剛才是指定了Fallback,那如果需要服務(wù)降級(jí)的方法很多,我們豈不是要寫很多的fallback,我們可以把Fallback配置加載類上,實(shí)現(xiàn)默認(rèn)fallback
@DefaultProperties(defaultFallback="defaultFallBack")

@RestController
@RequestMapping("consumer")
@Slf4j
@DefaultProperties(defaultFallback = "queryByIdFallBack")
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;


    @GetMapping("{id}")
    @HystrixCommand
    public String queyById(@PathVariable("id") Long id) {
        //根據(jù)服務(wù)id獲取服務(wù)實(shí)例列表
        String url = "http://user-service/user/" + id;
        System.out.println("url=========" + url);
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    public String queryByIdFallBack(Long id) {
        log.error("查詢用戶信息失敗id:{}", id);
        return "網(wǎng)絡(luò)錯(cuò)誤";
    }
}

超時(shí)設(shè)置

在之前的案例中,請(qǐng)求在超過1秒后會(huì)返回錯(cuò)誤信息,這是因?yàn)镠ystrix的默認(rèn)超時(shí)時(shí)長(zhǎng)為1秒,我們可以在配置中修改這個(gè)值

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

這個(gè)配置會(huì)作用于全局所有方法

服務(wù)熔斷

熔斷原理

熔斷器,也叫斷路器。Circuit Breaker


image.png

Hystrix的熔斷狀態(tài)機(jī)模型


熔斷狀態(tài)機(jī)模型

狀態(tài)機(jī)有3個(gè)狀態(tài)

  • Closed:關(guān)閉狀態(tài),所有請(qǐng)求都正常訪問
  • Open:打開狀態(tài),所有請(qǐng)求都會(huì)被降級(jí)。Hystrix會(huì)對(duì)請(qǐng)求情況計(jì)數(shù),當(dāng)一定時(shí)間內(nèi)失敗請(qǐng)求百分比達(dá)到閥值,則觸發(fā)熔斷,斷路器會(huì)完全關(guān)閉。默認(rèn)失敗比例的閥值是50%,請(qǐng)求次數(shù)最少不低于20次
  • Half Open 半開狀態(tài)。open狀態(tài)不是永久的,打開后會(huì)進(jìn)入休眠時(shí)間,隨后斷路器會(huì)自動(dòng)進(jìn)入半開狀態(tài),此時(shí)會(huì)釋放1次請(qǐng)求通過,若這個(gè)請(qǐng)求是健康的,則會(huì)關(guān)閉斷路器,否則繼續(xù)保持打開,再次進(jìn)行5秒休眠

實(shí)戰(zhàn)

為了能夠精確控制請(qǐng)求的成功或失敗,我們?cè)赾onsumer的調(diào)用業(yè)務(wù)中加入一些邏輯

  @GetMapping("{id}")
    @HystrixCommand
    public String queyById(@PathVariable("id") Long id) {
        if (id == 1) {
            throw new RuntimeException("busy");
        }
        //根據(jù)服務(wù)id獲取服務(wù)實(shí)例列表
        String url = "http://user-service/user/" + id;
        System.out.println("url=========" + url);
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

在這個(gè)邏輯中,如果參數(shù)id為1,則一定會(huì)失敗,其它情況都成功
我們準(zhǔn)備兩個(gè)請(qǐng)求窗口

  1. http://localhost:8080/consumer/1 注定失敗
  2. http://localhost:8080/consumer/2 注定成功
    修改熔斷器默認(rèn)參數(shù):
circuitBreaker:
requestVolumeThreshold: 10
sleepWindowInMilliseconds: 100
errorThresholdPercentage: 50
  • requestVolumeThreshold:觸發(fā)熔斷的最小請(qǐng)求次數(shù),默認(rèn)20
  • sleepWindowInMilliseconds:休眠時(shí)長(zhǎng),默認(rèn)5000毫秒
  • errorThresholdPercentage:觸發(fā)熔斷的失敗請(qǐng)求最小占比 默認(rèn)50%

我們的操作是:瘋狂的對(duì)id=1進(jìn)行請(qǐng)求,就會(huì)觸發(fā)熔斷,斷路器會(huì)斷開,一切請(qǐng)求都會(huì)被降級(jí)處理,此時(shí)訪問id=2,發(fā)現(xiàn)返回的也是失敗,而且失敗時(shí)間只有20毫秒左右

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

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

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