目錄
- 第一章 Ribbon介紹
- 1.1、什么是Ribbon
- 1.2、為啥用Ribbon
- 第二章 Ribbon入門案例
- 2.1、專案準備
- 2.2、添加服務提供者
- 2.3、修改服務消費者
- 2.3.1、方式一:LoadBalancerClient
- 2.3.2、方式二:@LoadBalanced
- 第三章 Ribbon負載均衡策略
- 3.1、輪詢策略(默認)
- 3.2、權重輪詢策略
- 3.3、隨機策略
- 3.4、最少并發數策略
- 3.5、重試策略
- 3.6、可用性敏感策略
- 3.7、區域敏感性策略
- 第四章 Ribbon負載均衡設定
- 4.1、全域替換
- 4.2、區域替換
- 第五章 Ribbon手寫負載均衡
- 5.1、負載均衡演算法
- 5.2、輪詢策略原始碼
- 5.3、手寫負載均衡
- 5.4、測驗最終效果
配套資料,免費下載
鏈接:https://pan.baidu.com/s/1la_3-HW-UvliDRJzfBcP_w
提取碼:lxfx
復制這段內容后打開百度網盤手機App,操作更方便哦
第一章 Ribbon介紹
1.1、什么是Ribbon
Ribbon 是一個基于 HTTP 和 TCP 的客服端負載均衡工具,它是基于 Netflix Ribbon 實作的,它不像 Spring Cloud 服務注冊中心、配置中心、API 網關那樣獨立部署,但是它幾乎存在于每個 Spring Cloud 微服務中,包括 Feign 提供的宣告式服務呼叫也是基于該 Ribbon 實作的,
Ribbon 默認提供很多種負載均衡演算法,例如輪詢、隨機等等,甚至包含自定義的負載均衡演算法,
1.2、為啥用Ribbon
Ribbon 提供了一套微服務的負載均衡解決方案,目前業界主流的負載均衡方案可分成兩類:
- 集中式負載均衡(服務器負載均衡),即在 consumer 和 provider 之間使用獨立的負載均衡設施(可以是硬體,如 F5,也可以是軟體,如 nginx),由該設施負責把訪問請求通過某種策略轉發至 provider;
- 行程內負載均衡(客戶端負載均衡),將負載均衡邏輯集成到 consumer,consumer 從服務注冊中心獲知有哪些地址可用,然后自己再從這些地址中選擇出一個合適的 provider,Ribbon 屬于后者,它只是一個類別庫,集成于 consumer 行程,consumer 通過它來獲取 provider 的地址,
第二章 Ribbon入門案例
2.1、專案準備
我們接下來的所有操作均是在Eureka最后完成的工程上進行操作,相關代碼請到配套資料中尋找,

2.2、添加服務提供者
既然我們要做服務的負載均衡,那肯定需要有很多服務,一個服務再怎么負載均衡還是他自己,因此,我們需要在原工程的基礎上在添加一個服務提供者,配置基本和service-provider8001差不多,只不過埠不一樣而已,接下來會給出具體不一樣的配置,
(1)修改啟動類為ServiceProvider8002Application
@EnableEurekaClient
@SpringBootApplication
public class ServiceProvider8002Application {
public static void main(String[] args) {
SpringApplication.run(ServiceProvider8002Application.class);
}
}
(2)修改組態檔application.yaml
server:
port: 8002
spring:
application:
#該名稱在集群模式下應該保持一致
name: service-provider
#度量指標監控與健康檢查
management:
endpoints:
web:
exposure:
#開啟 shutdown 端點訪問
include: shutdown
endpoint:
#開啟 shutdown 實作優雅停服
shutdown:
enabled: true
eureka:
instance:
#是否使用 ip 地址注冊
prefer-ip-address: true
#該實體注冊到服務中心的唯一ID
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#設定服務注冊中心地址
service-url:
defaultZone: http://root:123456@eureka-server7001.com:7001/eureka/,http://root:123456@eureka-server7002.com:7002/eureka/
(3)分別修改service-provider8001和service-provider8002的控制層ProductController 代碼,都加入一段新的控制器方法,代碼如下:
service-provider8001的ProductController
@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
return "8001 findByPid ...";
}
service-provider8002的ProductController
@RequestMapping("/provider/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
return "8002 findByPid ...";
}
(4)啟動注冊中心
- 啟動eureka-server7001
- 啟動eureka-server7002
(5)啟動服務提供者
- 啟動service-provider8001
- 啟動service-provider8002
(6)查看服務是否注冊到注冊中心,登錄賬戶為root,登錄密碼為123456
在瀏覽器打開地址:http://localhost:7001/

2.3、修改服務消費者
2.3.1、方式一:LoadBalancerClient
(1)修改service-consumer9001的ProductController控制器代碼,在里邊新增以下代碼
// Ribbon 負載均衡器
// import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@Autowired
private LoadBalancerClient loadBalancerClient;
@RequestMapping("/consumer/product/findByPid")
public String findByPid(@RequestParam("pid") Integer pid) {
//從注冊中心獲取一個SERVICE-PROVIDER服務的實體物件
ServiceInstance si = loadBalancerClient.choose("SERVICE-PROVIDER");
if (si == null) {
return null;
}
String findByPidUrl = si.getUri() + "/provider/product/findByPid" + "?pid=" + pid;
String product = restTemplate.getForObject(findByPidUrl, String.class);
return product;
//這里只是做模擬回傳了一個字串,當然你也可以回傳一個商品物件,這取決于你
}
(2)啟動service-consumer9001,然后打開瀏覽器輸入地址訪問:http://localhost:9001/consumer/product/findByPid?pid=1

2.3.2、方式二:@LoadBalanced
(1)修改service-consumer9001的ServiceConsumer9001Application啟動類代碼,在里邊新增以下代碼
@EnableEurekaClient
@SpringBootApplication
public class ServiceConsumer9001Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9001Application.class);
}
@Bean
@LoadBalanced // Ribbbon 負載均衡注解,加上這個注解,restTemplate默認就有負載均衡的能力了
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
(2)修改service-consumer9001的ProductController控制器代碼,在里邊新增以下代碼
@RequestMapping("/consumer/product/findByPid2")
public String findByPid2(@RequestParam("pid") Integer pid) {
String findByPidUrl = "http://SERVICE-PROVIDER/provider/product/findByPid" + "?pid=" + pid;
String product = restTemplate.getForObject(findByPidUrl, String.class);
System.out.println(product);
return product;
}
(3)重啟service-consumer9001,然后打開瀏覽器輸入地址訪問:http://localhost:9001/consumer/product/findByPid2?pid=1

第三章 Ribbon負載均衡策略
3.1、輪詢策略(默認)
策略對應類名:RoundRobinRule
實作原理:輪詢策略表示每次都順序取下一個 provider,比如一共有 5 個 provider,第 1 次取第 1 個,第 2 次取第 2 個,第 3 次取第 3 個,以此類推,
3.2、權重輪詢策略
策略對應類名:WeightedResponseTimeRule
實作原理:
- 根據每個 provider 的回應時間分配一個權重,回應時間越長,權重越小,被選中的可能性越低,
- 原理:一開始為輪詢策略,并開啟一個計時器,每 30 秒收集一次每個 provider 的平均回應時間,當資訊足夠時,給每個 provider 附上一個權重,并按權重隨機選擇 provider,高權越重的 provider 會被高概率選中,
3.3、隨機策略
策略對應類名:RandomRule
實作原理:從 provider 串列中隨機選擇一個,
3.4、最少并發數策略
策略對應類名:BestAvailableRule
實作原理:選擇正在請求中的并發數最小的 provider,除非這個 provider 在熔斷中,
3.5、重試策略
策略對應類名:RetryRule
實作原理:其實就是輪詢策略的增強版,輪詢策略服務不可用時不做處理,重試策略服務不可用時會重新嘗試集群中的其他節點,
3.6、可用性敏感策略
策略對應類名:AvailabilityFilteringRule
實作原理:過濾性能差的 provider
- 第一種:過濾掉在 Eureka 中處于一直連接失敗的 provider,
- 第二種:過濾掉高并發(繁忙)的 provider,
3.7、區域敏感性策略
策略對應類名:ZoneAvoidanceRule
實作原理:
- 以一個區域為單位考察可用性,對于不可用的區域整個丟棄,從剩下區域中選可用的 provider,
- 如果這個 ip 區域內有一個或多個實體不可達或回應變慢,都會降低該 ip 區域內其他 ip 被選中的權重,
第四章 Ribbon負載均衡設定
4.1、全域替換
在啟動類或配置類中注入負載均衡策略物件,所有服務請求均使用該策略,
@EnableEurekaClient
@SpringBootApplication
public class ServiceConsumer9001Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9001Application.class);
}
@Bean
@LoadBalanced // Ribbbon 負載均衡注解,加上這個注解,restTemplate默認就有負載均衡的能力了
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public RandomRule randomRule() {
return new RandomRule();
}
}
重啟消費者然后多次訪問:http://localhost:9001/consumer/product/findByPid2?pid=1,結果如下:

4.2、區域替換
請注釋掉剛才的全域設定,避免影響結果,修改組態檔指定服務的負載均衡策略,格式:服務應用名.ribbon.NFLoadBalancerRuleClassName
# 負載均衡策略
# service-provider 為呼叫的服務的名稱
service-provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
重啟消費者然后多次訪問:http://localhost:9001/consumer/product/findByPid2?pid=1,結果如下:

第五章 Ribbon手寫負載均衡
5.1、負載均衡演算法

5.2、輪詢策略原始碼
核心:自旋鎖,private int incrementAndGetModulo(int modulo)方法用到了自旋鎖
定義:
自旋鎖(spin lock)是一種非阻塞鎖,也就是說,如果某執行緒需要獲取鎖,但該鎖已經被其他執行緒占用時,該執行緒不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取鎖,執行緒反復檢查鎖變數是否可用,由于執行緒在這一程序中保持執行,因此是一種忙等待,
作用:
自旋鎖避免了行程背景關系的調度開銷,執行緒一直處于用戶態,減少了用戶態到內核態的開銷與損耗(減少了背景關系切換),
適用場景:
1、多執行緒
2、使用者占有鎖的時間短
輪詢原始碼:
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
this.nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
5.3、手寫負載均衡
(1)創建一個插件包名字叫:com.caochenlei.plugin
(2)在這個插件包中新建類:MyLoadBalanced
(3)手寫負載均衡器來替代默認的負載均衡器注解@LoadBalanced,原始碼如下:
@Component
public class MyLoadBalanced {
private AtomicInteger atomicInteger = new AtomicInteger(0);
//獲取服務提供者實體下標
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
//使用自旋鎖進行累加操作
private final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
//第一個引數是期望值,第二個引數是修改值
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("訪問次數:" + next);
return next;
}
}
(4)在控制器類ProductController中新增如下測驗代碼:
@Autowired
private MyLoadBalanced loadBalanced;
@RequestMapping("/consumer/product/findByPid3")
public String findByPid3(@RequestParam("pid") Integer pid) {
//獲取注冊中心服務串列
List<ServiceInstance> instances = discoveryClient.getInstances("SERVICE-PROVIDER");
if (CollectionUtils.isEmpty(instances)) {
return null;
}
//使用自定義負載均衡器
ServiceInstance si = loadBalanced.instances(instances);
String findByPidUrl = si.getUri() + "/provider/product/findByPid" + "?pid=" + pid;
String product = restTemplate.getForObject(findByPidUrl, String.class);
System.out.println(product);
return product;
}
(5)注釋組態檔中配置的隨機策略
# 負載均衡策略
# SERVICE-PROVIDER 為呼叫的服務的名稱
#SERVICE-PROVIDER:
# ribbon:
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
(6)注釋Ribbon默認的負載均衡器注解
@EnableEurekaClient
@SpringBootApplication
public class ServiceConsumer9001Application {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumer9001Application.class);
}
@Bean
//@LoadBalanced // Ribbbon 負載均衡注解,加上這個注解,restTemplate默認就有負載均衡的能力了
public RestTemplate restTemplate(){
return new RestTemplate();
}
// @Bean
// public RandomRule randomRule() {
// return new RandomRule();
// }
}
5.4、測驗最終效果
重啟消費者然后多次訪問:http://localhost:9001/consumer/product/findByPid3?pid=1,結果如下:

注意:此時如果你訪問http://localhost:9001/consumer/product/findByPid2?pid=1,則會報錯,因為這個方法中使用的是默認的負載均衡器,剛才我們已經注釋掉了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/254923.html
標籤:java
