以案例聊聊SpringCloud分佈式系統的架構者(三)

一般的系統中存在一個很棘手的問題,就是一個服務器同時訪問的量比較大,逼近甚至超過了臺服務器的最大負載量,然後服務器崩了,再比如:由於網路方面的問題,用戶可能在訪問一個網站或者使用網站的某個熱點功能的時候,突然訪問不成就像網站卡頓到了那,讓用戶退也不是不退也不是。一般的系統中存在這個問題,在我們分佈式系統中也存在這種問題,尤其是在高併發訪問的情況下,同一時刻用戶訪問量幾倍甚至幾百倍的超過服務器最大承受量,那麼這種情況也會發生。此時在理想的情況下就是,一旦某臺服務器上的服務,出現故障。那麼立即把這臺服務器上的服務斷開,返回一個斷開的標誌,讓這種故障不再向上傳導。

歸納起來就是:在微服務場景中,通常會有很多層的服務調用。如果一個底層服務出現問題,故障會被向上傳播給用戶。我們需要一種機制,當底層服務不可用時,可以阻斷故障的傳播。這就是斷路器的作用。他是系統服務穩定性的最後一重保障。

在springcloud中斷路器組件就是Hystrix。Hystrix也是Netflix套件的一部分。他的功能是,當對某個服務的調用在一定的時間內(默認10s,由metrics.rollingStats.timeInMilliseconds配置),有超過一定次數(默認20次,由circuitBreaker.requestVolumeThreshold參數配置)並且失敗率超過一定值(默認50%,由circuitBreaker.errorThresholdPercentage配置),該服務的斷路器會打開。返回一個由開發者設定的fallback。

fallback可以是另一個由Hystrix保護的服務調用,也可以是固定的值。fallback也可以設計成鏈式調用,先執行某些邏輯,再返回fallback。

在SpringCloud中有一個負載均衡的概念和實現的方式。具體的表現形式是這樣的:有一個服務,這一個服務運行在不同節點的機器上。注意兩臺機器上運行的服務本質上是一個服務。也可以這麼理解一個jar包運行在了兩個機器中(連裏面的配置都是一樣的,除了端口號以外)。雖然兩個機器中運行的是一個jar包,那麼這兩個jar包無論如何都需要向註冊中心註冊,那麼在註冊中心的表現就是服務提供者的名稱公用一個,但這個名稱下面對應兩臺機器的服務。在服務消費者在調用的時候就會出現負載均衡的情況。它會的訪問會在在兩臺機器上來回的切換。具體表現形式是:

上面就是SpringCloud提供的負載均衡的體現。SpringCloud實現均衡負載只需要在RestTemplter對象注入的時候在@Bean上加上@LoadBalanced註解就可以了。

那麼有了這種負載均衡的機制,其實是很好的。同樣它也存在隱患,那就是一臺機器發生了故障的時候。有的人看到這會說,在調用量不大的時候一臺機器發生故障了那就故障唄不是還有一臺機器嗎。那麼問題來了,一臺機器故障了那你的告訴它的調用者啊,萬一一個服務的調用者恰好調用了這個臺故障的機器呢,那麼因爲你的故障就會給服務的調用者返回不可預期的錯誤。所以這裏需要斷路器。一旦某臺機器發生故障,將這臺服務器中斷,然後服務的消費者如果調用故障機器那麼故障機器中的服務提供者就會調用預先處理故障的方法。調用者這邊只需要判斷服務提供者裏是否返回給了特定的錯誤標誌,沒有證明機器就是好的,只要有一條說明機器故障。可再次調用服務的提供者或者有其他的操作。好了接下來我就以之前的案例爲準進行拓展,將斷路器加進去。

這裏需要說明一下,SpringCloud對RestTempletr調用服務提供者的方式和Feign調用提供服務者的方式都實現了相應的斷路器,但本質上其實是同一個底層實現。先來RestTempletr對象的:

第一步:新創建一個maven項目也就是model

第二步:添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

第三步:設置配置文件

#配置端口號
server.port=6050

#配置服務消費者的名稱,以便依賴計算關係
spring.application.name=server-consumer-Hystrix-ribbon

#配置註冊中心的地址
eureka.client.service-url.defaultZone=http://localhost:6010/eureka/

第四步:在啓動的main方法上面注入RestTempletr對象和加入註解。如下:

第五步:提供controller供web訪問、再提供個service和其實現類
package com.alibaba.controller;

import com.alibaba.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    UserService userService;

    @RequestMapping(value="/getUserMassage")
    public String getUserMassgae(String username){
      return userService.getUserMassage(username);
    }
}

service的實現類:

第六步:直接啓動main方法。然後觀察到,當web瀏覽器中調用在上面定義的UserController中的getUserMassage方法後剛開始是能正確的在兩臺機器上來回切換的,當我們把其中的一個機器停了以後,再不斷的訪問getUserMassage時就會發現一個可以繼續返回正常的值,一個就會返回error。這個error的返回值就是服務提供者返回給服務消費者的標誌。

接下來,我說下一種Feign方式調用服務提供者時的斷路器。

第一步:在創建的項目的時候添加依賴


第二步:配置服務的信息

#配置服務消費者的端口號
server.port=6070

#激活Hystrix
feign.hystrix.enabled=true

#配置服務消費者的名稱,以便計算依賴
spring.application.name=server-consumer-Hystrix-Feige

#配置服務註冊中心的地址
eureka.client.service-url.defaultZone=http://localhost:6010/eureka/

第三步:創建用於web瀏覽器訪問的Controller和調用消費提供者的service

Controller:

package com.alibaba.controller;

import com.alibaba.service.UserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;


@RestController
public class UserController {

    @Resource
    UserService userService;

    @RequestMapping("/getUserMassage")
    public String getUserMassage(@RequestParam("username") String username){
      return userService.getUserMassage(username);
    }
}

Service:

package com.alibaba.service;

import com.alibaba.service.Impl.UserServiceImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "SERVER-PROVIDER",fallback = UserServiceImpl.class)
public interface UserService {

    @RequestMapping("/getUserMassage")
    String getUserMassage(@RequestParam("username")  String username);
}

ServiceImpl:

package com.alibaba.service.Impl;

import com.alibaba.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Override
    public String getUserMassage(String username) {
        return "error is";
    }
}

第四步:在啓動類中添加註解

package com.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class ServerConsumer03Application {

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

}

在Feign的服務調用方式中,我們將服務提供者的服務名和如果服務提供者發生異常後斷開服務的標誌(就是發生異常後,需要調用一個同名方法的返回值,這個放回值是斷開服務的標誌)需要的關聯對象都定義到了@FeignClient註解裏面。這個斷開服務的標誌一般是這個interface的實現類。在service接口中的某一個方法去調用服務提供者的服務的時候(注意:接口方法上面@RequestRapping註解裏的路徑是服務提供者的方法,它和接口上面@FeignClient註解裏面定義的服務提供者的名稱組合到一起就相當於是RestTemplater調用的URL路徑),如果服務提供者發生錯誤了以後那麼開始對服務提供者進行斷路,返回給服務消費者的時候就會調用服務消費者service接口中當前方法的實現類中的實現方法,這個實現方法的返回值就是服務提供者返回給服務消費者的標誌。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章