1. 程式人生 > >Spring Cloud 負載均衡初體驗

Spring Cloud 負載均衡初體驗

目錄

  • 服務搭建
    • 1.註冊中心——Eureka Server
    • 2.服務提供方——Service Provider
    • 3.服務消費方——Service Consumer
  • 服務消費 Feign 與斷路器 Hystrix
  • 特別注意
  • Summary
  • Reference
  • Source Code

本文首發於我的個人部落格,Spring Cloud 負載均衡初體驗 ,歡迎訪問!

使用 Spring Cloud Netflix 元件 Eureka 和 Ribbon 構建單註冊中心的負載均衡服務。

Spring Cloud 是基於 Spring 的微服務技術棧,可以這麼概括吧,裡面包含了很多例如服務發現註冊、配置中心、訊息匯流排、負載均衡、斷路器、資料監控等元件,可以通過 Spring Boot 的形式進行整合和使用。

目前,專案中有這麼個需求,Spring Boot 做一個 web 服務,然後呼叫 TensorFlow 模型得到結果。但是由於 TensorFlow GPU 版,不支援多執行緒多引擎,所以只能採用多程序的方式去進行排程,所以需要做一個負載均衡。負載均衡的話,可以分為客戶端和服務端負載均衡。我目前還沒能領悟到有什麼不同,畢竟整體的架構都是一樣的,如下如圖。其中客戶端均衡負載的代表是 Spring Cloud 的 Ribbon,服務端負載均衡代表是 Nginx。

由於專案的壓力並不大,日平均請求約 5000 左右,因此就採用 Spring Cloud 中的元件進行客戶端負載均衡。主要用到的就是 Spring Cloud 和 Eureka。很多部落格中會也看到 Ribbon 的身影。其實他們都是 Spring Cloud Netflix 中的元件,用來構建微服務。本文所講的例子,也可以看作是一個微服務,把原來一個的演算法服務拆成了若干個小服務。之前講到的 Spring Cloud 的各種元件也都是為了使得這些獨立的服務能夠更好的管理和協作。

回到負載均衡,一個使用 Spring Boot 搭建的客戶端負載均衡服務,其實只需要 Rureka 這一個元件就夠了。

Eureka 是 Spring Cloud Netflix 當中的一個重要的元件,用於服務的註冊和發現。Eureka 採用了 C-S 的設計架構。具體如下圖,Eureka Server 作為一個註冊中心,擔任服務中臺的角色,餘下的其他服務都是 Eureka 的 Client。所有的服務都需要註冊到 Eureka Server 當中去,由它進行統一管理和發現。Eureka Server 作為管理中心,自然,除了註冊和發現服務外,還有監控等其他輔助管理的功能。

具體從負載均衡的角度來講:

  1. Eureka Server——提供服務註冊和發現
  2. Service Provider——服務提供方,一般有多個服務參與排程
  3. Service Consumer——服務消費方,從 Eureka 獲取註冊服務列表,從而能夠消費服務,也就是請求的直接入口。

服務搭建

下面主要實戰一下負載均衡服務搭建。

正如上面所說,一套完整的負載均衡服務,至少需要三個服務。

1.註冊中心——Eureka Server

直接通過 IDEA 建立一個包含 Eureka Server 的 Spring Boot 專案,直接引入所需的 dependency。主要是 spring-cloud-dependenciesspring-cloud-starter-netflix-eureka-server

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

在啟動類上添加註解 @EnableEurekaServer

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

在 yml 中配置 eureka。

server:
  port: 8080
eureka:.
    instance:
    prefer-ip-address: true
  client:
    # 表示是否將自己註冊到 Eureka Server,預設為 true
    register-with-eureka: false
    # 表示是否從 Eureka Server 獲取註冊資訊,預設為 true
    fetch-registry: false
    service-url:
      # 預設註冊的域,其他服務都往這個 url 上註冊
      defaultZone: http://localhost:${server.port}/eureka/

由於目前配置的是單節點的註冊中心,因此 register-with-eurekafetch-registry 都設為 false,不需要把自己註冊到服務中心,不需要獲取註冊資訊。

啟動工程後,訪問:http://localhost:8080/,就能看到一個圖形化的管理介面,目前沒有註冊任何服務。

2.服務提供方——Service Provider

在 yml 中配置 Eureka。

eureka:
  instance:
    prefer-ip-address: true    # 以 IP 的形式註冊
    # 預設是 hostname 開頭的,修改成 ip 開頭
    instance-id: \${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/    # 註冊到之前的服務中心
server:
  port: 8084
spring:
  application:
    name: services-provider    # 應用名稱

預設,Eureka 是通過 hostname 來註冊到 Eureka Server 上的,由於後面可能涉及到多節點的配置,hostname 可能不如 ip 方便管理,所以將 prefer-ip-address 設為 true,通過 ip 註冊,並修改 instance-id 格式為:${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}。注意 ip-address 為短線連線。

然後寫一個簡單的 web 服務做測試。

@RestController
public class ServicesController {
    @RequestMapping("/")
    public String home() {
        return "Hello world";
    }
}

然後執行工程。

3.服務消費方——Service Consumer

新建專案同 2,然後配置 Eureka:

eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://localhost:8080/eureka/
server:
  port: 8085
spring:
  application:
    name: services-consumer

和之前一樣,注意區分埠號和 name 就可以了。

再在啟動類,配置 RestTemplate 用於負載均衡的服務分發。這是服務消費的一種基本方式,下面還會介紹第二種 Feign。

@SpringBootApplication
public class ServicesConsumerApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ServicesConsumerApplication.class, args);
    }
}

在 Controller 中的呼叫

@RestController
public class ConsumerController {
    // services-provider 為服務 provider 的 application.name,負載均衡要求多個服務的 name 相同
    private final String servicesUrl = "http://services-provider/";
    private final RestTemplate restTemplate;

    public ConsumerController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @RequestMapping("/")
    public String home() {
        return restTemplate.getForObject(servicesUrl,String.class);
    }
}

然後執行工程,可以發現兩個服務都已經成功註冊到註冊中心。

有的部落格中可能提到需要新增,@EnableDiscoveryClient 或 @EnableEurekaClient,而實際上,官方文件已經明確,只要 spring-cloud-starter-netflix-eureka-client 在 classpath 中,應用會自動註冊到 Eureka Server 中去。

By having spring-cloud-starter-netflix-eureka-clienton the classpath, your application automatically registers with the Eureka Server. Configuration is required to locate the Eureka server.
https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.0.M1/

直接呼叫 localhost:8085,就顯示 hello world,說明成功!如果不相信的話,可以起兩個 provider 服務,然後分別輸出不同的字串來驗證負載均衡是否成功。

2019-08-13 15:08:18.689  INFO 14100 --- [nio-8085-exec-4] c.n.l.DynamicServerListLoadBalancer: DynamicServerListLoadBalancer for client services-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=services-provider,current list of Servers=[192.168.140.1:8084]}

簡化後的日誌如上,可以看到,註解 @LoadBalanced 的負載均衡功能是通過 DynamicServerListLoadBalancer 來實現,這是一種預設的負載均衡策略。獲取到服務列表後,通過 Round Robin 策略(服務按序輪詢從 1 開始,直到 N)進行服務分發。

至此,本地的負載均衡服務就搭建完成了。

服務消費 Feign 與斷路器 Hystrix

如果是初體驗的話可以忽略這一節。新增這一節的目的是為了服務的完整性。在實際開發中,Feign 可能用到的更多,並且多會配合 Hystrix。

Feign 也是一種服務消費的方式,採用的是申明式的形式,使用起來更像是本地呼叫,它也是 Spring Cloud Netflix 中的一員。

Hystrix,簡而言之就是一種斷路器,負載均衡中如果有服務出現問題不可達後,通過配置 Hystrix,可以實現服務降級和斷路的作用,這個功能 Eureka 預設是不提供的。因此,之前說了,為了負載均衡的完整性,需要添上它,否則,Ribbon 依然會將請求分發到有問題的服務上去。

那麼要使用他們,首先需要新增依賴

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

在啟動類上加上 @EnableFeignClients

呼叫的時候需要實現介面。

@FeignClient(value = "services-provider")
public interface IRestRemote {
    @GetMapping("/")
    String home();
}

最後在 Controller 中注入介面,直接呼叫就可以了。

@RestController
public class ConsumerController {
    private final IRestRemote remote;

    public ConsumerController(IRestRemote remote) {
        this.remote = remote;
    }

    @RequestMapping("/")
    public String home() {
        return remote.home();
    }

相對於 RestTemplate,Fegin 使用起來更簡單,不需要去構造引數請求了,並且 Feign 底層集成了 Ribbon,也不需要顯示新增 @LoadBalanced 註解了。同時 Feign 也可以直接使用下面要講的 Hystrix。

Hystrix 的功能很多,遠不止斷路器一種,需要詳細瞭解可看別的部落格。這裡就講一下 Feign 如何整合 Hystrix 工作的。

下面先描述一下具體場景。假如現在有兩個負載均衡服務,其中有一個掛了或出現異常了,如果沒有斷路器,進行服務分發的時候,仍然會分配到。如果開啟 Hystrix,首先會進行服務降級(FallBack),也就是出現問題,執行預設的方法,並在滿足一定的條件下,熔斷該服務。

在原來 @FeignClient 的基礎上新增 fallback 引數, 並實現降級服務。

@FeignClient(value = "services-provider",fallback = RestRemoteHystrix.class)
public interface IRestRemote {
    @GetMapping("/")
    String home();
}
@Component
public class RestRemoteHystrix implements IRestRemote {
    @override
    public String home() {
        return "default";
    }
}

最後,在配置檔案中開啟 Feign 的 Hystrix 開關。

feign:
  hystrix:
    enabled: true

下面就可以測試了,沿著之前的例子,分別開啟兩個服務,並輸出不同的文字,當關閉一個服務後,再請求會發現,分配到關閉的那個服務時,會顯示 “default”,請求多次後發現,該服務不可用,說明斷路器配置成功!欲知更多,可以閱讀參考文獻 5-6。

特別注意

1.多節點的配置

如果不是單機,則需要修改部分欄位,具體如下注釋:

eureka:
  instance:
    prefer-ip-address: true     # 以 IP 的形式註冊
    # 主機的 ip 地址(同樣考慮叢集環境,一個節點可能會配置多個網段 ip,這裡可指定具體的)
    ip-address:
    # 主機的 http 外網通訊埠,該埠和 server.post 不同,比如外網需要訪問某個叢集節點,直接是無法訪問 server.post 的,而是需要對映到另一埠。
    # 這個欄位就是配置對映到外網的埠號
    non-secure-port:
     # 預設是 hostname 開頭的,修改成 ip 開頭
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 這裡不是 localhost 了,而是註冊中心的 ip:port
      defaultZone: http://ip:8080/eureka/
server:
  port: 8084
spring:
  application:
    name: services-provider    # 應用名稱

2.高可用註冊中心

本文上述搭建的是單個註冊中心的例子,基本已經滿足我目前的專案需求。但是在閱讀別的部落格中,發現真正的微服務架構,為了實現高可用,一般會有多個註冊中心,並且相互註冊形成。這裡先簡單做個記錄。

3.更復雜,自定義的負載均衡規則。

目前,其實只是引入了 spring cloud 和 Eureka 的依賴就實現了簡單的負載均衡。但仔細看 DynamicServerListLoadBalancer 類的位置,是在 Ribbon 下的。雖然沒有顯式去新增 Ribbon 的依賴包,但是實際上已經被包含進去了。那麼 Ribbon 是什麼?

Ribbon is a client-side load balancer that gives you a lot of control over the behavior of HTTP and TCP clients. Feign already uses Ribbon, so, if you use @FeignClient, this section also applies.

Ribbon 是 Spring Cloud 中的一個元件,主要是做客戶端負載均衡的。不關注底層細節,可能上文搭建的服務它無關,實際上還是用到了。那麼,如果需要自定義複雜均衡的規則,則需要通過配置 Ribbon 來實現。

Summary

本文主要是”拿來主義“的思想,直接用了 Spring Cloud 中的幾個元件來組建一個負載均衡服務。為了能夠更好地進行服務治理,還需按部就班先學習基本的原理概念,例如CAP理論等,在逐步學習 Cloud 中的元件,有一定的全域性觀。

註冊中心 Eureka 本身滿足的就是 AP,在生產環境中,為了保證服務的高可用,勢必要有至少兩個的註冊中心。

Reference

  1. springcloud(二):註冊中心 Eureka
  2. springcloud(三):服務提供與呼叫
  3. Spring Cloud Netflix
  4. SpringCloud - 負載均衡器 Ribbon
  5. Spring Cloud(四):服務容錯保護 Hystrix【Finchley 版】
  6. Setup a Circuit Breaker with Hystrix, Feign Client and Spring Boot

Source C