四. SpringCloud負載均衡與呼叫

MPolaris發表於2021-02-04

1. Ribbon概述

1.1 Ribbon是什麼

SpringCloud Ribbon是基於Netflix Ribbon實現的一套客戶端,是負載均衡的工具。

Ribbon是Netflix釋出的開源專案,主要功能是提供客戶端的軟體複雜均衡演算法和服務呼叫。Ribbon客戶端元件提供一系列完整的配置項如連線超時、重試等。簡單的說,就是在配置檔案中列出Load Balancer(負載均衡簡稱LB)後面所有的機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連線等)去連線這些機器。也可以使用Ribbon實現自定義的負載均衡演算法。

1.2 Ribbon能做什麼

主要是負載均衡(LB):所謂負載均衡,簡單的說就是將使用者的請求平攤的分配到多個服務上,從而達到系統的HA(High Available高可用),常見的負載均衡有軟體Nginx、LVS,硬體F5等。

Ribbon本地負載均衡客戶端和Nginx服務端負載均衡的區別:

  • Nginx是伺服器負載均衡,客戶端所有請求都會交給Nginx,然後由Nginx實現轉發請求,即負載均衡是由服務端實現的。
  • Ribbon是本地負載均衡,在呼叫微服務介面時候,會在註冊中心上獲取註冊資訊服務列表之後快取到JVM本地,從而在本地實現RPC遠端服務呼叫技術。

負載均衡又分為兩類,分別可以對應於Nginx和Ribbon:

  • 集中式LB:即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬體,如F5,也可以是軟體,如Nginx),由該設施負責把訪問請求通過某種策略轉發至服務的提供方。
  • 程式內LB:將LB邏輯整合到消費方,消費方從服務註冊中心獲知有哪些地址可用,然後自己再從這些地址中選擇出一個合適的伺服器,Ribbon就屬於程式內LB,它只是一個類庫,整合於消費方程式,消費方通過它來獲取到服務提供方的地址。

Ribbon實際上就是負載均衡 + RestTemplate呼叫

2. Ribbon使用案例

2.1 架構說明

Ribbon其實就是一個負載均衡的客戶端元件,他可以和其他所需請求的客戶端結合使用,和eureka結合只是其中的一個例項。Ribbon在工作的時候分兩步:

  • 先選擇EurekaServer,優先選擇同一個區域內負載較少的Server

  • 根據使用者指定的策略,從Server取到的服務註冊列表中選擇一個地址

其中Ribbon提供了多種的負載均衡策略,如輪詢、隨機和根據響應時間加強等。

image-20210128235245749
2.2 pom.xml

在POM檔案中我們引入瞭如下依賴:

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

點開該依賴的原始碼,我們發現事實上該依賴內部已經引入了Ribbon,其引入Ribbon的原始碼如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.1.RELEASE</version>
    <scope>compile</scope>
</dependency>

我們在Maven的依賴中也可以看到,在引入 spring-cloud-starter-netflix-eureka-client 的同時我們就已經引入了 **spring-cloud-starter-netflix-ribbon **,所以我們沒必要單獨新增Ribbon的依賴。

image-20210128231128616
1.3 RestTemplate使用

RestTemplate官方說明可以在RestTemplate官方API檢視,下面簡要說明其主要方法

  • getForObject方法:返回物件為響應體資料轉化成的物件,基本上可以理解為Json物件。
  • getForEntity方法:返回物件為ResponseEntity物件,包含了響應中的一些重要資訊,比如響應頭、響應狀態碼、響應體等。
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {
    @Resource
    private RestTemplate restTemplate;

    // private static final String PAYMENT_URL = "http://localhost:8001";
    private static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";

    @GetMapping("/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_SRV
                + "/payment/get/"
                + id, CommonResult.class);
    }

    @GetMapping("/payment/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity =
                restTemplate.getForEntity(PAYMENT_SRV
                 + "/payment/get/" + id, CommonResult.class);
        if (entity.getStatusCode().is2xxSuccessful()) {
            log.info("===> " + entity.getStatusCode()
                  + "\t" + entity.getHeaders());
            return entity.getBody(); //返回請求體
        } else {
            return new CommonResult<>(444, "操作失敗");
        }
    }
}

在後臺控制檯也輸出了狀態碼和請求頭的如下日誌:

===> 200 OK	[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Thu, 28 Jan 2021 15:44:50 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]

3. Ribbon核心元件IRule介面

3.1 IRule理解

它可以根據特定演算法從服務列表中選取一個要訪問的服務

IRule是一個介面,其原始碼如下:

package com.netflix.loadbalancer;

/**
 * Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
 * as a Strategy for loadbalacing. Well known loadbalancing strategies include
 * Round Robin, Response Time based etc.
 * 
 * @author stonse
 * 
 */
public interface IRule{
    /*
     * choose one alive server from lb.allServers or
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none
     *  server is available 
     */

    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

以下是IRule介面的部分實現,這些實現分別對應了若干負載均衡演算法

image-20210128235959015

以下簡要說明7種主要的負載均衡演算法,這些負載均衡演算法均是抽象類com.netflix.loadbalancer.AbstractLoadBalancerRule 的實現,而給抽象類實現了IRule介面:

  • com.netflix.loadbalancer.RoundRobinRule:輪詢,為預設的負載均衡演算法
  • com.netflix.loadbalancer.RandomRule:隨機
  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule(輪詢)的策略獲取服務,如果獲取服務失敗則在指定時間內進行重試,獲取可用的服務
  • com.netflix.loadbalancer.WeightedResponseTimeRule:對RoundRobinRule的擴充套件,響應速度越快的例項選擇權重越大,越容易被選擇。
  • com.netflix.loadbalancer.BestAvailableRule:先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,然後選擇一個併發量最小的服務
  • com.netflix.loadbalancer.AvailabilityFilteringRule:先過濾掉故障例項,再選擇併發較小的例項
  • com.netflix.loadbalancer.ZoneAvoidanceRule:複合判斷Server所在區域的效能和Server的可用性選擇伺服器
3.2 如何替換負載均衡演算法

服務消費者80新增輪詢演算法配置類

首先我們應該明確是服務消費方採用輪詢演算法來訪問同一服務提供方的不同微服務例項,所以我們應該在服務消費方80方的微服務中新增輪詢演算法配置類。

在新增配置類時,有必須要注意的點,就是官方文件明確給出了警告:這個自定義的輪詢演算法配置類不能放在@ComponentScan註解所掃描的當前包下以及子包下,否則自定義的這個配置類就會被所有Ribbon客戶端所共享,就達不到特殊化定製的目的了。換句話說,如果這個配置類我們能夠被@ComponentScan註解掃描到,那麼訪問所有的微服務提供方的具體例項時,我們都會採取配置類中的演算法,如果要特殊化定製 - 即指定訪問某些微服務提供方時採用配置的輪詢演算法,那麼我們就應該使這個配置類讓@ComponentScan註解掃描不到,我們知道在主啟動類的@SpringBootApplication註解中,其實這個註解包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan這三個註解,所以我們寫的輪詢演算法配置類不能和主啟動類在同一個包下,所以我們需要建新的包,實現定製輪詢演算法的配置類:

package com.polaris.myrule;

/**
 * @author polaris
 */
@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule() {
        return new RandomRule(); //定義隨機負載均衡演算法
    }
}

包結構的內容如下,我們可以看到,輪詢演算法配置類在主啟動類的@ComponentScan掃描不到的包下:

image-20210129000859823

服務消費者80主啟動類中新增@RibbonClient註解

@SpringBootApplication
@EnableEurekaClient
//訪問的微服務為CLOUD-PAYMENT-SERVICE,採用配置檔案中的輪詢演算法
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class);
    }
}

測試

測試發現我們用服務消費方訪問服務提供方的微服務時,8001和8002不再交替輪詢訪問,而是隨機訪問。

4. Ribbon負載均衡演算法

4.1 預設負載均衡演算法(輪詢)原理

輪詢負載均衡演算法原理:Rest介面第幾次請求數 % 伺服器叢集總數量 = 實際呼叫伺服器位置下標, 每次服務重啟後Rest介面計數從1開始。

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE")

根據服務方的服務名,獲取其所有例項,如有以下例項:

List[0] List[1]
服務名 payment8001 payment8002
服務地址 127.0.0.1:8001 127.0.0.1:8002

這兩個例項組合成一個叢集,共2臺機器,叢集總數為2,按照輪詢負載均衡演算法原理:

  • 請求總數為1時,1 % 2 = 1,對應下標位置是1,獲得服務地址127.0.0.1:8001

  • 請求總數為2時,2 % 2 = 0,對應下標位置是0,獲得服務地址127.0.0.1:8002

  • 請求總數為3時,3 % 2 = 1,對應下標位置是1,獲得服務地址127.0.0.1:8001

  • ...

4.2 輪詢原始碼分析

com.netflix.loadbalancer.RoundRobinRule原始碼的負載均衡演算法部分分析如下(程式碼中標註了中文註釋):

package com.netflix.loadbalancer;

/**
 * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
 */
public class RoundRobinRule extends AbstractLoadBalancerRule {

   	//...

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            //獲得還活著的健康的服務例項(機器)即可達的,也就是Status為up的例項
            List<Server> reachableServers = lb.getReachableServers();
            //獲取所有服務例項,無論是死是活,只要註冊進服務中心即可
            List<Server> allServers = lb.getAllServers();
            //Status為up的服務例項數量
            int upCount = reachableServers.size();
            //所有服務例項的數量,對應上述原理分析中的伺服器叢集總數量
            int serverCount = allServers.size();

            //如果沒有可達的服務例項的話,直接報警告
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            //呼叫伺服器位置下標 = incrementAndGetModulo(伺服器叢集總數)
            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);//根據下標獲取服務例項

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }
}
4.3 自己實現輪詢負載均衡演算法

首先我們將服務註冊中心(7001/7002構成叢集)啟動,然後在服務提供方8001/8002中的Controller中新增功能,用來一會兒測試服務消費方80來輪詢訪問CLOUD-PAYMENT-SERVICE服務:

@GetMapping("/payment/lb")
public String getPaymentLB(){
	return serverPort;
}

服務提供方的這個方法就是簡單的在頁面輸出自己的埠號,也就是我們可以在頁面區分訪問的CLOUD-PAYMENT-SERVICE服務到底對應的是8001例項還是8002例項。

啟動8001/8002,將兩個服務例項註冊進服務註冊中心後,我們再改造服務消費方80服務,分為以下四步:

  • 首先我們先讓RestTemplate失去Ribbon中的負載均衡能力,取消掉@LoadBalanced註解即可:
@Configuration
public class ApplicationContextConfig {
    @Bean
//    @LoadBalanced//使用該註解賦予RestTemplate負載均衡的能力
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
//applicationContext.xml <bean id="" class="">
  • 然後編寫自己的負載均衡介面:

給介面定義了方法instances用於在服務提供方服務的所有服務例項中選擇一個具體例項。

public interface LoadBalancer {
    /**
     * 從服務列表中用負載均衡演算法選擇出具體的例項
     * @param serviceInstances 服務列表
     * @return
     */
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
  • 用輪詢負載均衡演算法實現負載均衡介面:

RoundRobinRule原始碼中用for(;;)實現的自旋鎖,這裡我們用do{} while();實現自旋鎖。

@Component
public class MyLB implements LoadBalancer {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement() {
        int current;
        int next;
        //自旋鎖
        do {
            current = this.atomicInteger.get(); //初始值為0
            next = current >= 2147483647 ? 0 : current + 1; //最大整數
        } while (!this.atomicInteger.compareAndSet(current, next));
        System.out.println("===> 訪問次數next:" + next);
        return next;
    }

    /**
     * 從服務列表中用輪詢負載均衡演算法選擇出具體的例項
     * Rest介面第幾次請求數 % 伺服器叢集總數量 = 實際呼叫伺服器位置下標
     *
     * @param serviceInstances 服務列表
     * @return
     */
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}
  • 最後我們在80服務的Controller中新增方法:
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {
    @Resource
    private RestTemplate restTemplate;

    @Resource
    private LoadBalancer loadBalancer;

    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("payment/lb")
    public String getPaymentLB() {
        //獲取服務提供方所有的服務例項
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if (instances == null || instances.size() <= 0) {
            return null;
        }
        //採用自己實現的輪詢負載均衡演算法選擇具體例項
        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();
        return restTemplate.getForObject(uri + "/payment/lb", String.class);
    }
}

在瀏覽器中輸入http://localhost/consumer/payment/lb,也就是80埠的服務消費方採用我們自己編寫的輪詢負載均衡演算法訪問CLOUD-PAYMENT-SERVICE服務的具體例項,測試成功,在服務消費方80服務的後端控制檯也輸出了的日誌。

5. OpenFeign概述

5.1 OpenFeign是什麼?

Feign是一個聲名式WebService客戶端,使用Feign能讓編寫WebService客戶端更加簡單。它的使用方法是定義一個服務介面然後在上面新增註解。Feign也支援可拔插式的編碼器和解碼器。SpringCloud對Feign進行了封裝,使其支援了Spring MVC標準註解和HttpMessageConverters。Feign可以與Eureka和Ribbon組合使用以支援負載均衡。

5.2 Feign能做什麼?

Feign旨在使編寫Java Http客戶端變得更容易。之前我們使用Ribbon + RestTemplate時,利用RestTemplate對Http請求的封裝處理,形成了一套模板化的呼叫方法。但是在實際開發中由於對服務依賴的呼叫可能不止一處,往往一個介面會被多處呼叫,所以通常都會針對每個微服務自行封裝一些客戶端類來包裝這些依賴服務的呼叫。所以Feign在此基礎上做了進一步封裝,由它來幫助定義和實現依賴服務介面的定義。在Feign的實現下,只需要建立一個介面並使用註解的方式來配置它(例如以前是DAO介面上面標註Mapper註解,現在是一個微服務介面上面標註一個Feign註解),即可完成對服務提供方的介面繫結,簡化了使用SpringCloud Ribbon時,自動封裝服務呼叫客戶端的開發量。

Feign整合了Ribbon:利用Ribbon維護服務提供方的服務列表資訊,並且通過如輪詢的演算法實現了客戶端的負載均衡。而與Ribbon不同的是,通過Feign只需要定義服務繫結介面且以宣告式的方式,優雅而簡單的實現了服務呼叫。

5.3 Feign和OpenFeign的區別

Feign已經停止維護,所以我們只需要關注OpenFeign的使用即可,我們現在學習的就是利用OpenFeign實現我們之前用的Ribbon+RestTemplate實現的功能。

Feign OpenFeign
特點 Feign是SpringCloud元件中的一個輕量級RESTful的HTTP服務客戶端,Feign內建了Ribbon,用來做客戶端的負載均衡,去呼叫服務註冊中心的服務。Feign的使用方式是:使用Feign的註解定義介面,呼叫這個介面,就可以呼叫服務註冊中心的服務。 OpenFeign是SpringCloud在Feign的基礎上支援了SpringMVC的註解,如@RequestMapping 等。OpenFeign的 @FeignClient 可以解析SpringMVC的 @RequestMapping 註解下的介面,並通過動態代理的方式產生實現類,實現類中做負載均衡並呼叫其他服務。
啟動器 spring-cloud-starter-feign spring-cloud-starter-openfeign

6. OpenFeign使用案例

6.1 介面+註解,新建Module

新建cloud-consumer-feign-order80作為服務消費方服務。

在微服務呼叫的介面上新增註解@FeignClient,注意OpenFeign在服務消費方使用

6.2 pom.xml

在POM中我們引入了OpenFeign的依賴以及Eureka客戶端的依賴

<dependencies>
    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!--eureka client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.polaris</groupId>
        <artifactId>cloud-api-common</artifactId>
        <version>${project.version}</version>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--一般基礎通用配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
6.3 yml配置檔案

由於80為服務消費方,只是呼叫服務提供方的服務,所以可以不講自己註冊到服務註冊中心。

server:
  port: 80

eureka:
  client:
    register-with-eureka: false # 客戶端就不註冊入服務註冊中心了
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
6.4 主啟動類

注意主啟動類上需要新增 @EnableFeignClients 註解,這個註解宣告80服務可以使用Feign來實現服務介面的呼叫。

@SpringBootApplication
@EnableFeignClients //啟用Feign功能
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class);
    }
}
6.5 業務類

前面就提到過,在Feign的實現下,只需要建立一個介面並使用註解的方式來配置,這是什麼意思呢,就是我們在服務消費方中的service中編寫介面,並在該介面上使用 @FeignClient 註解,這樣的話就能夠實現對服務提供方的服務呼叫,首先我們看服務提供方8001中有如下的一個服務:

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
    //... 
    @GetMapping("/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        Payment paymentById = paymentService.getPaymentById(id);
        log.info("===> payment: " + paymentById);
        if(paymentById != null) {
            return new CommonResult(200,
                                    "查詢成功,埠號:" + serverPort,paymentById);
        }
        return new CommonResult(400,"查詢失敗",null);
	}
    //... 
}

在服務消費方80中編寫如下的介面即可對服務提供方的服務進行呼叫:

@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
    @GetMapping("/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);
}

@FeignClient 註解中的value值為要呼叫的服務名稱,也就是8001/8002服務提供方註冊到Eureka註冊中心的服務名,這個註解就是告訴該介面呼叫哪個服務,而預設OpenFeign會使用輪訓的負載均衡演算法來呼叫具體的服務例項,在這個介面中我們需要使用服務提供方服務的哪個具體方法,將該方法作為介面方法寫入介面中即可。

然後編寫80服務消費方的Controller:

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderFeignController {

    @Autowired
    private PaymentFeignService paymentFeignService;

    @GetMapping("/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
        return paymentFeignService.getPaymentById(id);
    }
}
6.6 測試

我們先啟動Eureka的叢集註冊中心,然後啟動服務提供方8001/8002,再啟動服務消費方80,訪問http://localhost/consumer/payment/get/1,我們可以發現OpenFeign使用輪詢的負載均衡演算法實現了服務提供方服務介面的呼叫。

6.7 總結

簡言之就是客戶端的服務介面使用用 @FeignClient 註解根據服務名稱去呼叫服務提供方的具體服務。

7. OpenFeign超時控制

7.1 超時設定

我們們這裡故意設定超時演示出錯情況

服務提供方8001故意寫暫停程式

@GetMapping("/feign/timeout")
public String paymentFeignTimeout() {
    //暫停幾秒執行緒
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return serverPort;
}

服務消費方80service新增超時方法paymentFeignTimeout,controller新增超時介面paymentFeignTimeout

@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
    @GetMapping("/payment/feign/timeout")
    String paymentFeignTimeout();
}
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderFeignController {
    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout() {
        //openfeign底層為ribbon,客戶端一般預設等待1秒鐘
        return paymentFeignService.paymentFeignTimeout();
    }
}

測試:http://localhost/consumer/payment/feign/timeout

錯誤頁面(OpenFeign預設等待1秒鐘,超過後報錯)

image-20210203235305718
7.2 開啟OpenFeign客戶端超時控制

服務消費者80的yml配置檔案中開啟OpenFeign客戶端超時控制

server:
  port: 80

eureka:
  client:
    register-with-eureka: false # 客戶端就不註冊入服務註冊中心了
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
# 設定feign客戶端超時時間(OpenFeign預設支援ribbon)
ribbon:
# 指的是建立連線所用的時間,適用於網路狀態正常的情況下,兩端連線所用的時間
ReadTimeout: 5000
# 指的是建立連線後從伺服器讀取到可用資源所用的時間
ConnectTimeout: 5000

8. OpenFeign日誌列印

理解

Feign提供了日誌列印功能,我們可以通過配置來調整日誌級別,從而瞭解 Fegin 中 Http 請求的細節。說白了就是對Feign介面的呼叫情況進行監控和輸出。

日誌級別

  • NONE 預設的,不顯示任何日誌
  • BASIC 僅記錄請求方法,URL,響應狀態碼及執行時間
  • HEADERS 除了BASIC中定義的訊息外,還有請求和響應的頭資訊
  • FULL 除了 HEADERS 中定義的資訊外,還有請求和響應的正文及後設資料

配置日誌Bean

@Configuration
public class FeignConfig {

    /**
     * feignClient配置日誌級別
     * @return
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        // 請求和響應的頭資訊,請求和響應的正文及後設資料
        return Logger.Level.FULL;
    }
}

yml配置檔案中開啟日誌的Feign客戶端

server:
  port: 80
eureka:
  client:
    register-with-eureka: false
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000
logging:
  level:
    # feign日誌以什麼級別監控哪個介面
    com.polaris.springcloud.service.PaymentFeignService: debug

後臺日誌檢視

image-20210204000730413

相關文章