當前位置:
首頁 > 知識 > 擴展Ribbon支持Nacos權重的三種方式

擴展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提供的負載均衡能力。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 千鋒JAVA開發學院 的精彩文章:

你所不知道的HelloWorld背後的原理

TAG:千鋒JAVA開發學院 |