一点背景
Nacos支持权重配置,是比较实用的功能。例如可以把好的机器权重升高,让硬件资源好的服务器享受更高的优先级;在某个服务器出现异常的时候可以降低这个服务器的权重或者暂时停止这个服务器的流量。
Nacos是自带Ribbon的。Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
Nacos配置负载均衡Demo
继续采用之前的项目进行改进来跑负载均衡。之前的项目:Nacos安装(docker),运行,持久化(单机MySQL),Eureka迁移至Nacos
总体结构
首先是两个服务provider:
ProviderApplication.java
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@RestController
class EchoController {
@Value("${server.port}")
private String port;
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) {
System.out.println(string);
return "Hello Nacos Discovery00 " + "server port : " + port + string;
}
}
}
Controller返回一行字符串+当前服务的端口。
配置文件:
application.properties:
server.port=8080
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
另外一个服务Provider01提供相同的服务,端口在8081。
定义一个消费者Consumer:
ConsumerApplication.java
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/echos", method = RequestMethod.GET)
public String echo() {
// System.out.println("str:"+str);
return restTemplate.getForObject("http://service-provider/echo/mxb", String.class);
}
}
}
他的作用是调用服务service-provider。
配置文件:
application.properties:
server.port=8070
spring.application.name=service-consumer
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
他运行在8070。
这样就可以运行起来服务了,效果如下:
把他们全部run起来:
我们访问 http://localhost:8070/echos,可以看到:
刷新一下可以看到:
默认方式下是轮询,所以每次刷新都会换一个服务。
在Nacos控制台上可以看到两个服务,其中provider的实例数是两个。
实现在Nacos控制台上配置服务权重
Nacos默认是轮询。此时在控制台上修改服务的权重是无效的,仍然是轮询。
可以在消费者Consumer的代码上面加上:
ConsumerApplication.java
@Bean
@Scope(value = "prototype")
public IRule loadBalanceRule()
{
return new NacosRule();
}
这样就可以支持在控制台上手动配置权重了。
参考了博客:微服务实战(七)实现服务负载均衡 - SpringCloud GateWay + Nacos + Robin
那么为什么这样可以实现呢?怎样实现这种人工赋权的呢?
阅读一下Nacos的源码
在package com.alibaba.cloud.nacos.ribbon下,有个NacosRule类继承自AbstractLoadBalancerRule,内容如下:
/**
* Supports preferentially calling the ribbon load balancing rules of the same cluster
* instance.
*
* @author itmuch.com
*/
public class NacosRule extends AbstractLoadBalancerRule {
private static final Logger LOGGER = LoggerFactory.getLogger(NacosRule.class);
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
@Override
public Server choose(Object key) {
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
String group = this.nacosDiscoveryProperties.getGroup();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties());
List<Instance> instances = namingService.selectInstances(name, group, true);
if (CollectionUtils.isEmpty(instances)) {
LOGGER.warn("no instance in service {}", name);
return null;
}
List<Instance> instancesToChoose = instances;
if (StringUtils.isNotBlank(clusterName)) {
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(clusterName,
instance.getClusterName()))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
}
else {
LOGGER.warn(
"A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}",
name, clusterName, instances);
}
}
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
catch (Exception e) {
LOGGER.warn("NacosRule error", e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
其中,重载的choose函数实现了对Server的选择。
NacosRule.choose(Object key)调用了
ExtendBalancer.getHostByRandomWeight2(List<Instance> instances)调用了
Balancer.getHostByRandomWeight(List<Instance> hosts)调用了
Chooser<K, T>.randomWithWeight()
在randomWithWeight里面,先生成了一个double类型的随机数random,然后对所有的权重weight数组double weight[]进行二分查找Arrays.binarySearch(weights,random)。
这个二分查找的解释是:
* Searches the specified array of doubles for the specified value using
* the binary search algorithm. The array must be sorted
* (as by the {@link #sort(double[])} method) prior to making this call.
* If it is not sorted, the results are undefined. If the array contains
* multiple elements with the specified value, there is no guarantee which
* one will be found. This method considers all NaN values to be
* equivalent and equal.
那么这个weight里面到底是什么呢?
在class Ref<T>里面,有个refresh()函数,可以看出来这个weight数组存的是累计占比。
获取的大致原理学习了博客:java中根据权重随机获取数据,根据权重随机选取指定条数记录的简单算法实现(C#)
Ribbon默认的rule是轮询,因此在没有设置的情况下默认规则是轮询。如果想使用结合Nacos后台权重设置的策略,只需要将 NacosRule 注册成为Bean,替换默认的 Rule即可。