擴展Ribbon支持Nacos權重的三種方式
Nacos支持權重配置,這是個比較實用的功能,例如:
把性能差的機器權重設低,性能好的機器權重設高,讓請求優先打到性能高的機器上去;
某個實例出現異常時,把權重設低,排查問題,問題排查完再把權重恢復;
想要下線某個實例時,可先將該實例的權重設為0,這樣流量就不會打到該實例上了——此時再去關停該實例,這樣就能實現優雅下線啦。當然這是為Nacos量身定製的優雅下線方案——Spring Cloud中,
然而測試發現,Nacos權重配置對Spring Cloud Alibaba無效。也就是說,不管在Nacos控制台上如何配置,調用時都不管權重設置的。
Spring Cloud Alibaba通過整合Ribbon的方式,實現了負載均衡。所使用的負載均衡規則是 ZoneAvoidanceRule 。
本節來探討如何擴展Ribbon,讓其支持Nacos的權重配置,筆者總結了三種方案。
方案1:自己實現負載均衡規則
思路:
自己首先一個Ribbon負載均衡規則就可以了。
權重配置啥的,都可以在實例信息中獲取到。
自己基於權重配置,計算出一個實例即可。
代碼:
@Slf4j
public class NacosWeightRandomV1Rule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
Listservers = this.getLoadBalancer().getReachableServers();
ListinstanceWithWeights = servers.stream()
.map(server - {
// 註冊中心只用Nacos,沒同時用其他註冊中心(例如Eureka),理論上不會實現
if (!(server instanceof NacosServer)) {
log.error(參數非法,server = {}, server);
throw new IllegalArgumentException(參數非法,不是NacosServer實例!);
}
NacosServer nacosServer = (NacosServer) server;
Instance instance = nacosServer.getInstance();
double weight = instance.getWeight();
return new InstanceWithWeight(
server,
Double.valueOf(weight).intValue()
);
})
.collect(Collectors.toList());
Server server = this.weightRandom(instanceWithWeights);
log.info(選中的server = {}, server);
return server;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
private class InstanceWithWeight {
private Server server;
private Integer weight;
}
/**
* 根據權重隨機
* 演算法參考 https://blog.csdn.net/u011627980/article/details/79401026
*
* @param list 實例列表
* @return 隨機出來的結果
*/
private Server weightRandom(Listlist) {
Listinstances = Lists.newArrayList();
for (InstanceWithWeight instanceWithWeight : list) {
int weight = instanceWithWeight.getWeight();
for (int i = 0; i = weight; i ) {
instances.add(instanceWithWeight.getServer());
}
}
int i = new Random().nextInt(instances.size());
return instances.get(i);
}
}
WARNING
本段代碼存在優化空間,只是用來演示思考的過程,不建議用於生產,如打算使用本方案實現,請參考以下兩點優化:
簡單起見,我直接把double型的權重(weight),轉成了integer計算了,存在精度丟失。
InstanceWithWeight太重了,在 weightRandom 還得再兩層for循環,還挺吃內存的,建議百度其他權重隨機演算法優化。不過實際項目一個微服務一般也就三五個實例,所以其實內存消耗也能忍受。不優化問題也不大。
方案2:利用Nacos Client的能力[推薦]
思路:
在閱讀代碼Nacos源碼的過程中,發現Nacos Client本身就提供了負載均衡的能力,並且負載均衡演算法正是我們想要的根據權重選擇實例!
代碼在 com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance ,只要想辦法調用到這行代碼,就可以實現我們想要的功能啦!
代碼:
@Slf4j
public class NacosWeightRandomV2Rule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties discoveryProperties;
@Override
public Server choose(Object key) {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
try {
Instance instance = discoveryProperties.namingServiceInstance()
.selectOneHealthyInstance(name);
log.info(選中的instance = {}, instance);
/*
* instance轉server的邏輯參考自:
* org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList.instancesToServerList
*/
return new NacosServer(instance);
} catch (NacosException e) {
log.error(發生異常, e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
方案3:最暴力的玩法
思路:
在閱讀源碼的過程中,發現如下代碼:
// 來自:org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList#getServers
private ListgetServers() {
try {
Listinstances = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId, true);
return instancesToServerList(instances);
}
catch (Exception e) {
throw new IllegalStateException(
Can not get service instances from nacos, serviceId= serviceId,
e);
}
}
這個NacosServerList 就是給Ribbon去做負載均衡的」數據源」!如果把這裡的代碼改成 com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance 不也可以實現我們想要的功能嗎?
也就是說,交給Ribbon的List永遠只有1個實例!這樣不管Ribbon用什麼負載均衡,都隨他便了。
代碼:
1 參考NacosServerList的代碼,重寫NacosRibbonServerList
/**
* 參考org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList
*/
@Slf4j
public class NacosRibbonServerList extends AbstractServerList{
private NacosDiscoveryProperties discoveryProperties;
private String serviceId;
public NacosRibbonServerList(NacosDiscoveryProperties discoveryProperties) {
this.discoveryProperties = discoveryProperties;
}
@Override
public ListgetInitialListOfServers() {
return getServers();
}
@Override
public ListgetUpdatedListOfServers() {
return getServers();
}
private ListgetServers() {
try {
Instance instance = discoveryProperties.namingServiceInstance()
.selectOneHealthyInstance(serviceId, true);
log.debug(選擇的instance = {}, instance);
return instancesToServerList(
Lists.newArrayList(instance)
);
} catch (Exception e) {
throw new IllegalStateException(
Can not get service instances from nacos, serviceId= serviceId,
e);
}
}
private ListinstancesToServerList(Listinstances) {
Listresult = new ArrayList();
if (null == instances) {
return result;
}
for (Instance instance : instances) {
result.add(new NacosServer(instance));
}
return result;
}
public String getServiceId() {
return serviceId;
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
this.serviceId = iClientConfig.getClientName();
}
}
2 編寫配置類
/**
* 參考:org.springframework.cloud.alibaba.nacos.ribbon.NacosRibbonClientConfiguration
*/
@Configuration
public class NacosRibbonClientExtendConfiguration {
@Bean
public ServerList?ribbonServerList(IClientConfig config, NacosDiscoveryProperties nacosDiscoveryProperties) {
NacosRibbonServerList serverList = new NacosRibbonServerList(nacosDiscoveryProperties);
serverList.initWithNiwsConfig(config);
return serverList;
}
}
3 添加註解,讓上面的NacosRibbonClientExtendConfiguration成為Ribbon的默認配置。
// ...其他註解
@RibbonClients(defaultConfiguration = NacosRibbonClientExtendConfiguration.class)
public class ConsumerMovieApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
注意 :
務必注意,將 NacosRibbonClientExtendConfiguration 放在ComponentScan上下文(默認是啟動類所在包及其子包)以外!!!
總結與對比
方案1:是最容易想到的玩法。
方案2:是個人目前最喜歡的方案。首先簡單,並且都是復用Nacos/Ribbon現有的代碼——而Ribbon/Nacos本身都是來自於大公司生產環境,經過嚴苛的生產考驗。
方案3:太暴力了,把Ribbon架空了。此方案中,扔給Ribbon做負載均衡選擇時,List只有1個元素,不管用什麼演算法去算,最後總是會返回這個元素!
思考
既然Nacos Client已經有負載均衡的能力,Spring Cloud Alibaba為什麼還要去整合Ribbon呢?
個人認為,這主要是為了符合Spring Cloud標準。Spring Cloud Commons有個子項目 spring-cloud-loadbalancer ,該項目制定了標準,用來適配各種客戶端負載均衡器(雖然目前實現只有Ribbon,但Hoxton就會有替代的實現了)。
Spring Cloud Alibaba遵循了這一標準,所以整合了Ribbon,而沒有去使用Nacos Client提供的負載均衡能力。
TAG:千鋒JAVA開發學院 |