springcloud

CH_song發表於2024-11-05

SpringCloud

微服務架構

隨著網際網路的不斷髮展,軟體系統的架構也是在不斷的更新。由原先的單體架構逐漸演變成分散式系統架構,再到目前非常主流的微服務系統架構。

分散式系統架構是指將一個軟體系統分割成多個獨立的服務,並且這些服務可以在不同的計算機或伺服器上執行,並透過網路進行通訊。

微服務系統架構:本質上也屬於分散式系統架構,在微服務系統架構中,更加重視的是服務拆分粒度。

image-20230503095741321

微服務架構的特點:

1、單一職責:微服務拆分粒度更小,每一個服務都對應唯一的業務能力,做到單一職責

2、自治:團隊獨立、技術獨立、資料獨立,獨立部署和交付

3、面向服務:服務提供統一標準的介面,與語言和技術無關

微服務系統架構的優點:

1、可擴充套件性好:由於系統中的不同元件可以獨立地進行擴充套件和升級,從而提高了整個系統的擴充套件性和可靠性。

2、容錯性高:由於系統中的元件可以在不同的計算機或伺服器上執行,因此即使某些節點出現故障也不會影響整個系統的執行。

3、高效性強:分散式系統可以將負載和任務分配到不同的節點上,從而提高系統的併發能力和處理速度。

4、靈活性強:分散式系統可以支援多種程式語言和應用程式框架,並且可以利用各種雲端計算技術,如Docker、Kubernetes等。

微服務系統架構的存在的問題:

1、微服務的管理:這些微服務如果沒有進行統一的管理,那麼維護性就會極差。

2、服務間的通訊:微服務之間肯定是需要進行通訊,比如購物車微服務需要訪問商品微服務。

3、前端訪問問題:由於每一個微服務都是部署在獨立的一臺伺服器的,每一個微服務都存在一個對應的埠號,前端在訪問指定微服務的時候肯定需要指定微服務的ip地址和埠號,難道需要在前端維護每一個微服務的ip地址和埠號?

4、配置檔案管理:當構建服務叢集的時候,如果每一個微服務的配置檔案還是和微服務進行繫結,那麼維護性就極差。

微服務技術對比

Dubbo SpringCloud SpringCloudAlibaba
註冊中心 zookeeper、Redis Eureka、Consul Nacos、Eureka
服務遠端呼叫 Dubbo協議 Feign(http協議) Dubbo、Feign
配置中心 SpringCloudConfig SpringCloudConfig、Nacos
服務閘道器 SpringCloudGateway、Zuul SpringCloudGateway、Zuul
服務監控和保護 dubbo-admin,功能弱 Hystrix Sentinel

企業需求

image-20241019105346931

Spring Cloud Alibaba概述

針對微服務系統架構所存在的問題,肯定是需要有具體的技術來解決,而所使用到的技術就是Spring Clouad Alibaba。那麼想要了解Spring Clouad Alibaba,那麼就需要先了解一下Spring Cloud。

1、Spring Cloud 是一系列框架的有序集合。在Spring Cloud這個專案中包含了很多的元件【子框架】,每一個元件都是用來解決問題系統架構中所遇到的問題,因此Spring Cloud可以看做是一套微服務的解決方案。

2、Spring Cloud中常見的元件:Eureka(服務註冊中心)、Openfeign(服務遠端呼叫)、Gateway(服務閘道器)、Spring Cloud Config(統一配置中心)等。

3、Spring Cloud專案官方網址:https://spring.io/projects/spring-cloud

4、Spring Cloud依賴於Spring Boot,並且有版本的相容關係,如下所示:

image-20230503102618925

Spring Cloud Alibaba簡介

Spring Cloud Alibaba是阿里針對微服務系統架構所存在的問題給出了一套解決方案,該專案包含了微服務系統架構必須的一些元件。

常見的元件可以參看官網地址:https://spring-cloud-alibaba-group.github.io/github-pages/2021/en-us/index.html

注意:

1、Spring Cloud Alibaba中所提供的元件是遵循Spring Cloud規範的,兩套技術所提供的元件是可以搭配使用的。

2、在現在企業開發中往往是兩套技術元件搭配進行使用:Nacos(服務註冊中心和配置中心)、Openfeign(遠端呼叫)、Ribbon(客戶端負載均衡器)、Gateway(服務閘道器)、Sentinel(服務保護元件)等。

服務遠端呼叫(RestTemplate)

RestTemplate 是 Spring Framework 提供的一個同步 HTTP 客戶端,用於在 Spring 應用程式中方便地進行 RESTful 服務的呼叫。它封裝了底層的 HTTP 客戶端,實現了與 RESTful API 進行互動的常見模式,簡化了 HTTP 請求和響應的處理。

主要特點

  • 簡化的 APIRestTemplate 提供了一系列方法來執行 HTTP 請求,例如 getForObject, getForEntity, postForObject, postForEntity, put, delete 等,使得呼叫 REST 介面非常簡單。
  • 支援多種 HTTP 方法:可以使用 RestTemplate 發起 GET、POST、PUT、DELETE 等多種型別的請求。
  • 物件轉換RestTemplate 使用 HttpMessageConverter 將 HTTP 請求和響應體中的 JSON/XML 資料與 Java 物件之間進行自動轉換,減少了手動解析的工作。
  • 錯誤處理:可以透過實現 ResponseErrorHandler 介面來自定義錯誤處理機制。
  • 支援 URI 模板:可以使用 URI 模板(例如 {id})來構建動態的 URL。
  • 整合:可以與 Spring 的其他部分(如 Spring Boot)無縫整合,提供易用的配置方式。

配置 RestTemplate

在 Spring Boot 應用中,通常可以透過配置類來定義 RestTemplate 的 Bean:

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.client.RestTemplate;  

@Configuration  
public class AppConfig {  

    @Bean  
    public RestTemplate restTemplate() {  
        return new RestTemplate();  
    }  
}  

使用示例

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import org.springframework.web.client.RestTemplate;  

@Service  
public class MyService {  

    @Autowired  
    private RestTemplate restTemplate;  

    public User getUserById(Long id) {  
        String url = "https://api.example.com/users/{id}";  
        return restTemplate.getForObject(url, User.class, id);  
    }  

    public void createUser(User user) {  
        String url = "https://api.example.com/users";  
        restTemplate.postForObject(url, user, User.class);  
    }  
}  

註冊中心產品

本小結主要給大家來介紹一下常見的註冊中心的產品。

Eureka

Eureka是Netflix開源的一個基於REST的服務治理框架,主要用於實現服務註冊、發現和負載均衡。透過Eureka,我們可以將微服務的各個例項註冊到服務中心,並根據需要進行負載均衡和呼叫,從而實現整個微服務架構的高可用和彈性。

image-20241019115332363

Eureka包含兩個元件:Eureka Server和Eureka Client。

服務提供者在啟動時會透過Eureka Client向Eureka Server註冊自己的資訊(包括IP地址、埠號和服務名等),並且每隔一段時間會傳送心跳來告訴Eureka Server它仍然存活。服務消費者可以透過Eureka Client從Eureka Server獲取服務提供者的列表,並對這些服務進行負載均衡和呼叫。

Eureka的優點包括:

1、簡單易用:Eureka框架非常簡單易用,便於快速上手和部署。

2、高可用性:Eureka支援多節點部署,並會自動將失效的節點剔除,確保整個系統的高可用性和彈性。

3、動態擴充套件性:Eureka可以根據實際需求進行擴充套件,透過新增新的服務提供者可以很容易地增加應用程式的處理能力。

4、易於整合:Eureka可以與Spring Cloud等流行的微服務框架進行無縫整合,從而提供更完善的微服務體系支援。

Eureka的不足之處:

1、Eureka Server 為單點故障問題,雖然可以透過多節點部署來最佳化和緩解,但是在高併發場景下仍可能成為限制系統擴充套件的瓶頸。

2、Eureka的服務註冊中心本身也需要高可用環境,一旦出現問題,可能影響到整個微服務的正常執行。

官網地址:https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/

Nacos

Nacos官網地址:https://nacos.io/zh-cn/

Nacos是 Dynamic Naming and Configuration Service的首字母簡稱,一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺。

Nacos 致力於幫助您發現、配置和管理微服務。Nacos 提供了一組簡單易用的特性集,幫助您快速實現動態服務發現、服務配置、服務後設資料及流量管理。

image-20241019115524434

Nacos Server:服務註冊中心,它是服務,其例項及後設資料的資料庫。服務例項在啟動時註冊到服務登錄檔,並在關閉時登出。服務註冊中心可能會呼叫服務例項的健康檢查 API 來驗證它是否能夠處理請求。Nacos Server需要獨立的部署。

Nacos Client: Nacos Client負責和Nacos Server進行通訊完成服務的註冊和服務的發現。

Nacos Console:是Nacos的控制模組,Nacos提供了視覺化的後臺管理系統,可以很容易的實現服務管理操作。

Nacos的優點包括:

1、高可用性:Nacos支援多節點部署,透過選舉演算法實現了高可用和故障轉移能力,在節點當機或網路異常情況下仍能保證整個系統的穩定執行。

2、動態擴充套件性:Nacos可以根據實際需求進行快速擴充套件和縮容,支援叢集、多資料中心、地域感知等特性。

3、完備的功能支援:Nacos支援服務註冊與發現、配置管理、流量管理、DNS解析、儲存KV對等功能,並且提供了Web介面和RESTful API等多種方式來使用這些功能。

4、易於整合:Nacos提供了多種語言和框架的整合方案,並且支援Spring Cloud等流行的微服務框架。

總的來說,Nacos是一個功能齊全、易於使用和高可用的分散式服務治理平臺,可以為分散式系統提供高效、穩定的執行環境。

Nacos註冊中心

透過註冊中心可以對服務提供方和服務消費方進行解耦。

image-20241019115209096

工作流程說明:

1、服務提供方在啟動的時候,會向註冊中心註冊自己服務的詳情資訊(ip、埠號等)。在註冊中心中會維護一張服務清單,儲存這些註冊資訊,註冊中心需要以心跳的方式去監測清單中的服務是否可用,如果不可用,需要在服務清單中剔除不可用的服務。

2、服務消費方向服務註冊中心諮詢服務,並獲取所有服務的例項清單,然後按照指定的負載均衡演算法從服務清單中選擇一個服務例項進行訪問。

Nacos入門

Windows環境安裝Nacos

  • https://nacos.io/download/nacos-server/
  • 解壓Nacos安裝檔案到沒有中文和空格目錄

  • 進入bin目錄,使用cmd開啟,透過命令啟動Nacos服務

startup.cmd -m standalone

image-20241019134317964

  • 開啟瀏覽器訪問nacos的所提供的後端管理介面:http://localhost:8848/nacos

使用者名稱和密碼:nacos/nacos,登入成功以後會進入到nacos的主頁面

微服務註冊到nacos中

1、在工程中引入如下依賴

<!-- nacos作為註冊中心的依賴 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2、在application.yml檔案中新增如下配置

spring:
  # 配置nacos註冊中心的地址
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  #nacos註冊中心的IP加埠號
  application:
    name: yourServiceName   # 每一個服務註冊到nacos註冊中心都需要提供一個服務名稱,order微服務註冊的時候需要更改微服務名稱

3、啟動微服務:就可以在nacos的後臺管理系統中看到

image-20241019181202444

另外,我們可以將service多次啟動, 模擬多例項部署,但為了避免埠衝突,需要修改埠設定:

image-20241019150026944

image-20241019150043204

image-20241020125848944

遠端呼叫Nacos

當我們把微服務都註冊到註冊中心以後,那麼此時就可以根據服務的名稱從註冊中心獲取服務的ip地址和埠號了,就可以使用遠端呼叫。

1、RestTemplate

只使用restTemplate對遠端介面進行呼叫

2、DiscoveryClient

使用到Spring Cloud中所提供的一個服務發現的客戶端物件DiscoveryClient

在 Spring Cloud 中,DiscoveryClient 是實現微服務之間通訊的核心元件之一。具體實現通常透過 EurekaDiscoveryClientConsulDiscoveryClient 來完成。

分散式系統:在任何需要微服務協作的分散式系統中,服務發現機制都是必不可少的,它讓微服務能夠動態地找到和呼叫彼此。

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired          // 注入RestTemplate遠端呼叫工具
    private RestTemplate restTemplate ;

    @Autowired     
    private DiscoveryClient discoveryClient ;

    @Autowired
    private OrderMapper orderMapper ;

    @Override
    public Order findOrderByOrderId(Long orderId) {

        // 根據id查詢訂單資料
        Order order = orderMapper.findOrderByOrderId(orderId);

        // 根據服務名稱從註冊中心中獲取服務例項列表
        ServiceInstance serviceInstance = chooseServiceInstance("chs-cloud-user");

        // 發起遠端呼叫
        User user = restTemplate.getForObject("http://" + serviceInstance.getHost() +":" + serviceInstance.getPort() +"/api/user/findUserByUserId/" + order.getUserId(), User.class);
        order.setUser(user);

        // 返回訂單資料
        return order;
    }

    // 根據服務的名稱從註冊中心中獲取服務地址資訊
    public ServiceInstance chooseServiceInstance(String applicationName) {

        // 獲取服務例項列表
        List<ServiceInstance> instances = discoveryClient.getInstances(applicationName);

        // 編寫一個簡易的隨機負載均衡演算法
        int size = instances.size();
        Random random = new Random() ;
        int instanceIndex = random.nextInt(size);
        ServiceInstance serviceInstance = instances.get(instanceIndex);

        // 返回服務例項
        return serviceInstance ;
    }

}

在 Spring Cloud 的使用場景中,開發者可以在應用程式類中新增 @EnableDiscoveryClient 註解,使應用能夠作為一個服務註冊到服務註冊中心:

java@SpringBootApplication  
@EnableDiscoveryClient  
public class MyMicroserviceApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(MyMicroserviceApplication.class, args);  
    }  
}  

3、spring-cloud-loadbalancer

spring-cloud-loadbalancer 是 Spring Cloud 的一部分,提供了一個輕量級的負載均衡解決方案,用於在微服務架構中處理服務間的請求分發。它允許你在使用 Spring Cloud 的環境中輕鬆實現客戶端負載均衡。

主要特點

客戶端負載均衡spring-cloud-loadbalancer 實現了客戶端負載均衡的概念,由客戶端自己選擇後端服務例項,而不是透過外部負載均衡器。這種方式可以減少延遲,提高響應速度。

整合 Spring Cloud:與其他 Spring Cloud 專案(如 Eureka、Consul 和 Spring Cloud Gateway)深度整合,支援從這些服務發現工具中獲取服務例項資訊。

可插拔的負載均衡策略:使用者可以自定義負載均衡策略,Spring Cloud LoadBalancer 支援多種內建的負載均衡演算法,如輪詢、隨機、介面雜湊等

透明性:透過使用 Spring Cloud LoadBalancer,開發者可以將負載均衡的配置與業務邏輯分開,使程式碼更加簡潔和易於管理。

與 WebFlux 整合spring-cloud-loadbalancer 完全支援反應式程式設計模型(WebFlux),允許在響應式應用程式中使用負載均衡。

使用步驟:

1、在order微服務中新增依賴

<!-- spring cloud 所提供的負載均衡器 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

2、在宣告RestTemplate的方法上新增@LoadBalanced註解

@Configuration
public class AppConfig {
    @Bean
    @LoadBalanced  // 讓RestTemplate具有負載均衡的能力
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

3、更改遠端呼叫程式碼

// 服務提供方的服務ip地址和埠號可以使用服務提供方的服務名稱進行替換
restTemplate.getForObject("http://chs-cloud-user/api/user/findUserByUserId/" + order.getUserId(), User.class);

注意:預設使用的負載均衡演算法就是輪詢【依次呼叫對應服務】

4、OpenFeign+loadbalancer(重點)

使用OpenFeign.+loadbalancer元件進行遠端服務呼叫(最推薦的一種方式)

feign是一個宣告式的http客戶端,官方地址:https://github.com/OpenFeign/feign其作用就是幫助我們優雅的實現http請求的傳送。

OpenFeign 是一個宣告式的 HTTP 客戶端,簡化了在 Java 應用中呼叫 RESTful 服務的開發。它適用於微服務架構,使得服務間的呼叫更加簡潔和易於維護。當 OpenFeign 與負載均衡器結合使用時,可以有效地在多個服務例項之間分配請求,從而提高可用性和容錯能力。

OpenFeign 與負載均衡的結合

1、新增依賴:
pom.xml 中加入 OpenFeign 和負載均衡器的依賴

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

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

2、啟用 OpenFeign:

在主應用程式類中新增 @EnableFeignClients 註解

import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.openfeign.EnableFeignClients;  

@SpringBootApplication  
//開啟openFeign遠端呼叫,預設在當前啟動類所在的包及子包下去掃描openFeign介面
@EnableFeignClients(vlaue="指定掃描的包名")
public class MyApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(MyApplication.class, args);  
    }  
}

3、定義 Feign 客戶端:

建立介面並使用 Feign 註解定義請求

import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.GetMapping;  

@FeignClient(name = "my-service")   //nacos中伺服器的名字
public interface MyServiceClient {  
    //無參
    @GetMapping("/api/data") //請求資料的uri
    String getData();  
    
    //有參
    @GetMapping("/api/user/findUserByUserId/{userId}") //請求資料的uri
    public abstract User queryById(@PathVariable("userId") Long userId) ;	// 根據userId查詢使用者資訊的介面方法
}  

注意==》如果引數過多可以建立一個類,實現該介面進行操作

4、呼叫 Feign 客戶端:
透過自動注入的 Feign 客戶端呼叫服務,負載均衡器會根據配置處理請求。

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  

@RestController  
public class MyController {  
    @Autowired
    private MyServiceClient MyServiceClient ; 
    
    @GetMapping("/fetch-data")  
    public String fetchData() {  
        //呼叫自己建立的介面
        return myServiceClient.getData();  
    }  
    
    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable("userId") Long userId) {
        User user = myServiceClient.queryById(id);
        return user ;
    } 
}  

請求分發: 負載均衡器能夠智慧地將請求分發到不同的服務例項,避免某個例項過載。

故障轉移: 如果一個服務例項不可用,負載均衡器可以自動將請求切換到其他健康的例項,提高系統可靠性。

簡化開發: OpenFeign 透過註解的方式簡化了 HTTP 請求的構建,可以輕鬆與負載均衡器整合。

Nacos高階特性

服務叢集

在實際生產環境中,為了保證每一個服務的高可用,那麼此時就需要去構建服務叢集,但是並不是說把所有的服務都部署在一個機房裡。而是將多個服務分散的部署到不同的機房中,每一個機房的服務可以看做成是一個叢集

image-20241019180040165

微服務互相訪問時,應該儘可能訪問同叢集例項,因為本地訪問速度更快。當本叢集內不可用時,才訪問其它叢集。例如:上海機房內的order微服務應該優先訪問同機房的user微服務。

叢集配置

  • 修改chs-cloud-user的application.yml檔案,新增叢集配置:
spring:
  cloud:
    nacos:
      discovery:
        cluster-name: SH		# 配置服務所屬叢集

啟動三個服務user微服務例項,例項所屬叢集分配情況:例項1屬於SH,例項2和例項3屬於BJ

  • 透過新增新增JVM引數更改服務例項所屬叢集,啟動例項2和例項3

image-20230809102346561

例項2:10101

-Dserver.port=10101 -Dspring.cloud.nacos.discovery.cluster-name=BJ

例項3:10103

-Dserver.port=10103 -Dspring.cloud.nacos.discovery.cluster-name=BJ

結果:image-20241019181449649

叢集訪問

NacosLoadBalancer

優先同叢集內進行隨機的負載均衡。如果統計群內的目標服務例項均不可用,此時跨叢集隨機負載均衡。

需求:當order服務優先訪問SH叢集中的user微服務例項,當SH叢集中的user微服務例項出現問題以後,在訪問BJ叢集中的例項。

步驟:

1、給order微服務的application.yml檔案,新增叢集配置:

spring:
  cloud:
    nacos:
      discovery:
        cluster-name: SH		# 配置服務所屬叢集

2、order微服務在loadbalancer元件中整合nacos

spring:
  # 配置nacos註冊中心的地址
  cloud:
    loadbalancer:
      nacos:    # 整合nacos的負載均衡演算法
        enabled: true

權重配置

實際部署中會出現這樣的場景:伺服器裝置效能有差異,部分例項所在機器效能較好,另一些較差,我們希望效能好的機器承擔更多的使用者請求。

Nacos提供了權重配置來控制訪問頻率,權重越大則訪問頻率越高。

在Nacos控制檯,找到chs-cloud-user的例項列表,點選編輯,即可修改權重:

image-20241019182252820

權重取值範圍:0~100

  • 在配置檔案中進行權重配置:
spring:
  cloud:
    nacos:
      discovery:
        weight: 0.1

注意:如果權重修改為0,則該例項永遠不會被訪問

環境隔離

在實際的開發過程中,可能會存在很多個軟體環境:開發環境、測試環境、生產環境。

nacos也是支援多環境隔離配置的,在nacos是透過namespace來實現多環境的隔離。

image-20241019182404658

namespace + group 才可以確定具體的微服務例項。預設情況下,所有service、group都在同一個namespace,名為public

建立名稱空間

我們也可以建立新的名稱空間,來將不同的服務隔離到不同的環境下面。

image-20230503191050511

image-20241019182839561

微服務配置名稱空間

給微服務新增名稱空間的配置,來指定該微服務所屬環境。

例如,修改chs-cloud-order的application.yml檔案

spring:
  # 配置nacos註冊中心的地址
  cloud:
    nacos:
      discovery:
        namespace: 4a88035e-acf3-45a9-924f-2421acbff67a  # 配置服務例項所屬名稱空間

此時order微服務所對應的服務例項就屬於新的名稱空間,user微服務所對應的服務例項屬於public的名稱空間,那麼此時在進行遠端呼叫的時候,就會出現如下的錯誤:

image-20241019182733494

微服務配置分組

cloud:
  nacos:
    discovery:
      server-addr: 192.168.126.1:8848
      group: groupName  #分組的名稱

同一個名稱空間下,還可以建立多個組,預設的組DEFAULT GROUP,名稱空間和組,用於實現環境的隔離,專案的隔離…一種隔離機制。

注意:微服務的釋出和呼叫,針對【同一個名稱空間和組】進行的。

例項型別

Nacos中的服務例項存在兩種型別:

1、臨時例項:如果例項當機超過一定時間,會從服務列表剔除,並且例項會定時上報自身的健康狀態給Nacos註冊中心,預設的型別。

2、非臨時例項:如果例項當機,不會從服務列表剔除,Nacos註冊中心會主動詢問例項的健康狀態,也可以叫永久例項

配置一個服務例項為永久例項:

spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false  # 配置該例項為非臨時例項

LoadBalancer

Spring Cloud LoadBalancer是Spring Cloud中負責客戶端負載均衡的模組,其主要原理是透過選擇合適的服務例項來實現負載均衡。

客戶端負載均衡:就是負載均衡演算法由客戶端提供

image-20241020121402296

LoadBalancer原理

Spring Cloud LoadBalancer的底層採用了一個攔截器【LoadBalancerInterceptor】,攔截了RestTemplate發出的請求,對地址做了修改

image-20230503222331245

執行流程說明:

1、透過LoadBalancerInterceptor請求攔截器攔截我們的RestTemplate請求:http://chs-cloud-user/api/user/findUserByUserId/1

2、獲取請求的url,然後從請求的url中獲取服務提供方的主機名稱

3、然後呼叫LoadBalancerClient中的execute方法,將服務提供方的名稱傳遞過去

4、在LoadBalancerClient的choose方法中透過ReactiveLoadBalancer.Factory從Nacos註冊中心中獲取服務列表以及負載均衡演算法例項物件

5、透過ReactiveLoadBalancer從服務列表中選擇一個服務例項地址,然後發起遠端呼叫

原始碼跟蹤

LoadBalancerInterceptor

核心原始碼如下所示:

image-20230503223822056

可以看到這裡的intercept方法,攔截了使用者的HttpRequest請求,然後做了幾件事:

1、request.getURI():獲取請求uri,本例中就是 http://chs-cloud-user/api/user/findUserByUserId/1

2、originalUri.getHost():獲取uri路徑的主機名,其實就是服務id,chs-cloud-user

3、this.loadBalancer.execute():處理服務id,和使用者請求。

這裡的this.loadBalancerBlockingLoadBalancerClient型別,我們繼續跟入。

BlockingLoadBalancerClient

核心原始碼如下所示:

image-20230503224702411

ReactiveLoadBalancer.Factory的getInstance方法做了兩件事情:

1、獲取了一個具體的負載均衡演算法物件

2、根據服務的id從Nacos註冊中心中獲取服務地址列表

緊跟著呼叫了RoundRobinLoadBalancer#choose方法,從服務列表中選擇一個服務例項物件。

預設的負載均衡演算法:RoundRobinLoadBalancer

更改負載均衡演算法

LoadBalancer預設的負載均衡演算法是RoundRobinLoadBalancer,如果想更改預設的負載均衡演算法,那麼此時需要向Spring容器中註冊一個Bean,並且配置負載均衡的使用者。

隨機負載均衡策略

1、在Spring容器中註冊一個Bean

public class CustomLoadBalancerConfiguration {

    /**
     * @param environment: 用於獲取環境屬性配置,其中LoadBalancerClientFactory.PROPERTY_NAME表示該負載均衡器要應用的服務名稱。
     
     * @param loadBalancerClientFactory: 是Spring Cloud中用於建立負載均衡器的工廠類,透過getLazyProvider方法獲取ServiceInstanceListSupplier物件,以提供可用的服務列表。
     
     * ServiceInstanceListSupplier:用於提供ServiceInstance列表的介面,可以從DiscoveryClient或者其他註冊中心中獲取可用的服務例項列表。
     *
     */
    
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
        Environment environment, 
        LoadBalancerClientFactory loadBalancerClientFactory) 
    {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);
    }
}

注意:Bean的返回值型別為==》ReactorLoadBalancer

2、配置負載均衡演算法的使用者

@Configuration
@LoadBalancerClients(value = {
        @LoadBalancerClient(name = "chs-cloud-user" , configuration = CustomLoadBalancerConfiguration.class)      // 將負載均衡演算法應用到指定的服務提供方中
})
public class RestTemplateConfiguration {

    @Bean
    @LoadBalanced       // 讓RestTemplate具有負載均衡的能力
    public RestTemplate restTemplate() {
        return new RestTemplate() ;
    }

}

OpenFeign元件

使用OpenFeign.+loadbalancer元件進行遠端服務呼叫(最推薦的一種方式)

feign是一個宣告式的http客戶端,官方地址:https://github.com/OpenFeign/feign其作用就是幫助我們優雅的實現http請求的傳送。

OpenFeign 是一個宣告式的 HTTP 客戶端,簡化了在 Java 應用中呼叫 RESTful 服務的開發。它適用於微服務架構,使得服務間的呼叫更加簡潔和易於維護。當 OpenFeign 與負載均衡器結合使用時,可以有效地在多個服務例項之間分配請求,從而提高可用性和容錯能力。

OpenFeign 與負載均衡的結合

1、新增依賴:
pom.xml 中加入 OpenFeign 和負載均衡器的依賴

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

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

2、啟用 OpenFeign:

在主應用程式類中新增 @EnableFeignClients 註解

import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.openfeign.EnableFeignClients;  

@SpringBootApplication  
//開啟openFeign遠端呼叫,預設在當前啟動類所在的包及子包下去掃描openFeign介面
@EnableFeignClients(vlaue="指定掃描的包名")
public class MyApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(MyApplication.class, args);  
    }  
}

3、定義 Feign 客戶端:

建立介面並使用 Feign 註解定義請求

import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.GetMapping;  

@FeignClient(name = "my-service")   //nacos中伺服器的名字
public interface MyServiceClient {  
    //無參
    @GetMapping("/api/data") //請求資料的uri
    String getData();  
    
    //有參
    @GetMapping("/api/user/findUserByUserId/{userId}") //請求資料的uri
    public abstract User queryById(@PathVariable("userId") Long userId) ;	// 根據userId查詢使用者資訊的介面方法
}  

注意==》如果引數過多可以建立一個類,可以接收一個物件。

4、呼叫 Feign 客戶端:
透過自動注入的 Feign 客戶端呼叫服務,負載均衡器會根據配置處理請求。

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  

@RestController  
public class MyController {  
    @Autowired
    private MyServiceClient MyServiceClient ; 
    
    @GetMapping("/fetch-data")  
    public String fetchData() {  
        //呼叫自己建立的介面
        return myServiceClient.getData();  
    }  
    
    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable("userId") Long userId) {
        User user = myServiceClient.queryById(id);
        return user ;
    } 
}  

請求分發: 負載均衡器能夠智慧地將請求分發到不同的服務例項,避免某個例項過載。

故障轉移: 如果一個服務例項不可用,負載均衡器可以自動將請求切換到其他健康的例項,提高系統可靠性。

簡化開發: OpenFeign 透過註解的方式簡化了 HTTP 請求的構建,可以輕鬆與負載均衡器整合。

OpenFeign自定義配置

日誌配置

OpenFeign可以支援很多的自定義配置,如下表所示:

型別 作用 說明
feign.Logger.Level 修改日誌級別 包含四種不同的級別:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder 響應結果的解析器 http遠端呼叫的結果做解析,例如解析json字串為java物件
feign.codec.Encoder 請求引數編碼 將請求引數編碼,便於透過http請求傳送
feign.Contract 支援的註解格式 預設是SpringMVC的註解
feign.Retryer 失敗重試機制 請求失敗的重試機制,預設是沒有,不過會使用Ribbon的重試

一般情況下,預設值就能滿足我們使用,如果要自定義時,只需要建立自定義的@Bean覆蓋預設Bean即可。

日誌兩種方式的配置

1、基於配置檔案的方式

基於配置檔案修改feign的日誌級別可以針對單個服務

# 1、將feign包下產生的日誌的級別設定為debug
logging:
  level:
    com.chs.chs.cloud.order.feign: debug
    
# 2、openfeign日誌級別配置
spring:
  cloud:
    openfeign:
      client:
        config: 
          chs-cloud-user:  
            loggerLevel: full

也可以針對所有服務

# 1、將feign包下產生的日誌的級別設定為debug
logging:
  level:
    com.chs.chs.cloud.order.feign: debug
    
# 2、openfeign日誌級別配置
spring:
  cloud:
    openfeign:
      client:
        config: 
          default:  # 這裡用default就是全域性配置,如果是寫服務名稱,則是針對某個微服務的配置
            loggerLevel: full

而日誌的級別分為四種:

① NONE:不記錄任何日誌資訊,這是預設值。

② BASIC:僅記錄請求的方法,URL以及響應狀態碼和執行時間

③ HEADERS:在BASIC的基礎上,額外記錄了請求和響應的頭資訊

④ FULL:記錄所有請求和響應的明細,包括頭資訊、請求體、後設資料。

2、Java程式碼的方式

基於Java程式碼來修改日誌級別,先宣告一個類,然後宣告一個Logger.Level的物件:

public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // 日誌級別為BASIC
    }
}

如果要全域性生效,將其放到啟動類的@EnableFeignClients這個註解中:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class) 

如果是區域性生效,則把它放到對應的@FeignClient這個註解中:

@FeignClient(value = "chs-cloud-user", configuration = DefaultFeignConfiguration .class) 

超時配置

超時機制概述:Feign 的超時機制是指在使用 Feign 進行服務間的 HTTP 呼叫時,設定請求的超時時間。當請求超過設定的超時時間後,Feign 將會中斷該請求並丟擲相應的異常。

超時機制的意義

1、防止長時間等待:透過設定適當的超時時間,可以避免客戶端在請求服務時長時間等待響應而導致的效能問題。如果沒有超時機制,客戶端可能會一直等待,從而影響整個系統的吞吐量和響應時間。

2、避免資源浪費:超時機制可以幫助及時釋放佔用的資源,例如連線、執行緒等。如果請求一直處於等待狀態而不超時,將導致資源的浪費和系統的負載增加。

3、最佳化使用者體驗:超時機制可以防止使用者長時間等待無響應的情況發生,提供更好的使用者體驗。當請求超時時,可以及時給出錯誤提示或進行相應的處理,以提醒使用者或採取其他措施。

feign預設的超時配置為:建立連線的超時時間為:10s,讀取資料的超時時間為:60s

image-20230624103625541

超時時間越長,資源浪費的時間就越長,系統的穩定性就越差,因此需要設定為一個較為合理的超時時間,設定防止如下所示:

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            loggerLevel: full	
            read-timeout: 2000			# 讀取資料的超時時間設定為2s
            connect-timeout: 2000		# 建立連線的超時時間設定為2s

重試配置

feign一旦請求超時了,那麼此時就會直接丟擲SocketTimeoutException: Read timed out的異常。請求超時的原因有很多種,如網路抖動、服務不可用等。如果由於網路暫時不可用導致觸發了超時機制,那麼此時直接返回異常資訊就並不是特別的合理,尤其針對查詢請求,肯定希望得到一個結果。合理的做法:觸發超時以後,讓feign進行重試

具體步驟:

1、自定義重試器

public class FeignClientRetryer implements Retryer {

    // 定義兩個成員變數來決定重試次數
    private int start = 1 ;
    private int end = 3 ;

    @Override
    public void continueOrPropagate(RetryableException e) {     // 是否需要進行重試取決於該方法是否丟擲異常,如果丟擲異常重試結束
        if(start >= end) {
            throw new RuntimeException(e) ;
        }
        start++ ;
    }
    
    @Override
    public Retryer clone() {    // 框架底層呼叫該方法得到一個重試器
        return new FeignClientRetryer();
    }
}

2、指定配置的重試器類

spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            loggerLevel: full
            read-timeout: 2000
            connect-timeout: 2000
            retryer: com.chs.chs.cloud.order.feign.FeignClientRetryer		# 配置自定義重試器,重試器的全類名

Nacos配置中心

Nacos除了可以做註冊中心,同樣可以做配置管理來使用。

統一配置管理

當微服務部署的例項越來越多,達到數十、數百時,逐個修改微服務配置就顯得十分的不方便,而且很容易出錯。我們需要一種統一配置管理方案,可以集中管理所有例項的配置

image-20241020171315202

nacos一方面可以將配置集中管理,另一方可以在配置變更時,及時通知微服務,實現配置的熱更新。

Nacos中新增配置

在Nacos服務端建立一個配置

image-20230624171530387

然後在彈出的表單中,填寫配置資訊

image-20210714164856664

微服務整合配置中心

微服務需要進行改造,從Nacos配置中心中獲取配置資訊進行使用。

1、引入依賴

<!-- nacos作為配置中心時所對應的依賴 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、 配置檔案中配置 Nacos Config 地址並引入服務配置

spring:
  cloud:
    nacos:
      config:
        server-addr: localhost:8848  # nacos伺服器的IP:埠號
  config:
    import:
      - nacos:userservice-dev.yaml # 引入伺服器的配置檔案

注意: 引入的配置檔案的名稱跟nacos中建立的名稱一致

讀取自定義配置

@Value

透過@Value註解讀取自定義配置

@RestController
@RequestMapping(value = "/api/user")
@Slf4j
public class UserController {
    @Autowired
    private UserService userService ;
    
    @Value("${pattern.dateformat}")  //獲取nacos配置檔案中的值
    private String pattern;

    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable(value = "userId") Long userId) {
        System.out.println("pattern = " + pattern);
        return userService.findUserByUserId(userId) ;
    }

}

@ConfigurationProperties

透過實體類,配合@ConfigurationProperties註解讀取自定義配置。

當配置屬性很多時,可以用到該方法讀取配置資訊

1、定義一個實體類,類中的屬性名跟配置檔案中的屬性名一致,必須提供get和set方法

@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat ;
}

2、在啟動類上新增@EnableConfigurationProperties註解

@SpringBootApplication
@EnableConfigurationProperties(value = { PatternProperties.class })
public class UserApplication {

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

}

3、使用該實體類

@RestController
@RequestMapping(value = "/api/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService ;

    @Value("${pattern.dateformat}") //獲取nacos配置檔案中的值
    private String pattern ;

    @Autowired   // 注入實體類
    private PatternProperties patternProperties ; 

    @GetMapping(value = "/findUserByUserId/{userId}")
    public User findUserByUserId(@PathVariable(value = "userId") Long userId) {
        System.out.println("pattern = " + pattern);
        System.out.println("patternProperties = " + patternProperties);
        return userService.findUserByUserId(userId) ;
    }

}

配置熱更新

我們最終的目的,是修改Nacos中的配置後,微服務中無需重啟即可讓配置生效,也就是配置熱更新

熱更新的兩種方式

方式一:在@Value注入的變數所在類上新增註解@RefreshScope

image-20230624200928589

方式二:透過實體類,配合@ConfigurationProperties註解讀取配置資訊,自動支援熱更新

配置優先順序

思考問題:如果在application.yml檔案中和Nacos配置中心中都定義了相同的配置內容,那麼哪一個配置的優先順序較高呢?

優先順序順序:Nacos配置中心的配置(後匯入的配置 > 先匯入的配置) > application.yml

相關文章