1. 程式人生 > 其它 >Spring Cloud 學習-中

Spring Cloud 學習-中

1.Spring Cloud 學習

  • Feign 宣告式服務呼叫
  • Hystrix 熔斷器
  • Gateway 閘道器

2.Feign

2.1-Feign-概述

• Feign 是一個宣告式的 REST 客戶端,它用了基於介面的註解方式,很方便實現客戶端配置。
• Feign 最初由 Netflix 公司提供,但不支援SpringMVC註解,後由 SpringCloud 對其封裝,支援了SpringMVC注
解,讓使用者更易於接受

2.2-Feign-快速入門

  1. 在消費端引入 open-feign 依賴
   <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 編寫Feign呼叫介面
package com.itheima.consumer.feign;


import com.itheima.consumer.config.FeignLogConfig;
import com.itheima.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 *
 * feign宣告式介面。發起遠端呼叫的。
 *
 String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
 Goods goods = restTemplate.getForObject(url, Goods.class);
 *
 * 1. 定義介面
 * 2. 介面上添加註解 @FeignClient,設定value屬性為 服務提供者的 應用名稱
 * 3. 編寫呼叫介面,介面的宣告規則 和 提供方介面保持一致。
 * 4. 注入該介面物件,呼叫介面方法完成遠端呼叫
 */
@FeignClient(value = "FEIGN-PROVIDER")
public interface GoodsFeignClient {
    @GetMapping("/goods/findOne/{id}")
    public Goods findGoodsById(@PathVariable("id") int id);
}

OrderController

package com.itheima.consumer.controller;


import com.itheima.consumer.domain.Goods;
import com.itheima.consumer.feign.GoodsFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
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;

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private GoodsFeignClient goodsFeignClient;

    @GetMapping("/goods/{id}")
    public Goods findGoodsById(@PathVariable("id") int id){

        /*
        String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
        // 3. 呼叫方法
        Goods goods = restTemplate.getForObject(url, Goods.class);

        return goods;*/

        Goods goods = goodsFeignClient.findGoodsById(id);

        return goods;
    }


}

goodsFeignClient報紅,不影響使用

  1. 在啟動類 新增 @EnableFeignClients 註解,開啟Feign功能
package com.itheima.consumer;

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

@EnableDiscoveryClient // 啟用DiscoveryClient
@EnableEurekaClient
@SpringBootApplication

@EnableFeignClients //開啟Feign的功能
public class ConsumerApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class,args);
    }
}

  1. 測試呼叫

2.3-Feign超時配置

• Feign 底層依賴於 Ribbon 實現負載均衡和遠端呼叫。
• Ribbon預設1秒超時。
• 超時配置:

feign-consumer application.yml

# 設定Ribbon的超時時間
ribbon:
  ConnectTimeout: 1000 # 連線超時時間 預設1s  預設單位毫秒
  ReadTimeout: 3000 # 邏輯處理的超時時間 預設1s 預設單位毫秒

2.4-Feign-日誌記錄

• Feign 只能記錄 debug 級別的日誌資訊。

feign-consumer application.yml

# 設定當前的日誌級別 debug,feign只支援記錄debug級別的日誌
logging:
  level:
    com.itheima: debug

• 定義Feign日誌級別Bean

FeignLogConfig

package com.itheima.consumer.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignLogConfig {
    /*
        NONE,不記錄
        BASIC,記錄基本的請求行,響應狀態碼資料
        HEADERS,記錄基本的請求行,響應狀態碼資料,記錄響應頭資訊
        FULL;記錄完成的請求 響應資料
     */
    @Bean
    public Logger.Level level(){
        return Logger.Level.FULL;
    }
}

• 啟用該Bean:

package com.itheima.consumer.feign;


import com.itheima.consumer.config.FeignLogConfig;
import com.itheima.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 *
 * feign宣告式介面。發起遠端呼叫的。
 *
 String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
 Goods goods = restTemplate.getForObject(url, Goods.class);
 *
 * 1. 定義介面
 * 2. 介面上添加註解 @FeignClient,設定value屬性為 服務提供者的 應用名稱
 * 3. 編寫呼叫介面,介面的宣告規則 和 提供方介面保持一致。
 * 4. 注入該介面物件,呼叫介面方法完成遠端呼叫
 */

@FeignClient(value = "FEIGN-PROVIDER",configuration = FeignLogConfig.class)
public interface GoodsFeignClient {

    @GetMapping("/goods/findOne/{id}")
    public Goods findGoodsById(@PathVariable("id") int id);

}

3.Hystrix

3.1-Hystrix-概述

• Hystix 是 Netflix 開源的一個延遲和容錯庫,用於隔離訪問遠端服務、第三方庫,防止出現級聯失敗(雪崩)。
• 雪崩:一個服務失敗,導致整條鏈路的服務都失敗的情形

Hystix 主要功能
• 隔離

​ 執行緒池隔離
​ 訊號量隔離

• 降級:異常,超時
• 熔斷
• 限流

3.2-Hystrix-降級

3.2.1-提供方降級

Hystix 降級:當服務發生異常或呼叫超時,返回預設資料

服務提供方降級

  1. 在服務提供方,引入 hystrix 依賴

            <!-- hystrix -->
             <dependency>
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
             </dependency>
    
  2. 定義降級方法

 /**
     * 定義降級方法:
     *  1. 方法的返回值需要和原方法一樣
     *  2. 方法的引數需要和原方法一樣
     */
    public Goods findOne_fallback(int id){
        Goods goods = new Goods();
        goods.setTitle("降級了~~~");

        return goods;
    }
  1. 使用 @HystrixCommand 註解配置降級方法
/**
     * 降級:
     *  1. 出現異常
     *  2. 服務呼叫超時
     *      * 預設1s超時
     *
     *  @HystrixCommand(fallbackMethod = "findOne_fallback")
     *      fallbackMethod:指定降級後呼叫的方法名稱
     */
    @GetMapping("/findOne/{id}")
    @HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
            //設定Hystrix的超時時間,預設1s
 @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public Goods findOne(@PathVariable("id") int id){

        //1.造個異常
        int i = 3/0;
        try {
            //2. 休眠2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Goods goods = goodsService.findOne(id);

        goods.setTitle(goods.getTitle() + ":" + port);//將埠號,設定到了 商品標題上
        return goods;
    }
  1. 在啟動類上開啟Hystrix功能:@EnableCircuitBreaker
package com.itheima.provider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * 啟動類
 */
@EnableEurekaClient //該註解 在新版本中可以省略
@SpringBootApplication
@EnableCircuitBreaker // 開啟Hystrix功能
public class ProviderApp {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApp.class,args);
    }
}

3.2.2-消費方降級

  1. feign 元件已經集成了 hystrix 元件。

  2. 定義feign 呼叫介面實現類,複寫方法,即 降級方法

    GoodsFeignClientFallback

package com.itheima.consumer.feign;

import com.itheima.consumer.domain.Goods;
import org.springframework.stereotype.Component;

/**
 * Feign 客戶端的降級處理類
 * 1. 定義類 實現 Feign 客戶端介面
 * 2. 使用@Component註解將該類的Bean加入SpringIOC容器
 */
@Component
public class GoodsFeignClientFallback implements GoodsFeignClient {
    @Override
    public Goods findGoodsById(int id) {
        Goods goods = new Goods();
        goods.setTitle("又被降級了~~~");
        return goods;
    }
}

  1. 在 @FeignClient 註解中使用 fallback 屬性設定降級處理類。

    GoodsFeignClient

package com.itheima.consumer.feign;


import com.itheima.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "HYSTRIX-PROVIDER",fallback = GoodsFeignClientFallback.class)
public interface GoodsFeignClient {
    @GetMapping("/goods/findOne/{id}")
    public Goods findGoodsById(@PathVariable("id") int id);

}

  1. 配置開啟 feign.hystrix.enabled = true

    application.yml

# 開啟feign對hystrix的支援
feign:
  hystrix:
    enabled: true

3.3-Hystrix-熔斷

3.3.1-熔斷-概念

• Hystrix 熔斷機制,用於監控微服務呼叫情況,當失敗的情況達到預定的閾值(5秒失敗20次),會開啟
斷路器,拒絕所有請求,直到服務恢復正常為止。

斷路器三種狀態:開啟、半開、關閉、

3.3.2-熔斷-程式碼演示

修改服務提供方的方法,演示熔斷機制

熔斷配置

• circuitBreaker.sleepWindowInMilliseconds:監控時間
• circuitBreaker.requestVolumeThreshold:失敗次數
• circuitBreaker.errorThresholdPercentage:失敗率

GoodsController

package com.itheima.provider.controller;

import com.itheima.provider.domain.Goods;
import com.itheima.provider.service.GoodsService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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 java.util.Date;

/**
 * Goods Controller 服務提供方
 */

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    @Value("${server.port}")
    private int port;

    /**
     * 降級:
     *  1. 出現異常
     *  2. 服務呼叫超時
     *      * 預設1s超時
     *
     *  @HystrixCommand(fallbackMethod = "findOne_fallback")
     *      fallbackMethod:指定降級後呼叫的方法名稱
     */

 @GetMapping("/findOne/{id}")
@HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
         //設定Hystrix的超時時間,預設1s
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
            //監控時間 預設5000 毫秒
@HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"),
            //失敗次數。預設20次
 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "20"),
            //失敗率 預設50%
 @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50") })
    public Goods findOne(@PathVariable("id") int id){
        //如果id == 1 ,則出現異常,id != 1 則正常訪問
        if(id == 1){
            //1.造個異常
            int i = 3/0;
        }
        /*try {
            //2. 休眠2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        Goods goods = goodsService.findOne(id);

        goods.setTitle(goods.getTitle() + ":" + port);//將埠號,設定到了 商品標題上
        return goods;
    }


    /**
     * 定義降級方法:
     *  1. 方法的返回值需要和原方法一樣
     *  2. 方法的引數需要和原方法一樣
     */
    public Goods findOne_fallback(int id){
        Goods goods = new Goods();
        goods.setTitle("降級了~~~");

        return goods;
    }

}

3.3.3-熔斷監控

• Hystrix 提供了 Hystrix-dashboard 功能,用於實時監控微服務執行狀態。
• 但是Hystrix-dashboard只能監控一個微服務。
• Netflix 還提供了 Turbine ,進行聚合監控。

熔斷器監控安裝 請檢視Turbine搭建步驟.md

4.Gateway

4.1-Gateway-概述

  • 閘道器旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。

  • 在微服務架構中,不同的微服務可以有不同的網路地址,各個微服務之間通過互相呼叫完成使用者請求,客戶端可能通過呼叫N個微服務的介面完成一個使用者請求。

  • 存在的問題:

	1.客戶端多次請求不同的微服務,增加客戶端的複雜性
	2.認證複雜,每個服務都要進行認證
	3.http請求不同服務次數增加,效能不高
  • 閘道器就是系統的入口,封裝了應用程式的內部結構,為客戶端提供統一服務,一些與業務本身功能無關的公共邏輯可以在這裡實現,諸如認證、鑑權、監控、快取、負載均衡、流量管控、路由轉發等

  • 在目前的閘道器解決方案裡,有Nginx+ Lua、Netflix Zuul 、Spring Cloud Gateway等等

4.2-Gateway-快速入門

  1. 搭建閘道器模組

    建立api-gateway-server模組

  2. 引入依賴:starter-gateway

     <dependencies>
            <!--引入gateway 閘道器-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <!-- eureka-client -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
        </dependencies>
    
  3. 編寫啟動類

    package com.itheima.gateway;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    public class ApiGatewayApp {
    
        public static void main(String[] args) {
            SpringApplication.run(ApiGatewayApp.class,args);
        }
    
    }
    
    
  4. 編寫配置檔案

    application.yml

    server:
      port: 80
    
    spring:
      application:
        name: api-gateway-server
    
      cloud:
        # 閘道器配置
        gateway:
          # 路由配置:轉發規則
          routes: #集合。
          # id: 唯一標識。預設是一個UUID
          # uri: 轉發路徑
          # predicates: 條件,用於請求閘道器路徑的匹配規則
    
    
          - id: gateway-provider
            uri: http://localhost:8001/
            predicates:
            - Path=/goods/**
    
  5. 啟動測試

4.3-Gateway-靜態路由

application.yml 中的uri是寫死的,就是靜態路由

server:
  port: 80

spring:
  application:
    name: api-gateway-server

  cloud:
    # 閘道器配置
    gateway:
      # 路由配置:轉發規則
      routes: #集合。
      # id: 唯一標識。預設是一個UUID
      # uri: 轉發路徑
      # predicates: 條件,用於請求閘道器路徑的匹配規則
      # filters:配置區域性過濾器的
      - id: gateway-provider
        # 靜態路由
        uri: http://localhost:8001/
        predicates:
        - Path=/goods/**

4.4-Gateway-動態路由

啟動類新增@EnableEurekaClient(新版本不加也可以)

package com.itheima.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {

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

}

引入eureka-client配置

application.yml 中修改uri屬性:uri: lb://服務名稱

server:
  port: 80

spring:
  application:
    name: api-gateway-server

  cloud:
    # 閘道器配置
    gateway:
      # 路由配置:轉發規則
      routes: #集合。
      # id: 唯一標識。預設是一個UUID
      # uri: 轉發路徑
      # predicates: 條件,用於請求閘道器路徑的匹配規則
      # filters:配置區域性過濾器的

      - id: gateway-provider
      	# 靜態路由
        # uri: http://localhost:8001/
        # 動態路由
        uri: lb://GATEWAY-PROVIDER
        predicates:
        - Path=/goods/**

4.5-Gateway-微服務名稱配置

application.yml中配置微服務名稱配置

      # 微服務名稱配置
      discovery:
        locator:
          enabled: true # 設定為true 請求路徑前可以新增微服務名稱
          lower-case-service-id: true # 允許為小寫

4.6-Gateway-過濾器

4.6.1-過濾器-概述

  • Gateway 支援過濾器功能,對請求或響應進行攔截,完成一些通用操作。

  • Gateway 提供兩種過濾器方式:“pre”和“post”

    pre 過濾器,在轉發之前執行,可以做引數校驗、許可權校驗、流量監控、日誌輸出、協議轉換等。
    post 過濾器,在響應之前執行,可以做響應內容、響應頭的修改,日誌的輸出,流量監控等。

  • Gateway 還提供了兩種型別過濾器
    GatewayFilter:區域性過濾器,針對單個路由
    GlobalFilter :全域性過濾器,針對所有路由

4.6.2-區域性過濾器

  • GatewayFilter 區域性過濾器,是針對單個路由的過濾器。
  • 在Spring Cloud Gateway 元件中提供了大量內建的區域性過濾器,對請求和響應做過濾操作。
  • 遵循約定大於配置的思想,只需要在配置檔案配置區域性過濾器名稱,併為其指定對應的值,就可以讓其生效。

具體配置參見gateway內建過濾器工廠.md

測試配置

api-gateway-server application.yml

server:
  port: 80
spring:
  application:
    name: api-gateway-server
  cloud:
    # 閘道器配置
    gateway:
      # 路由配置:轉發規則
      routes: #集合。
      # id: 唯一標識。預設是一個UUID
      # uri: 轉發路徑
      # predicates: 條件,用於請求閘道器路徑的匹配規則
      # filters:配置區域性過濾器的

      - id: gateway-provider
        # 靜態路由
        # uri: http://localhost:8001/
        # 動態路由
        uri: lb://GATEWAY-PROVIDER
        predicates:
        - Path=/goods/**
        filters:
        - AddRequestParameter=username,zhangsan

gateway-provider模組中GoodsController中的findOne新增username引數

 public Goods findOne(@PathVariable("id") int id,String username){

        System.out.println(username);

        //如果id == 1 ,則出現異常,id != 1 則正常訪問
        if(id == 1){
            //1.造個異常
            int i = 3/0;
        }

        /*try {
            //2. 休眠2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        Goods goods = goodsService.findOne(id);

        goods.setTitle(goods.getTitle() + ":" + port);//將埠號,設定到了 商品標題上
        return goods;
    }

4.6.3-全域性過濾器

  • GlobalFilter 全域性過濾器,不需要在配置檔案中配置,系統初始化時載入,並作用在每個路由上。

  • Spring Cloud Gateway 核心的功能也是通過內建的全域性過濾器來完成。

  • 自定義全域性過濾器步驟:

    1. 定義類實現 GlobalFilter 和 Ordered介面
    2. 複寫方法
    3. 完成邏輯處理

MyFilter

package com.itheima.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class MyFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        System.out.println("自定義全域性過濾器執行了~~~");

        return chain.filter(exchange);//放行
    }

    /**
     * 過濾器排序
     * @return 數值越小 越先執行
     */
    @Override
    public int getOrder() {
        return 0;
    }
}