SPRING CLOUD微服務DEMO-下篇


1 Hystix

1.1 簡介

Hystix是Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程服務、第三方庫,防止出現級聯失敗。

我感覺難以解釋清楚,鑒於接下來的demo項目基本不會用這個模塊,就過一遍代碼算了。

1.2 配置並測試

模擬一下熔斷:一旦請求的接口超過1s沒有響應就不再繼續請求,開始執行實現定義好的回滾函數,返回一個提示信息。
注意:Ribbon的重試機制和Hystrix的熔斷機制有一定關系。

1.2.1 引入依賴

往user-consume即服務調用者的pom文件里面添加hystrix依賴。

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

1.2.2 開啟熔斷

在UserConsumeApplication中添加@EnableCircuitBreaker

可以看到注解有點多了,可以用@SpringCloudApplication代替上面三個注解

//@EnableDiscoveryClient // 開啟EurekaClient功能
//@EnableCircuitBreaker  //開啟熔斷
//@SpringBootApplication
@SpringCloudApplication   //三合一注解

1.2.3 改造consume

改造consume即消費者的調用方法,記錄調用接口耗費的時間,編寫調用超時的回滾方法。

@HystrixCommand(fallbackMethod="queryUserByIdFallback"):聲明一個失敗回滾處理函數queryUserByIdFallback,當queryUserById執行超時(默認是1000毫秒),就會執行fallback函數,返回錯誤提示。

@Component
public class UserDao {
    @Autowired
    private RestTemplate restTemplate;

    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

    @HystrixCommand(fallbackMethod = "queryUserByIdFallback")
    public User queryUserById(Long id){
        long begin = System.currentTimeMillis();
        String url = "http://user-service/user/" + id;
        User user = this.restTemplate.getForObject(url, User.class);
        long end = System.currentTimeMillis();
        // 記錄訪問用時:
        logger.info("訪問用時:{}", end - begin);
        return user;
    }

    public User queryUserByIdFallback(Long id){
        User user = new User();
        user.setId(id);
        user.setUsername("用戶信息查詢出現異常!");
        return user;
    }
}
@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        ids.forEach(id -> {
            // 我們測試多次查詢,
            users.add(this.userDao.queryUserById(id));
        });
        return users;
    }
}

1.2.4 改造service

即改造服務提供者,將方法隨機延遲一段時間,模擬超時。

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id)  throws InterruptedException {
        // 為了演示超時現象,我們在這里然線程休眠,時間隨機 0~2000毫秒
        Thread.sleep(new Random().nextInt(2000));
        return this.userMapper.selectByPrimaryKey(id);
    }
}

1.2.5 結果

很明顯,超過1000ms的請求被回滾方法處理掉了。

1.2.6 設置Hystrix超時時間

之前我們設置的Robbin的ReadTimeout是1000ms,即請求一個接口超過1000ms未響應就請求另一個有相同功能的接口而Hystrix的超時時間默認也是1000ms,觀察現象可知,先觸發了熔斷。但這樣是不合理的,明明可以請求另一個接口得到結果,結果觸發了熔斷,返回的是找不到結果。
所以注意一點:Hystrix的超時時間一定要大於Robbin的重試時間。

hystrix:
  command:
  	default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 設置hystrix的超時時間為6000ms

2. Feign

2.1 簡介

Feign可以把Rest的請求進行隱藏,偽裝成類似SpringMVC的Controller一樣。你不用再自己拼接url,拼接參數等等操作,一切都交給Feign去做。

2.2 使用Feign

Feign的引入和配置全部都在consume中進行

2.2.1 引入依賴

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

2.2.2 UserConsumeApplication添加注解

@EnableFeignClients

2.2.3 編寫UserClient接口

在項目中添加client包,下面新建UserClient接口

@FeignClient("user-service")
public interface UserClient {
    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
  • 首先這是一個接口,Feign會通過動態代理,幫我們生成實現類。這點跟mybatis的mapper很像
  • @FeignClient,聲明這是一個Feign客戶端,類似@Mapper注解。同時通過value屬性指定服務名稱
  • 接口中的定義方法,完全采用SpringMVC的注解,Feign會根據注解幫我們生成URL,並訪問獲取結果

2.2.4 使用UserClient請求數據

改造UserService,改為使用UserClient請求數據

@Service
public class UserService {
    //@Autowired
    //private UserDao userDao;

    @Autowired
    private UserClient userClient;    //注入UserClient

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        ids.forEach(id -> {
            //使用UserClient請求接口
            users.add(userClient.queryUserById(id));
            //users.add(this.userDao.queryUserById(id));
        });
        return users;
    }
}

2.3 負載均衡

Feign是對請求的封裝,本身集成了Ribbon負載均衡。可以對其進行配置,和單獨使用Robbin的寫法一樣

user-service:
  ribbon:
    ConnectTimeout: 250 # 連接超時時間(ms)
    ReadTimeout: 1000 # 通信超時時間(ms)
    OkToRetryOnAllOperations: true # 是否對所有操作重試
    MaxAutoRetriesNextServer: 1 # 同一服務不同實例的重試次數
    MaxAutoRetries: 1 # 同一實例的重試次數

2.4 Hystrix支持

Feign也集成了Hystrix。

需要配置以開啟Hystrix

feign:
  hystrix:
    enabled: true # 開啟Feign的熔斷功能

回滾方法不像之前那樣寫了,要專門定義一個類。

@Component
public class UserFeignClientFallback implements UserClient {
    @Override
    public User queryUserById(Long id) {
        User user = new User();
        user.setId(id);
        user.setUsername("用戶查詢出現異常");
        return user;
    }
}

在UserClient接口上配置回滾類

@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}

2.5.請求壓縮

Spring Cloud Feign 支持對請求和響應進行GZIP壓縮,以減少通信過程中的性能損耗。通過下面的參數即可開啟請求與響應的壓縮功能:

feign:
  compression:
    request:
      enabled: true # 開啟請求壓縮
    response:
      enabled: true # 開啟響應壓縮

同時,我們也可以對請求的數據類型,以及觸發壓縮的大小下限進行設置:

feign:
  compression:
    request:
      enabled: true # 開啟請求壓縮
      mime-types: text/html,application/xml,application/json # 設置壓縮的數據類型
      min-request-size: 2048 # 設置觸發壓縮的大小下限

注:上面的數據類型、壓縮大小下限均為默認值。

3. Zuul網關

3.1 簡介

Zuul是Netflix開源的微服務網關,它可以和Eureka、Ribbon和Hystrix等組件配合使用。核心是一系列的過濾器,完成身份認證與權限管理動態路由、壓力測試等功能。

工作流程如下所示

不管是來自於客戶端(PC或移動端)的請求,還是服務內部調用。一切對服務的請求都會經過Zuul這個網關,然后再由網關來實現 鑒權、動態路由等等操作。Zuul就是我們服務的統一入口。

3.2 搭建

3.2.1 建一個新的module

其他步驟前面已經說過,選擇模塊時注意選擇Zuul即可。

3.2.2 引入依賴

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

3.2.3 添加注解

@SpringBootApplication
@EnableZuulProxy        //開啟zuul網關
@EnableDiscoveryClient  //開啟Eureka客戶端發現功能
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

3.3 路由功能

測試之前先把之前添加的user-service的隨機延遲給注釋掉

Zuul的路由功能:

  • 配置
server:
  port: 10010 #服務端口
spring:
  application:
    name: api-gateway #指定服務名

eureka:
  client:
    registry-fetch-interval-seconds: 5 # 獲取服務列表的周期:5s
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1

zuul:
  prefix: /api # 添加路由前綴
  routes:
    user-service: # 路由id,一般與服務名相同
      path: /user-service/** # 映射路徑
      serviceId: user-service

  • 解釋:Zuul從Eureka中拉取服務列表,如果有人請求/api/user-service/**,將請求轉發給服務列表中的

    user-service服務。

坑爹的一點:之前測試的時候Zuul一直報找不到user-service服務的錯誤,找了一段時間后來自己好了。

沒有再復現出來,不知道為啥。如果報錯了,可以嘗試着用maven的reimport,再等等試試。

  • 由於路由id一般與服務名相同,因此zuul提供了一種簡化配置,與上面實現相同功能
zuul:
  prefix: /api # 添加路由前綴
  routes:
    user-service: /user-service/** # 這里是映射路徑

訪問http://localhost:10010/api/user-service/user/20即可得到結果。

3.4 過濾功能

3.4.1 ZuulFilter

ZuulFilter是過濾器的頂級父類,我們自己實現的過濾器都要繼承自它。我們看看其中重要的四個方法:

public abstract ZuulFilter implements IZuulFilter{
    abstract public String filterType();
    abstract public int filterOrder();
    boolean shouldFilter();
    Object run() throws ZuulException;
}
  • shouldFilter:返回布爾值,判斷過濾器是否需要執行。true表示執行,false反之。
  • run:表示具體的過濾邏輯。
  • filterType:返回字符串,表示本過濾器的類型
    • pre:請求在被路由之前執行。
    • routing:在路由請求時調用
    • post:在routing和errror過濾器之后調用
    • error:處理請求時發生錯誤調用
  • filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先級越高。

3.4.2 生命周期

  • 正常流程:
    • 請求到達首先會經過pre類型過濾器,而后到達routing類型,進行路由,請求就到達真正的服務提供者,執行請求,返回結果后,會到達post過濾器。而后返回響應。
  • 異常流程:
    • 整個過程中,pre或者routing過濾器出現異常,都會直接進入error過濾器,再error處理完畢后,會將請求交給post過濾器,最后返回給用戶。
    • 如果是error過濾器自己出現異常,最終也會進入POST過濾器,而后返回。
    • 如果是post過濾器出現異常,會跳轉到error過濾器,但是與pre和routing不同的時,請求不會再到達post過濾器了。

3.4.3 使用場景

  • 請求鑒權:一般放在pre類型,如果發現沒有訪問權限,直接就攔截了
  • 異常處理:一般會在error類型和post類型過濾器中結合來處理。
  • 服務調用時長統計:pre和post結合使用。

3.4.4 模擬登錄校驗

添加登錄過濾器

@Component
public class LoginFilter extends ZuulFilter {
    @Override
    public String filterType() {
        // 登錄校驗,肯定是在前置攔截
        return "pre";
    }
    @Override
    public int filterOrder() {
        // 順序設置為1
        return 1;
    }
    @Override
    public boolean shouldFilter() {
        // 返回true,代表過濾器生效。
        return true;
    }
    @Override
    public Object run() {
        // 登錄校驗邏輯。
        // 1)獲取Zuul提供的請求上下文對象
        RequestContext ctx = RequestContext.getCurrentContext();
        // 2) 從上下文中獲取request對象
        HttpServletRequest req = ctx.getRequest();
        // 3) 從請求中獲取token
        String token = req.getParameter("access-token");
        // 4) 判斷
        if(token == null || "".equals(token.trim())){
            // 沒有token,登錄校驗失敗,攔截
            ctx.setSendZuulResponse(false);
            // 返回401狀態碼。也可以考慮重定向到登錄頁。
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        // 校驗通過,可以考慮把用戶信息放入上下文,繼續向后執行
        return null;
    }
}

3.5 負載均衡和熔斷功能

Zuul內部集成了Ribbon負載均衡和Hystrix熔斷器,需要配置

zuul:
  retryable: true
ribbon:
  ConnectTimeout: 250 # 連接超時時間(ms)
  ReadTimeout: 2000 # 通信超時時間(ms)
  OkToRetryOnAllOperations: true # 是否對所有操作重試
  MaxAutoRetriesNextServer: 2 # 同一服務不同實例的重試次數
  MaxAutoRetries: 1 # 同一實例的重試次數
hystrix:
  command:
  	default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 熔斷超時時長:6000ms

總結

一直在想着如何配置,如何實現,容易犯一葉障目不見泰山的毛病,下面就概括一下。

Eureka 注冊中心

服務提供者在里面注冊,服務消費者拉取服務列表,根據服務列表去發請求並獲取數據。

這其中涉及到最重要的一個問題:如何保證服務列表里面的服務是有效的

  • 對於提供者,這個問題是:你怎么才能知道我什么時候可用,什么時候不可用?

Eureka和提供者就形成了約定:提供者准備好后在Eureka里面注冊,Eureka知道提供者可用了。注冊后提供者每隔一段時間向Eureka發個消息說我還活着,Eureka就知道提供者依舊可用。Eureka每隔一段時間就會掃描整個服務列表,把很長時間沒報告過的服務剔除出去。這樣注冊中心的服務列表就能始終更新,始終可用。

  • 對於消費者,這個問題是:我怎么知道你不行了?

消費者每隔一段時間就從注冊中心拉取服務列表,注冊中心和服務提供者的約定保證了這個列表可以信任,我就根據這個列表發出請求,得到數據。

注意上面的三個時間段,這都是可以配置的,怎么配置就根據實際情況來了。

Robbin 負載均衡

服務的消費者在拉取服務列表后,針對同一業務可能有多個可選的服務提供者。

如果一直使用其中一個,那就是傳說中的"一核有難,八核圍觀",表現差勁,浪費資源,這就需要負載均衡了。

Ribbon提供了諸如隨機、輪詢等多種策略可選。

它還提供了重試機制:選擇了一個服務后,如果這個服務壞了(畢竟每隔一段時間才拉取服務列表,服務列表也是每隔一段時間才更新),或者說反應太慢,到了一定時間就考慮再換一個服務。

Hystrix 熔斷機制

為了避免一個模塊的錯誤拖垮整個系統,因此將其隔離起來。

不要讓一顆老鼠屎壞了一鍋湯?這個機制我不太能講清楚。

但是注意它和Ribbon的聯系,一個服務沒有反應,應該先用Ribbon進行重試,而不是先熔斷。然后這又引出來一個問題,一個錯誤如果每次都被重試機制掩蓋了,那系統還會被拖垮嗎?還是說這個業務的服務都壞了,試來試去找不到一個好用的,才觸發熔斷?想不清楚,真遇到了再說吧。

Feign 請求封裝

封裝了RestTemplate,讓請求的編碼更簡單。

請求必然涉及負載均衡和熔斷問題,所以Feign里面也可以配置Robbin和Hystrix。

但不光如此,Feign還提供了請求壓縮等小功能。

Zuul 網關

微服務提供者的接口是暴露在外的,要是誰都能用那就亂套了。

就像學校的器材室有各種器材,但是誰都能自由取用就亂了。因此我們需要一個管理器材的老師,拿器材的人來了,先判斷他有沒有老師的批准,再根據老師的批准決定他能拿什么器材,最后把他帶到對應的器材室拿相應的器材。

Zuul就像是這個老師,負責鑒權(判斷一個人能不能拿器材,能拿什么器材)、路由(領到地方拿器材)。

Zuul雖然不發請求,但是對請求有很強的干預能力,所以它也可以配置負載均衡和熔斷。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM