之前的文章已经介绍了Nacos的服务注册发现和配置中心的功能,这期主要用来介绍Nacos与负载均衡相关话题。那么我们首先来介绍一下什么是负载均衡。
负载均衡
一句话描述就是,负载均衡是在支持应用程序的资源池中平均分配网络流量的一种方法
,在微服务中负载均衡组件也是核心的重要组件之一。
负载均衡的实现方式通常有两种方式,一种是通过由服务端来提供,客户端从外面看上去只有一个服务。例如我们通常会部署多个服务器节点,然后通过Nginx来实现负载均衡。
与上面一种正好相反的则是客户端负载均衡,客户端通过服务列表获取服务的所有地址,然后根据算法访问其中一台服务器。
而我们在微服务中说的负载均衡一般指的是后者,也就是客户端负载均衡。
Ribbon与LoadBalancer
在SpringCloud中提供的客户端负载均衡组件目前来说主流的就两种,其中最早最出名的就是Ribbon,它是Netflix公司内部开源出来的组件之一,同时还有像服务组件与发现的Eureka、网关Zuul、监控和熔断Hystrix等。不过后期Netflix宣布了大量的组件不再进行更新和维护,所以SpringCloud在后期版本就移除了部分Netflix组件,改用其他组件来替代。而我们的Ribbon就是移除的组件之一,Spring官方则提供了新的组件LoadBalancer用来替代。
Ribbon的移除版本是在H版之后,也就是后期版本的SpringCloud不再提供Ribbon了。
而我们教程用的SpringCloud版本为2020.0.1
,该版本已经不再支持Ribbon了,所以我们这里将选用LoadBalancer作为负载均衡组件。
LoadBalancer组件是在Spring Cloud Commons中添加的,所以查找文档时需要去Spring Cloud Commons查找相关文档,具体文档地址可以参考链接:https://docs.spring.io/spring-cloud-commons/docs/3.0.1/reference/html/#spring-cloud-loadbalancer
快速开始
基于之前的项目,我们创建子模块nacos-loadbalancer
,然后引入LoadBalancer依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
最后整个项目依赖如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-demo</artifactId>
<groupId>com.zc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nacos-loadbalancer</artifactId>
<packaging>jar</packaging>
<name>nacos-loadbalancer</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
接下来创建测试接口类如下:
@RestController
public class LoadBalancerController {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Resource
private RestTemplate restTemplate;
@GetMapping("loadbalancer")
public String loadbalancer(){
ResponseEntity<String> response = restTemplate.getForEntity("http://nacos-register/hello", String.class);
return response.getBody();
}
}
上面代码中,我们配置了一个RestTemplate
实例用来调用服务,并使用注解@LoadBalanced
使其具有负载均衡的能力。
nacos-register
是服务的名称,可以直接使用其代替IP和Port便于维护和管理。
在完成上面代码后,我们还需要配置Nacos相关配置,它的作用就是用来获取服务列表,这里就不贴相关配置代码了。最后启动三个nacos-register
服务,它们的端口分别为9001
、9002
、9003
。
最后我们访问localhost:${port}/loadbalancer
,其响应内容如下:
Hello World! 9001
Hello World! 9002
Hello World! 9003
...
从多次调用的结果可以看出,每次调用都是从不同的nacos-register
服务实例返回结果,这就是客户端负载均衡的效果。
负载均衡算法
LoadBalancer目前内置的算法只有两种,分别为轮循RoundRobinLoadBalancer
和随机RandomLoadBalancer
算法,默认情况下为轮循算法。我们这里先介绍如何将轮循修改为随机算法。
修改内置算法
首先创建随机算法配置类,示例代码如下:
public class RandomLoadBalancerRuleConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
然后为服务指定配置,示例代码如下:
@LoadBalancerClient(name = "nacos-register", configuration = RandomLoadBalancerRuleConfig.class)
public class LoadBalancerConfig {
}
nacos-register
是服务名,configuration后指定的配置类。
接下来再次重启服务,多次请求会发现接口每次返回的内容都没有规律。
自定义权重算法
在上面有讲过,LoadBalancer内置的负载均衡算法目前只有两种,但是在实际业务中我们可能有自定义算法的需求,这里我简单的实现随机权重算法。
要自定义算法的第一步就是要实现ReactorServiceInstanceLoadBalancer
接口,其内置的两种算法也是通过实现该接口完成的。我们参照源码中RoundRobinLoadBalancer
的实现如下:
public class WeightLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
final String serviceId;
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public WeightLoadBalancer(String serviceId, ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {
if (serviceInstances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
//获取权重数组
double[] values = new double[serviceInstances.size()];
for (int i = 0; i < serviceInstances.size(); i++) {
double value = Double.parseDouble(serviceInstances.get(i).getMetadata().getOrDefault("nacos.weight", "1.0"));
values[i] = value;
}
int index = WeightUtils.get(values);
return new DefaultResponse(serviceInstances.get(index));
}
}
整体代码比较简单,我这里先讲一下思路。首先要解决的第一个问题就是获取各个服务实例的权重值。我们这里使用的Nacos作为服务的注册与发现,所以我这里的权重值直接获取的nacos.weight
,关键代码如下:
serviceInstances.get(i).getMetadata().getOrDefault("nacos.weight", "1.0")
具体如何获取权重,这个还是根据你自己项目的实际情况来。解决了获取权重的问题,那么第二个问题就是如何实现权重算法。我这里简单的采用了随机权重算法,算法大致过程如下:
-
将权重值存入到数组中。例如有一组权重值为
[1,2,3]
,将其存入数组的后则为[1,3,6]
。 -
接下来随机生成一个数字,其区间值为
[0,数组最大值)
。根据上面的描述,也就是获取一个区间范围为[0,6)
的值,未包含右区间。 -
判断该值在数组中的第几个区间。例如现在随机值为
5.5
,那么其应该在区间[3,6)
,也就是第三个区间。
- 其算法实现如下:
public static int get(double[] weightArrays){
//初始化数组
double[] values = new double[weightArrays.length];
values[0] = weightArrays[0];
for (int i = 1; i < values.length; i++) {
values[i] = values[i-1] + weightArrays[i];
}
//获取随机值
double value = ThreadLocalRandom.current().nextDouble() * values[values.length - 1];
System.out.println(value);
for (int i = 0; i < values.length; i++) {
if (value < values[i]){
return i;
}
}
throw new RuntimeException();
}
通过上面的内容,我们已经完成了随机权重算法。接下来就是使用测试,还是跟之前一样,我们在使用之前需要配置,其代码如下:
public class WeightLoadBalancerRuleConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new WeightLoadBalancer(name, loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class));
}
}
然后修改LoadBalancerConfig
注解中的代码如下:
@LoadBalancerClient(name = "nacos-register", configuration = WeightLoadBalancerRuleConfig.class)
在Nacos的控制后台,我们还可以动态地修改节点的权重,这样可以做到动态的修改节点权重。
金丝雀测试
首先我们了解一下什么是金丝雀测试。如果现在线上有个订单服务正在运行,当你修改了这个服务的部分接口,这时候你需要做一个线上测试。既然是测试我们不可能一下子把所有的服务全部替换掉,很可能只想拿出集群中的一台或者几台服务做测试。如果这几台服务没有问题,后续我们就能更新所有的服务。
这个需求其实能通过自定义负载均衡算法来实现,主要过程分为以下几步:
-
首先我们需要能识别出哪些流量去到测试的应用,通常的做法就是在测试请求的请求头上加上标记。
-
通过自定义算法识别出打了测试标记的请求将其路由到特定的测试应用。对于特定的测试应用,我们可以在Nacos集群节点中通过元数据标记出来。
-
对于正常的请求还是按照原有方式路由。
相关内容
本文的示例工程代码:
https://github.com/I-Like-Pepsi/spring-cloud-alibaba-demo
Spring Cloud Alibaba版本教程-Nacos服务注册和发现
SpringCloud Alibaba版本教程-Nacos统一配置
原文始发于微信公众号(一只菜鸟程序员):SpringCloud Alibaba版本教程-Nacos与负载均衡
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72782.html