客户端负载均衡
负载均衡顾名思义,是指通过软件或者硬件措施。它将来自客户端的请求按照某种策略平均的分配到集群的每一个节点上,保证这些节点的 CPU、内存等设备负载情况大致在一条水平线,避免由于局部节点负载过高产生宕机,再将这些处理压力传递到其他节点上产生系统性崩溃。
负载均衡按实现方式分类可区分为:服务端负载均衡与客户端负载均衡。
服务端负载均衡顾名思义,在架构中会提供专用的负载均衡器,由负载均衡器持有后端节点的信息,服务消费者发来的请求经由专用的负载均衡器分发给服务提供者,进而实现负载均衡的作用。目前常用的负载均衡器软硬件有:F5、Nginx、HaProxy 等。
客户端负载均衡是指,在架构中不再部署额外的负载均衡器,在每个服务消费者内部持有客户端负载均衡器,由内置的负载均衡策略决定向哪个服务提供者发起请求。通俗来讲,就是客户端在发送请求之前就通过某种策略选定了要请求的服务提供者,而不是让一个中间件来帮忙决定。
Ribbon
Netfilx Ribbon 是 Netflix 公司开源的一个负载均衡组件,是属于客户端负载均衡器。目前Ribbon 已被 Spring Cloud 官方技术生态整合,运行时以 SDK 形式内嵌到每一个微服务实例中,为微服务间通信提供负载均衡与高可用支持。
过程如下:
- 订单服务(order-service)与商品服务(goods-service)实例在启动时向 Nacos 注册;
- 订单服务向商品服务发起通信前,Ribbon 向 Nacos 查询商品服务的可用实例列表;
- Ribbon 根据设置的负载策略从商品服务可用实例列表中选择实例;
- 订单服务实例向商品服务实例发起请求,完成 RESTful 通信;
下面用一个实例来演示:
创建服务生产者
创建springboot项目模块,引入web和nacos客户端依赖
配置文件
spring:
application:
name: provider-service #应用/微服务名字
cloud:
nacos:
discovery:
server-addr: 49.234.82.226:8848 #nacos服务器地址
username: nacos #用户名密码
password: nacos
server:
port: 8081
创建示例服务
@RestController
public class ProviderController {
@GetMapping("/provider/msg")
public String sendMessage(){
return "This is the message from provider service one!";
}
}
连续创建3个provider,注意配置文件中的应用/微服务名要一致,代表同一种服务,为了直接了当地识别服务来自不同的微服务示例,可以在返回的字符串中添加自己的标示。启动后可以在nacos中看到健康示例数为3:
创建服务消费者
引入web和nacos客户端依赖之外,还要引入ribbon
<!-- Ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
配置文件和服务提供者只有应用名和端口的区别。
服务调用示例
首先是要添加RestTemplate对象
@SpringBootApplication
public class ConsumerServiceApplication {
//Java Config声明RestTemplate对象
//在应用启动时自动执行restTemplate()方法创建RestTemplate对象,其BeanId为restTemplate。
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerServiceApplication.class, args);
}
}
然后添加controller
@RestController
public class ConsumerController {
private Logger logger = LoggerFactory.getLogger(ConsumerController.class);
//注入 Ribbon 负载均衡器对象
//在引入 starter-netflix-ribbon 后在 SpringBoot 启动时会自动实例化 LoadBalancerClient 对象。
//在 Controller 使用 @Resource 注解进行注入即可。
@Resource
private LoadBalancerClient loadBalancerClient;
@Resource
//将应用启动时创建的 RestTemplate 对象注入 ConsumerController
private RestTemplate restTemplate;
@GetMapping("/consumer/msg1")
public String getProviderMessage1() {
//loadBalancerClient.choose()方法会从 Nacos 获取 provider-service 所有可用实例,
//并按负载均衡策略从中选择一个可用实例,封装为 ServiceInstance(服务实例)对象
//结合现有环境既是从三个实例中选择一个包装为ServiceInstance
ServiceInstance serviceInstance = loadBalancerClient.choose("provider-service");
//获取服务实例的 IP 地址
String host = serviceInstance.getHost();
//获取服务实例的端口
int port = serviceInstance.getPort();
//在日志中打印服务实例信息
logger.info("本次调用由provider-service的" + host + ":" + port + " 实例节点负责处理" );
//通过 RestTemplate 对象的 getForObject() 方法向指定 URL 发送请求,并接收响应。
//getForObject()方法有两个参数:
//1. 具体发送的 URL,结合当前环境发送地址为:http://[ip]:[port]/provider/msg
//2. String.class说明 URL 返回的是纯字符串,如果第二参数是实体类, RestTemplate 会自动进行反序列化,为实体属性赋值
String result = restTemplate.getForObject("http://" + host + ":" + port + "/provider/msg", String.class);
//输出响应内容
logger.info("provider-service 响应数据:" + result);
//向浏览器返回响应
return "consumer-service 响应数据:" + result;
}
}
调用服务测试
发现每次刷新都是不一样的服务示例来服务的,这里默认采用了轮询机制
来负载均衡
@LoadBalanced
采用注解模式用@LoadBalanced
注解就可以很大程度上简化代码
注解加在RestTemplate对象上
@SpringBootApplication
public class ConsumerServiceApplication {
//Java Config声明RestTemplate对象
//在应用启动时自动执行restTemplate()方法创建RestTemplate对象,其BeanId为restTemplate。
@Bean
@LoadBalanced //使RestTemplate对象自动支持Ribbon负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerServiceApplication.class, args);
}
}
添加服务
@GetMapping("/consumer/msg2")
public String getProviderMessage2() {
//关键点:将原有IP:端口替换为服务名,RestTemplate便会在通信前自动利用Ribbon查询可用provider-service实例列表
//再根据负载均衡策略选择节点实例
String result = restTemplate.getForObject("http://provider-service/provider/msg", String.class);
logger.info("consumer-service获得数据:" + result);
return "consumer-service获得数据:" + result;
}
可以发现比第一种方式减少了很多的代码
如何配置 Ribbon 负载均衡策略
Ribbon 内置多种负载均衡策略,常用的分为以下几种。
- RoundRobinRule:
轮询策略,Ribbon 默认策略。默认超过 10 次获取到的 server 都不可用,会返回⼀个空的 server。
- RandomRule:
随机策略,如果随机到的 server 为 null 或者不可用的话。会不停地循环选取。
- RetryRule:
重试策略,⼀定时限内循环重试。默认继承 RoundRobinRule,也⽀持自定义注⼊,RetryRule 会在每次选取之后,对选举的 server 进⾏判断,是否为 null,是否 alive,并且在 500ms 内会不停地选取判断。而 RoundRobinRule 失效的策略是超过 10 次,RandomRule 没有失效时间的概念,只要 serverList 没都挂。
- BestAvailableRule:
最小连接数策略,遍历 serverList,选取出可⽤的且连接数最小的⼀个 server。那么会调用 RoundRobinRule 重新选取。
- AvailabilityFilteringRule:
可用过滤策略。扩展了轮询策略,会先通过默认的轮询选取⼀个 server,再去判断该 server 是否超时可用、当前连接数是否超限,都成功再返回。
- ZoneAvoidanceRule:
区域权衡策略。扩展了轮询策略,除了过滤超时和链接数过多的 server,还会过滤掉不符合要求的 zone 区域⾥⾯的所有节点,始终保证在⼀个区域/机房内的服务实例进行轮询。
这里所有负载均衡策略名本质都是 com.netflix.loadbalancer 包下的类:
修改微服务的负载均衡规则
直接修改配置文件application.yml添加
provider-service: #服务提供者的微服务id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置对应的负载均衡类
这里采用随机算法,用上面的示例测试一下:
2021-04-14 01:20:04.122 INFO 82083 --- [nio-8090-exec-1] c.c.c.controller.ConsumerController : consumer-service获得数据:This is the message from provider service two!
2021-04-14 01:20:06.694 INFO 82083 --- [nio-8090-exec-2] c.c.c.controller.ConsumerController : consumer-service获得数据:This is the message from provider service one!
2021-04-14 01:20:09.022 INFO 82083 --- [nio-8090-exec-3] c.c.c.controller.ConsumerController : consumer-service获得数据:This is the message from provider service one!
2021-04-14 01:20:09.989 INFO 82083 --- [nio-8090-exec-4] c.c.c.controller.ConsumerController : consumer-service获得数据:This is the message from provider service three!
2021-04-14 01:20:20.379 INFO 82083 --- [nio-8090-exec-5] c.c.c.controller.ConsumerController : consumer-service获得数据:This is the message from provider service two!
果然是随机的!