目錄
Using the Ribbon API Directly(直接使用 Ribbon API)
Ribbon 負載均衡策略概述
1、如有微服務 mc 下有 3 個節點 A、B、C,當微服務 mk 請求微服務 mc 時,應該使用何種規則向節點 A、B、C 發起請求呢?於是 Ribbon 有了負載均衡策略。
2、Ribbon 負載均衡繼承結構如下圖所示,IRule 接口是整個規則/策略的超類/接口:
策略名 | 描述 |
BestAvailableRule | 選擇一個最小的併發請求的server。逐個考察 Server,如果 Server 被 tripped(跳閘)了,則忽略,再選擇其中 ActiveRequestsCount 最小的 server。 |
AvailabilityFilteringRule | 過濾掉那些因爲一直連接失敗的被標記爲circuit tripped的後端server,並過濾掉那些高併發的的後端server(active connections 超過配置的閾值) |
WeightedResponseTimeRule | 根據響應時間分配一個weight,響應時間越長,weight越小,被選中的可能性越低。 |
RetryRule | 對選定的負載均衡策略機上重試機制。在一個配置時間段內當選擇server不成功,則一直嘗試使用subRule的方式選擇一個可用的server |
RoundRobinRule | 輪詢index,選擇index對應位置的server |
RandomRule | 隨機選擇一個server。在index上隨機,選擇index對應位置的server |
ZoneAvoidanceRule | 複合判斷server所在區域的性能和server的可用性選擇server |
Ribbon 負載均衡策略配置
1、仍然以 <<netflix ribbon 概述與基本使用、以及 RestTemplate 概述>> 中的 3 個應用作爲本文介紹的基礎:
eurekaserverchangSha 應用作爲 Eureka 服務端, eurekaclientfood、eurekaclient_cat 作爲客戶端,eurekaclientcat 微服務請求 eurekaclientfood 微服務。
2、eureka 依賴 ribbon,導入了 eureka 客戶端組件就同時導入了 ribbon。環境:Java jdk 8 + Spring boot 2.1.3 + spring cloud Greenwich.SR1 + spring 5.1.5。
3、官網 "6.4 Customizing the Ribbon Client by Setting Properties" 有說明:
全局配置文件的選項優先級高於 @RibbonClient(configuration=MyRibbonConfig.class 代碼方式,@RibbonClient 代碼方式高於默認值。
從 1.2.0 版本開始 Spring Cloud Netflix 支持從全局文件進行配置,支持的配置如下:
<clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule
<clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing
<clientName>.ribbon.NIWSServerListClassName: Should implement ServerList
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter
其中的 <clientName> 爲 Eureka 註冊中心註冊好的微服務名稱,也是微服務應用配置的 spring.applicatoin.name 屬性值。
配置的屬性值爲各個接口實現類的全類名。如下所示 users 爲需要請求的微服務名稱,屬性值爲各接口實現類的全類名:
users:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #加權響應時間規則
4、eurekaclientcat 請求 eurekaclientfood,所以修改 eurekaclient_cat 的配置文件如下,其餘的內容都無需修改:
server:
port: 9394
spring:
application:
name: eureka-client-cat #微服務名稱
eureka:
client:
service-url:
defaultZone: http://localhost:9393/eureka/ #eureka 服務器地址
instance:
prefer-ip-address: true # IP 地址代替主機名註冊
instance-id: changSha-cat # 微服務實例id名稱
#EUREKA-CLIENT-FOOD 請求的微服務名稱,即對方的 spring.application.name 屬性值
EUREKA-CLIENT-FOOD:
ribbon:
#隨機規則,對 EUREKA-CLIENT-FOOD 微服務下的節點隨機訪問
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
5、啓動 eurekaserverchangSha 、eurekaclient_cat 應用 ,然後使用下面的命令再啓動打包好的 eurekaclient_food 3 個實例,使用不同的端口以及實例 id:
java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9396 --eureka.instance.instance-id=changSha-food-9396
java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9397 --eureka.instance.instance-id=changSha-food-9397
java -jar eurekaclient_food-0.0.1-SNAPSHOT.jar --server.port=9398 --eureka.instance.instance-id=changSha-food-9398
此時 eureka 註冊中心如下所示:
6、訪問 http://localhost:9394/getCatById?id=110 通過 changSha-cat 微服務後臺請求 Eureka-client-food 微服務下的3個節點:
如果沒有在 eurekaclient_cat 中配置隨機訪問負載均衡策略,則默認情況下是使用輪詢策略的,如上所示,顯示現在是隨機訪問的,負載均衡配置生效。其它的均衡策略也是同理。
Ribbon 脫離 Eureka 使用
Ribbon 脫離 Eureka 使用分爲兩種情況,一是項目中並沒有使用 Eureka,二是項目中已經有 Eureka
沒有 Eureka 時
1、之前說過 Eureka 無論是服務端還是客戶端都依賴了 Ribbon,所以導入了 Eureka 組件後,同時已經導入了 Ribbon 組件,所以直接編碼 Ribbon 即可。現在沒有 Eureka 時,需要單獨導入 Ribbon 組件,修改 eureka-client-cat 的 pom.xml 文件如下(此時沒有 Eureka 組件):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
2、官網介紹地址:"How to Use Ribbon Without Eureka" ,當沒有 Eureka 時,只需要修改 application.yml 如下:
stores:
ribbon:
listOfServers: example.com,google.com
#stores 是一個自定義的標識符,建議寫成請求的微服務名稱,即對方的 spring.application.name 屬性值。
#listOfServers 爲請求的服務域名地址,也可以直接是 Ip:Port 格式,不用帶應用名稱,而是在代碼中寫應用名
3、現在繼續修改 eureka-client-cat 配置文件如下(此時沒有 eureka 的配置了):
server:
port: 9394
#EUREKA-CLIENT-FOOD 純粹是一個自定義的標識,會在代碼中使用類似如下的方式進行識別,根據標識找到服務器地址,然後發起請求
#restTemplate.getForObject("http://EUREKA-CLIENT-FOOD/getHunanCuisine", String.class);#如果對方寫提供了應用名稱,則也要在URL中加上
EUREKA-CLIENT-FOOD:
ribbon:
#隨機規則,對 EUREKA-CLIENT-FOOD 微服務下的節點隨機訪問
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
listOfServers: 192.168.3.6:9396,192.168.3.6:9397 #請求的服務地址,ip:port,多個時使用 逗號 隔開
#注意不用帶應用名稱,而是在代碼中寫應用名
4、eureka-client-cat 其它位置都不需要修改。後臺代碼使用:
String foodMenu = restTemplate.getForObject("http://EUREKA-CLIENT-FOOD/getHunanCuisine", String.class);
因爲 restTemplate 具有負載均衡的能力,所以會根據標識 EUREKA-CLIENT-FOOD 查找配置文件中的服務器實際地址(listOfServers),然後根據策略發起請求。後臺代碼不變
5、此時因爲沒有使用 Eureka 客戶端,所以 Eureka 註冊中心是沒有 eureka-client-cat 微服務的,但是並不影響對 EUREKA-CLIENT-FOOD 的訪問。
總結:沒有使用 Eureka 時,先導入 Ribbon 組件,然後修改配置文件添加服務器列表,最後使用具有負載均衡能力的 RestTemplate 向目標微服務發起 http 請求。
已有 Eureka 時
1、官網文檔 Disable Eureka Use in Ribbon,對於項目中已經使用了 Eureka 時,需要 Ribbon 禁用 Eureka ,配置如下:
ribbon:
eureka:
enabled: false
2、ribbon.eureka.enabled=false 是 Ribbon 不再使用 Eureka 發現的服務,而使用自己 stores.ribbon.listOfServers 配置的服務。
所以僅僅是 Ribbon 不再依賴 Eureka,而不是項目中禁用 Eureka。
3、修改 eureka-client-cat 的 pom.xml 文件重新添加 spring-cloud-starter-netflix-eureka-client,然後修改配置文件如下:
server:
port: 9394
spring:
application:
name: eureka-client-cat #微服務名稱
eureka:
client:
service-url:
defaultZone: http://localhost:9393/eureka/ #eureka 服務器地址
instance:
prefer-ip-address: true # IP 地址代替主機名註冊
instance-id: changSha-cat # 微服務實例id名稱
#EUREKA-CLIENT-FOOD :雖然 Ribbon 脫離 Eureka 使用可以自定義標識符,但還是建議寫成對方的微服務名稱
EUREKA-CLIENT-FOOD:
ribbon:
#隨機規則,對 EUREKA-CLIENT-FOOD 微服務下的節點隨機訪問
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
listOfServers: 192.168.3.6:9396 #請求的服務地址,ip:port,多個時使用 逗號 隔開。不要帶應用名稱
ribbon:
eureka:
enabled: false #Ribbon 禁用 Eureka,禁用後 Ribbon 自己的 *.ribbon.listOfServers 服務配置纔會生效
總結:有 Eureka 與沒有 Eureka 相比多了一個 Ribbon 禁用 Eureka 的操作,其餘是一樣的。
後臺代碼:String foodMenu = restTemplate.getForObject("http://EUREKA-CLIENT-FOOD/getHunanCuisine", String.class);(如果有應用名稱,則在 url 中加上)
原理:Ribbon 沒有脫離 Eureka 時,負載均衡請求的服務名稱 "EUREKA-CLIENT-FOOD" 會自動從 Eureka 客戶端服務發現的服務列表中進行查詢解析,然後根據實際地址發起請求。當 Ribbon 脫離了 Eureka 時,顯然無法再從 Eureka 發現的服務列表中獲取,所以需要在配置文件中使用 *.ribbon.listOfServers 進行服務配置,配置服務實際的 ip 與 端口。
因爲 EUREKA-CLIENT-FOOD.ribbon.listOfServers 只配置了一個服務器地址,所以永遠都是請求它。
Using the Ribbon API Directly(直接使用 Ribbon API)
1、可以通過 LoadBalancerClient API 來獲取請求的服務實例 org.springframework.cloud.client.ServiceInstance。
2、直接 @Autowired、@Resource 從容器中獲取 org.springframework.cloud.client.loadbalancer.LoadBalancerClient 使用即可。
3、官網文檔 "Using the Ribbon API Directly" 已經寫的很詳細,這裏在依樣畫葫蘆:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
@RestController
public class SystemController {
//獲取容器中創建好的 RestTemplate 實例
@Resource
private RestTemplate restTemplate;
//默認已經在容器中創建好了實例,直接獲取即可
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* localhost:9394/loadBalancerClient
*
* @return
*/
@GetMapping("loadBalancerClient")
public String testLoadBalancerClient() {
//choose(String serviceId):服務id,沒有脫離 Eureka 時,這裏通常就是對方服務名稱,即 spring.application 屬性值
//當 Ribbon 脫離 Eureka 時,服務id 就是與自己的配置文件 xxx.ribbon.listOfServers 中的 xxx 保持一致
ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-CLIENT-FOOD");
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
String instanceId = serviceInstance.getInstanceId();
String serviceId = serviceInstance.getServiceId();
URI uri = serviceInstance.getUri();
URI storesUri = URI.create(String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort()));
System.out.println("host:" + host);
System.out.println("port:" + port);
System.out.println("instanceId:" + instanceId);
System.out.println("serviceId:" + serviceId);
System.out.println("uri:" + uri);
System.out.println("storesUri:" + storesUri);
return "";
}
}
//控制檯輸出如下:
host:192.168.3.6
port:9395
instanceId:192.168.3.6:9395
serviceId:wmx
uri:http://192.168.3.6:9395
storesUri:http://192.168.3.6:9395
演示源碼 github 地址:https://github.com/wangmaoxiong/ribbon_study