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 作為管理中心,自然,除了註冊和發現服務外,還有監控等其他輔助管理的功能。
具體從負載均衡的角度來講:
- Eureka Server——提供服務註冊和發現
- Service Provider——服務提供方,一般有多個服務參與排程
- Service Consumer——服務消費方,從 Eureka 獲取註冊服務列表,從而能夠消費服務,也就是請求的直接入口。
服務搭建
下面主要實戰一下負載均衡服務搭建。
正如上面所說,一套完整的負載均衡服務,至少需要三個服務。
1.註冊中心——Eureka Server
直接通過 IDEA 建立一個包含 Eureka Server 的 Spring Boot 專案,直接引入所需的 dependency。主要是 spring-cloud-dependencies
和 spring-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-eureka
和 fetch-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
- springcloud(二):註冊中心 Eureka
- springcloud(三):服務提供與呼叫
- Spring Cloud Netflix
- SpringCloud - 負載均衡器 Ribbon
- Spring Cloud(四):服務容錯保護 Hystrix【Finchley 版】
- Setup a Circuit Breaker with Hystrix, Feign Client and Spring Boot