Nacos支持權(quán)重配置赖条,這是個(gè)比較實(shí)用的功能失乾,例如:
- 把性能差的機(jī)器權(quán)重設(shè)低,性能好的機(jī)器權(quán)重設(shè)高纬乍,讓請(qǐng)求優(yōu)先打到性能高的機(jī)器上去碱茁;
- 某個(gè)實(shí)例出現(xiàn)異常時(shí),把權(quán)重設(shè)低仿贬,排查問(wèn)題纽竣,問(wèn)題排查完再把權(quán)重恢復(fù);
- 想要下線某個(gè)實(shí)例時(shí)诅蝶,可先將該實(shí)例的權(quán)重設(shè)為0退个,這樣流量就不會(huì)打到該實(shí)例上了——此時(shí)再去關(guān)停該實(shí)例,這樣就能實(shí)現(xiàn)優(yōu)雅下線啦调炬。當(dāng)然這是為Nacos量身定制的優(yōu)雅下線方案——Spring Cloud中,要想實(shí)現(xiàn)優(yōu)雅下線還有很多姿勢(shì)舱馅,詳見:《實(shí)用技巧:Spring Cloud中缰泡,如何優(yōu)雅下線微服務(wù)?》 代嗤,里面筆者總結(jié)了四種優(yōu)雅下線的方式棘钞。
然而測(cè)試發(fā)現(xiàn),Nacos權(quán)重配置對(duì)Spring Cloud Alibaba無(wú)效干毅。也就是說(shuō)宜猜,不管在Nacos控制臺(tái)上如何配置,調(diào)用時(shí)都不管權(quán)重設(shè)置的硝逢。
Spring Cloud Alibaba通過(guò)整合Ribbon的方式姨拥,實(shí)現(xiàn)了負(fù)載均衡绅喉。所使用的負(fù)載均衡規(guī)則是 ZoneAvoidanceRule
。
本節(jié)來(lái)探討如何擴(kuò)展Ribbon叫乌,讓其支持Nacos的權(quán)重配置柴罐,筆者總結(jié)了三種方案。
方案1:自己實(shí)現(xiàn)負(fù)載均衡規(guī)則
思路:
自己首先一個(gè)Ribbon負(fù)載均衡規(guī)則就可以了憨奸。
- 權(quán)重配置啥的革屠,都可以在實(shí)例信息中獲取到。
- 自己基于權(quán)重配置排宰,計(jì)算出一個(gè)實(shí)例即可似芝。
代碼:
@Slf4j
public class NacosWeightRandomV1Rule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
List<Server> servers = this.getLoadBalancer().getReachableServers();
List<InstanceWithWeight> instanceWithWeights = servers.stream()
.map(server -> {
// 注冊(cè)中心只用Nacos,沒(méi)同時(shí)用其他注冊(cè)中心(例如Eureka)板甘,理論上不會(huì)實(shí)現(xiàn)
if (!(server instanceof NacosServer)) {
log.error("參數(shù)非法党瓮,server = {}", server);
throw new IllegalArgumentException("參數(shù)非法,不是NacosServer實(shí)例虾啦!");
}
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;
}
/**
* 根據(jù)權(quán)重隨機(jī)
* 算法參考 https://blog.csdn.net/u011627980/article/details/79401026
*
* @param list 實(shí)例列表
* @return 隨機(jī)出來(lái)的結(jié)果
*/
private Server weightRandom(List<InstanceWithWeight> list) {
List<Server> instances = 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
本段代碼存在優(yōu)化空間麻诀,只是用來(lái)演示思考的過(guò)程,不建議用于生產(chǎn)傲醉,如打算使用本方案實(shí)現(xiàn)蝇闭,請(qǐng)參考以下兩點(diǎn)優(yōu)化:
- 簡(jiǎn)單起見,我直接把double型的權(quán)重(weight)硬毕,轉(zhuǎn)成了integer計(jì)算了呻引,存在精度丟失。
- InstanceWithWeight太重了吐咳,在
weightRandom
還得再兩層for循環(huán)逻悠,還挺吃內(nèi)存的,建議百度其他權(quán)重隨機(jī)算法優(yōu)化韭脊。不過(guò)實(shí)際項(xiàng)目一個(gè)微服務(wù)一般也就三五個(gè)實(shí)例童谒,所以其實(shí)內(nèi)存消耗也能忍受。不優(yōu)化問(wèn)題也不大沪羔。
方案2:利用Nacos Client的能力[推薦]
思路:
在閱讀代碼Nacos源碼的過(guò)程中饥伊,發(fā)現(xiàn)Nacos Client本身就提供了負(fù)載均衡的能力,并且負(fù)載均衡算法正是我們想要的根據(jù)權(quán)重選擇實(shí)例蔫饰!
代碼在 com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance
琅豆,只要想辦法調(diào)用到這行代碼,就可以實(shí)現(xiàn)我們想要的功能啦篓吁!
代碼:
@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轉(zhuǎn)server的邏輯參考自:
* org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList.instancesToServerList
*/
return new NacosServer(instance);
} catch (NacosException e) {
log.error("發(fā)生異常", e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
方案3:最暴力的玩法
思路:
在閱讀源碼的過(guò)程中茫因,發(fā)現(xiàn)如下代碼:
// 來(lái)自:org.springframework.cloud.alibaba.nacos.ribbon.NacosServerList#getServers
private List<NacosServer> getServers() {
try {
List<Instance> instances = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId, true);
return instancesToServerList(instances);
}
catch (Exception e) {
throw new IllegalStateException(
"Can not get service instances from nacos, serviceId=" + serviceId,
e);
}
}
這個(gè)NacosServerList
就是給Ribbon去做負(fù)載均衡的”數(shù)據(jù)源”!如果把這里的代碼改成 com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance
不也可以實(shí)現(xiàn)我們想要的功能嗎杖剪?
也就是說(shuō)冻押,交給Ribbon的List永遠(yuǎn)只有1個(gè)實(shí)例驰贷!這樣不管Ribbon用什么負(fù)載均衡,都隨他便了翼雀。
代碼:
1 參考NacosServerList的代碼饱苟,重寫NacosRibbonServerList
/**
* 參考o(jì)rg.springframework.cloud.alibaba.nacos.ribbon.NacosServerList
*/
@Slf4j
public class NacosRibbonServerList extends AbstractServerList<NacosServer> {
private NacosDiscoveryProperties discoveryProperties;
private String serviceId;
public NacosRibbonServerList(NacosDiscoveryProperties discoveryProperties) {
this.discoveryProperties = discoveryProperties;
}
@Override
public List<NacosServer> getInitialListOfServers() {
return getServers();
}
@Override
public List<NacosServer> getUpdatedListOfServers() {
return getServers();
}
private List<NacosServer> getServers() {
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 List<NacosServer> instancesToServerList(List<Instance> instances) {
List<NacosServer> result = 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的默認(rèn)配置狼渊。
// ...其他注解
@RibbonClients(defaultConfiguration = NacosRibbonClientExtendConfiguration.class)
public class ConsumerMovieApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
注意 :
務(wù)必注意箱熬,將 NacosRibbonClientExtendConfiguration
放在ComponentScan上下文(默認(rèn)是啟動(dòng)類所在包及其子包)以外!1芬亍城须!
總結(jié)與對(duì)比
- 方案1:是最容易想到的玩法。
- 方案2:是個(gè)人目前最喜歡的方案米苹。首先簡(jiǎn)單糕伐,并且都是復(fù)用Nacos/Ribbon現(xiàn)有的代碼——而Ribbon/Nacos本身都是來(lái)自于大公司生產(chǎn)環(huán)境,經(jīng)過(guò)嚴(yán)苛的生產(chǎn)考驗(yàn)蘸嘶。
- 方案3:太暴力了良瞧,把Ribbon架空了。此方案中训唱,扔給Ribbon做負(fù)載均衡選擇時(shí)褥蚯,List只有1個(gè)元素,不管用什么算法去算况增,最后總是會(huì)返回這個(gè)元素赞庶!
思考
既然Nacos Client已經(jīng)有負(fù)載均衡的能力,Spring Cloud Alibaba為什么還要去整合Ribbon呢澳骤?
個(gè)人認(rèn)為歧强,這主要是為了符合Spring Cloud標(biāo)準(zhǔn)。Spring Cloud Commons有個(gè)子項(xiàng)目
spring-cloud-loadbalancer
为肮,該項(xiàng)目制定了標(biāo)準(zhǔn)摊册,用來(lái)適配各種客戶端負(fù)載均衡器(雖然目前實(shí)現(xiàn)只有Ribbon,但Hoxton就會(huì)有替代的實(shí)現(xiàn)了)颊艳。Spring Cloud Alibaba遵循了這一標(biāo)準(zhǔn)丧靡,所以整合了Ribbon,而沒(méi)有去使用Nacos Client提供的負(fù)載均衡能力籽暇。