自定義Ribbon負載均衡
一. 按照權重實現負載均衡
ribbon本身是沒有權重的概念的, 那么如何才能實現代用權重的負載均衡呢?
我們在nacos中, 服務其的集群有一個權重的概念, 當給服務器設置了權重, 那么流量就可以根據權重比例分配到服務器上.
1. 先來看看如何自定義一個負載均衡策略.
首先是繼承自AbstractLoadBalancerRule. 從下面這張圖可以看出, ribbon自定義的策略, 最終都繼承自這個類, 這個類封裝了負載均衡策略的公共方法.
在nacos中可以配置服務器的權重
在nacos中,有兩個重要的類, 一個是NameService, 一個是ConfigService
- NameService: 注冊中心
- ConfigService: 配置中心
二. 實現帶有權重的負載均衡器
第一步: 自定義一個帶有權重的負載均衡器MyWeightRule
package com.lxl.www.gateway.myrule; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; /** * 自定義一個權重負載均衡策略 */ @Slf4j public class MyWeightRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { // 讀取配置文件, 並且初始化, ribbon內部基本上用不上 } /** * 這個方法是實現負載均衡策略的方法 * * @param * @return */ @Override public Server choose(Object key) { try { log.info("key:{}", key); // 調用父類方法, 獲取當前使用的負載均衡器 BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // 獲取當前服務的名稱 String serviceName = baseLoadBalancer.getName(); /** * namingService: 獲取nacos的服務發現API */ NamingService namingService = discoveryProperties.namingServiceInstance(); /** * 根據名稱獲取服務發現實例 * 在selectOneHealthyInstance中, nacos實現了權重的負載均衡算法 */ Instance instance = namingService.selectOneHealthyInstance(serviceName); return new NacosServer(instance); } catch (NacosException e) { e.printStackTrace(); } return null; } }
第二步: 啟用自定義的負載均衡器應用
修改自定義的ribbon config.
package com.lxl.www.gateway.config; import com.lxl.www.ribbonconfig.GlobalRibbonConfig; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.context.annotation.Configuration; @Configuration /*@RibbonClients(value = { @RibbonClient(name="product", configuration = ProductConfiguration.class), @RibbonClient(name = "customer", configuration = CustomerConfiguration.class) })*/ // 使用全局的配置 @RibbonClients(defaultConfiguration = GlobalRibbonConfig.class) public class CustomeRibbonConfig { }
設置為全局配置GlobalRibbonConfig.class
然后在全局配置中,我們執行當前使用的負載均衡策略是自定義的權重負載均衡策略
package com.lxl.www.ribbonconfig; import com.lxl.www.gateway.myrule.MyWeightRule; import com.netflix.loadbalancer.IRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GlobalRibbonConfig { @Bean public IRule getRule() { // 實現帶有權重的負載均衡策略 return new MyWeightRule(); } }
第三步:啟動服務, 並設置nacos權重
我們看到啟動了兩台product, 一台order. 接下來設置product兩台實例的權重
我們看到一個設置為0.1, 另一個是0.9, 也就是說如果有10次請求, 基本上都是會打到8083端口上的.
第四步: 瀏覽器訪問連接, 測試結果
http://localhost:8080/get/product
觸發了10次請求, 基本上都打到了8083服務器上.
三 實現同集群優先調用原則的負載均衡器
盡量避免跨集群調用
比如, 南京集群的product優先調用南京集群的order . 北京集群的product優先調用北京集群的order.
實現如上圖所示的功能
我們有product服務和order服務, 假設各有10台. product和order有5台部署在北京集群上, 另外5台部署在南京集群上.
那么當有請求打到南京的product的時候, 那么他調用order要優先調用南京集群的, 南京集群沒有了, 在調用北京集群的.
當有請求打到北京的product的是偶, 優先調用北京集群的order, 北京沒有找到, 再去調用南京的order.
第一步: 自定義一個同集群優先策略的負載均衡器
package com.lxl.www.gateway.myrule; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.omg.PortableInterceptor.INACTIVE; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * 同集群優先調用--負載均衡策略 */ @Slf4j public class TheSameClusterPriorityRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * 主要實現choose方法 * * @param o * @return */ @Override public Server choose(Object o) { try { // 第一步: 獲取服務所在的集群名稱 String clusterName = discoveryProperties.getClusterName(); // 第二步: 獲取當前負載均衡器 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // 第三步: 獲取當前服務的實例名稱 String serviceName = loadBalancer.getName(); // 第四步: 獲取nacos client服務注冊發現api NamingService namingService = discoveryProperties.namingServiceInstance(); // 第五步: 通過namingService獲取當前注冊的所有服務實例 List<Instance> allInstances = namingService.getAllInstances(serviceName); List<Instance> instanceList = new ArrayList<>(); // 第六步: 過濾篩選同集群下的服務實例 for (Instance instance: allInstances) { if (StringUtils.endsWithIgnoreCase(instance.getClusterName(), clusterName)) { instanceList.add(instance); } } Instance toBeChooseInstance; // 第七步: 選擇合適的服務實例 if (instanceList == null || instanceList.size() == 0) { // 從其他集群中隨機選擇一個服務實例 toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(allInstances); log.info("跨集群調用---{}", toBeChooseInstance.getPort()); } else { // 從本集群中隨機選擇一個服務實例 toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(instanceList); log.info("同集群調用---{}", toBeChooseInstance.getPort()); } return new NacosServer(toBeChooseInstance); } catch (NacosException e) { e.printStackTrace(); } return null; } }
package com.lxl.www.ribbon.myrule; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.naming.core.Balancer; import java.util.List; /** * 根據權重隨機選擇一個實例 */ public class WeightedBalancer extends Balancer { /** * 根據隨機權重策略, 從一群服務器中選擇一個 * @return */ public static Instance chooseInstanceByRandomWeight (List<Instance> instanceList) { // 這是父類Balancer自帶的根據隨機權重獲取服務的方法. return getHostByRandomWeight(instanceList); } }
第二步: 設置當前的負載均衡策略為同集群優先策略
@Configuration /*@RibbonClients(value = { @RibbonClient(name="product", configuration = ProductConfiguration.class), @RibbonClient(name = "customer", configuration = CustomerConfiguration.class) })*/ // 使用全局的配置 @RibbonClients(defaultConfiguration = GlobalRibbonConfig.class) public class CustomeRibbonConfig { }
@Configuration public class GlobalRibbonConfig { @Bean public IRule getRule() { // 實現帶有權重的負載均衡策略 //return new MyWeightRule(); // 實現同集群優先的服務實例 return new TheSameClusterPriorityRule(); } }
第三步: 啟動服務
可以看到order服務實例有1台, 所屬集群是BJ-CLUSTER, product服務有4台. BJ-CLUSTER和NJ-CLUSTER各兩台
第四步: 訪問請求
http://localhost:8080/get/product
由於order服務是BJ-CLUSTER, 所以, 我們看到流量是隨機打到了ORDER的BJ-CLUSTER集群上.
停止BJ-CLUSTER集群的兩台實例, 現在BJ-CLUSTER集群上沒有order的服務實例了, 這是在請求, 流量就會達到NJ-CLUSTER上.
四. 金絲雀發布--實現同版本同集群優先負載均衡策略
金絲雀發布, 也稱為灰度發布, 是什么意思呢?
首先, 我們的product服務實例有100台, order服務實例有100台. 現在都是在v1 版本上
然后新開發了一個變化很大的功能, 為了保證效果, 要進行灰度測試
在product-center上發布了5台, 在order-center上發布了5台
那么現在用戶的流量過來了, 我們要設定一部分用戶, 請求的是v1版本的流量, 而且全部都走v1版本, product-center, order-center都是v1版本
如果過來的用戶, 請求的v2版本的流量, 那么product和order都走v2版本.
下面我們要實現的功能描述如下:
1. 同集群,同版本優先調用,
2. 沒有同集群的服務提供者, 進行跨集群,同版本調用
3. 不可以進行不同版本間的調用
也就是說: 南京集群V1版本的product優先調用南京集群V1版本的order, 如果沒有南京集群V1版本的order, 那么就調用北京集群V1版本的order, 那么能調用v2版本的么? 當然不行.
下面來具體實現一下
第一步: 實現帶有版本的集群優先策略的負載均衡算法
package com.lxl.www.gateway.myrule; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; /** * 同集群優先帶版本的負載均衡策略 */ @Slf4j public class TheSameClusterPriorityWithVersionRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object o) { try { // 第一步: 獲取當前服務的集群名稱 和 服務的版本號 String clusterName = discoveryProperties.getClusterName(); String version = discoveryProperties.getMetadata().get("version"); // 第二步: 獲取當前服務的負載均衡器 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // 第三步: 獲取目標服務的服務名 String serviceName = loadBalancer.getName(); // 第四步: 獲取nacos提供的服務注冊api NamingService namingService = discoveryProperties.namingServiceInstance(); // 第五步: 獲取所有服務名為serviceName的服務實例 List<Instance> allInstances = namingService.getAllInstances(serviceName); // 第六步: 過濾相同版本的服務實例 List<Instance> sameVersionInstance = new ArrayList<>(); for (Instance instance : allInstances) { if (instance.getMetadata().get("version").equals(version)) { sameVersionInstance.add(instance); } } // 第七步: 過濾有相同集群的服務實例 List<Instance> sameClusterInstance = new ArrayList<>(); for (Instance instance: sameVersionInstance) { if (instance.getClusterName().equals(clusterName)) { sameClusterInstance.add(instance); } } // 第八步: 選擇合適的服務實例 Instance toBeChooseInstanc; if (sameClusterInstance == null || sameClusterInstance.size() == 0) { toBeChooseInstanc = WeightedBalancer.chooseInstanceByRandomWeight(sameVersionInstance); log.info("同版本不同集群的服務實例--{}", toBeChooseInstanc.getPort()); } else { toBeChooseInstanc = WeightedBalancer.chooseInstanceByRandomWeight(sameClusterInstance); log.info("同版本同集群服務實例--{}", toBeChooseInstanc.getPort()); } return new NacosServer(toBeChooseInstanc); } catch (NacosException e) { e.printStackTrace(); } return null; } }
第二步: 啟用自定義負載均衡策略
@Configuration /*@RibbonClients(value = { @RibbonClient(name="product", configuration = ProductConfiguration.class), @RibbonClient(name = "customer", configuration = CustomerConfiguration.class) })*/ // 使用全局的配置 @RibbonClients(defaultConfiguration = GlobalRibbonConfig.class) public class CustomeRibbonConfig { }
@Configuration public class GlobalRibbonConfig { @Bean public IRule getRule() { // 實現帶有權重的負載均衡策略 //return new MyWeightRule(); // 實現同集群優先的負載均衡策略 // return new TheSameClusterPriorityRule(); // 實現同版本集群優先的服務負載均衡策略 return new TheSameClusterPriorityWithVersionRule(); } }
第三步:啟動服務
如圖, 我們啟動了兩個服務, 一個order, 一個product, 其中order啟動了1個實例, product啟動了4個實例.
下面來看看order實例的詳情
屬於北京集群, 版本號是v1版本
再來看看product集群的詳情
北京集群有兩台實例, 一個版本號是v1,另一個是v2
南京集群有兩台實例, 一個是v1, 另一個也是v2
第四步: 現在發起請求
http://localhost:8080/get/product
order集群是北京, 版本是version1. 那么服務應該達到哪台服務器呢? 應該打到北京集群的v1版本服務,端口號是8081的服務器.
果然,請求達到了8081服務上,
加入這時候, 北京集群的8081宕機了, 流量應該達到南京集群的v1版本的集群上, 端口號是8083
果然是8083.
那么如果8083也宕機了, 流量會打到v2服務器上么?
沒錯,不會的, 報錯了, 沒有服務