1. 什么是负载均衡?
负载均衡是一种基础的网络服务, 它的核心原理是按照指定的负载均衡算法, 将请求分配到后端服务集群上, 从而为系统提供并行处理和高可用的能力. 提到负载均衡, 你可能想到 nginx. 对于负载均衡, 一般分为服务端负载均衡和客户端负载均衡
服务端负载均衡: 在消费者和服务提供方中间使用独立的代理方式进行负载, 有硬件的负载均衡器, 比如 F5, 也有软件, 比如 Nginx.
客户端负载均衡: 所谓客户端负载均衡, 就是客户端根据自己的请求情况做负载, 本文介绍的 Netflix Ribbon 就是客户端负载均衡的组件
2. 什么是 Netflix Ribbon?
在上一章的学习中, 我们知道了微服务的基本概念, 知道怎么基于 Ribbon+restTemplate 的方式实现服务调用, 接着上篇博客, 我们再比较详细学习客户端负载均衡 Netflix Ribbon, 学习本博客之前请先学习上篇博客, 然后再学习本篇博客
Ribbon 是由 Netflix 发布的负载均衡器, 它有助于控制 HTTP 和 TCP 的客户端的行为. Ribbon 属于客户端负载均衡.
3. Netflix Ribbon 实验环境准备
环境准备:
- JDK 1.8
- SpringBoot2.2.1
- SpringCloud(Hoxton.SR6)
- Maven 3.2+
开发工具
- IntelliJ IDEA
- smartGit
创建一个 SpringBoot Initialize 项目, 详情可以参考我之前博客: SpringBoot 系列之快速创建项目教程
可以引入 Eureka Discovery Client, 也可以单独添加 Ribbon
Spring Cloud Hoxton.SR6 版本不需要引入 spring-cloud-starter-netflix-ribbon, 已经默认集成
也可以单独添加 Ribbon 依赖:
本博客的是基于
spring-cloud-starter-netflix-eureka-client
进行试验, 试验前要运行 eureka 服务端, eureka 服务提供者, 代码请参考上一章博客
补充: IDEA 中多实例运行方法
step1: 如图, 不要加上勾选
step2: 指定不同的 server 端口和实例 id, 如图:
启动成功后, 是可以看到多个实例的
4. Netflix Ribbon API 使用
使用 LoadBalancerClient:
- @Autowired
- LoadBalancerClient loadBalancerClient;
- @Test
- void contextLoads() {
- ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
- URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
- System.out.println(uri.toString());
- }
构建 BaseLoadBalancer 实例例子:
- @Test
- void testLoadBalancer(){
- // 服务列表
- List<Server> serverList = Arrays.asList(new Server("localhost", 8083), new Server("localhost", 8084));
- // 构建负载实例
- BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
- loadBalancer.setRule(new RandomRule());
- for (int i = 0; i <5; i++) {
- String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build()
- .submit(new ServerOperation<String>() {
- public Observable<String> call(Server server) {
- try {
- String address = "http://" + server.getHost() + ":" + server.getPort()+"/EUREKA-SERVICE-PROVIDER/api/users/mojombo";
- System.out.println("调用地址:" + address);
- return Observable.just("");
- } catch (Exception e) {
- return Observable.error(e);
- }
- }
- }).toBlocking().first();
- System.out.println("result:" + result);
- }
- }
5. 负载均衡 @LoadBalanced
Ribbon 负载均衡实现, RestTemplate 要加上 @LoadBalanced
- package com.example.springcloud.ribbon.configuration;
- import org.springframework.cloud.client.loadbalancer.LoadBalanced;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.client.RestTemplate;
- /**
- * <pre>
- * RestConfiguration
- * </pre>
- *
- * <pre>
- * @author mazq
- * 修改记录
- * 修改后版本: 修改人: 修改日期: 2020/07/31 09:43 修改内容:
- * </pre>
- */
- @Configuration
- public class RestConfiguration {
- @Bean
- @LoadBalanced
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- }
YAML 配置:
- server:
- port: 8082
- spring:
- application:
- name: eureka-service-consumer
- eureka:
- client:
- service-url:
- defaultZone: http://localhost:8761/eureka/
- fetch-registry: true
- register-with-eureka: false
- healthcheck:
- enabled: false
- instance:
- status-page-url-path: http://localhost:8761/actuator/info
- health-check-url-path: http://localhost:8761/actuator//health
- prefer-ip-address: true
- instance-id: eureka-service-consumer8082
关键点, 使用 SpringCloud 的 @LoadBalanced, 才能调 http://EUREKA-SERVICE-PROVIDER/API/users/? 接口的数据, 浏览器是不能直接调的
- import com.example.springcloud.ribbon.bean.User;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
- import org.springframework.Web.bind.annotation.*;
- import org.springframework.Web.client.RestTemplate;
- import java.NET.URI;
- @SpringBootApplication
- @EnableEurekaClient
- @RestController
- @Slf4j
- public class SpringcloudRibbonApplication {
- @Autowired
- RestTemplate restTemplate;
- public static void main(String[] args) {
- SpringApplication.run(SpringcloudRibbonApplication.class, args);
- }
- @GetMapping("/findUser/{username}")
- public User index(@PathVariable("username")String username){
- return restTemplate.getForObject("http://EUREKA-SERVICE-PROVIDER/api/users/"+username,User.class);
- }
- }
6. 定制 Netflix Ribbon client
具体怎么定制? 可以参考官网,@RibbonClient 指定定制的配置类既可
- package com.example.springcloud.ribbon.configuration;
- import com.example.springcloud.ribbon.component.MyRule;
- import com.netflix.loadbalancer.IPing;
- import com.netflix.loadbalancer.IRule;
- import com.netflix.loadbalancer.PingUrl;
- import org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- /**
- * <pre>
- * Ribbon Clients configuration
- * </pre>
- *
- * <pre>
- * @author mazq
- * 修改记录
- * 修改后版本: 修改人: 修改日期: 2020/07/29 14:22 修改内容:
- * </pre>
- */
- //@Configuration(proxyBeanMethods = false)
- //@IgnoreComponentScan
- public class RibbonClientConfiguration {
- // @Autowired
- // IClientConfig config;
- @Bean
- public IRule roundRobinRule() {
- return new MyRule();
- }
- @Bean
- public ZonePreferenceServerListFilter serverListFilter() {
- ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
- filter.setZone("myTestZone");
- return filter;
- }
- @Bean
- public IPing ribbonPing() {
- return new PingUrl();
- }
- }
在 Application 类加上 @RibbonClient,name 是为服务名称, 跟 Bootstrap.YAML 配置的一样既可
@RibbonClient(name = "eureka-service-provider",configuration = RibbonClientConfiguration.class)
特别注意: 官网这里特意提醒, 这里的意思是说 @RibbonClient 指定的配置类必须加 @Configuration(不过在 Hoxton.SR6 版本经过我的验证, 其实是可以不加的, 加了反而可能报错),@ComponentScan 扫描要排除自定义的配置类, 否则, 它由所有 @RibbonClients 共享. 如果你使用 @ComponentScan(或 @SpringBootApplication)
其实就是想让我们排除这个配置的全局扫描, 所以我们可以进行编码, 写个注解类
@IgnoreComponentScan
, 作用于类, 指定
- @Target(ElementType.TYPE)
- package com.example.springcloud.ribbon.configuration;
- import java.lang.annotation.*;
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface IgnoreComponentScan {
- }
加上自定义的注解类
任何在 Application 加上代码, 避免全局扫描:
@ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value= IgnoreComponentScan.class)})
7. Netflix Ribbon 常用组件
ps: 介绍 Netflix Ribbon 的负载策略之前, 先介绍 Netflix Ribbon 常用组件及其作用:
组件 | 作用 |
---|---|
ILoadBalancer | 定义一系列的操作接口,比如选择服务实例。 |
IRule | 负载算法策略,内置算法策略来为服务实例的选择提供服务。 |
ServerList | 负责服务实例信息的获取(可以获取配置文件中的,也可以从注册中心获取。) |
ServerListFilter | 过滤掉某些不想要的服务实例信息。 |
ServerListUpdater | 更新本地缓存的服务实例信息。 |
IPing | 对已有的服务实例进行可用性检查,保证选择的服务都是可用的。 |
8. 定制 Netflix Ribbon 策略
因为服务提供者是多实例的, 所以再写个接口测试, 调用了哪个实例, 来看看 Netflix Ribbon 的负载策略
- @Autowired
- LoadBalancerClient loadBalancerClient;
- @GetMapping(value = {"/test"})
- public String test(){
- ServiceInstance serviceInstance = loadBalancerClient.choose("EUREKA-SERVICE-PROVIDER");
- URI uri = URI.create(String.format("http://%s:%s", serviceInstance.getHost() , serviceInstance.getPort()));
- System.out.println(uri.toString());
- return uri.toString();
- }
部署成功, 多次调用, 可以看到每次调用的服务实例都不一样? 其实 Netflix Ribbon 默认是按照轮询的方式调用的
要定制 Netflix Ribbon 的负载均衡策略, 需要实现 AbstractLoadBalancerRule 抽象类, 下面给出类图:
Netflix Ribbon 内置了如下的负载均衡策略, 引用 https://juejin.im/post/6854573215587500045 的归纳:
ok, 接着我们可以在配置类, 修改规则
- @Bean
- public IRule roundRobinRule() {
- return new BestAvailableRule();
- }
测试, 基本都是调 8083 这个实例, 因为这个实例性能比较好
显然, 也可以自己写个策略类, 代码参考
com.netflix.loadbalancer.RandomRule
, 网上也有很多例子, 思路是修改 RandomRule 原来的策略, 之前随机调服务实例一次, 现在改成每调 5 次后, 再调其它的服务实例
- package com.example.springcloud.ribbon.component;
- import com.netflix.client.config.IClientConfig;
- import com.netflix.loadbalancer.AbstractLoadBalancerRule;
- import com.netflix.loadbalancer.ILoadBalancer;
- import com.netflix.loadbalancer.Server;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.ThreadLocalRandom;
- public class MyRule extends AbstractLoadBalancerRule
- {
- // 总共被调用的次数, 目前要求每台被调用 5 次
- private int total = 0;
- // 当前提供服务的机器号
- private int index = 0;
- public Server choose(ILoadBalancer lb, Object key)
- {
- if (lb == null) {
- return null;
- }
- Server server = null;
- while (server == null) {
- if (Thread.interrupted()) {
- return null;
- }
- // 获取可用的服务列表
- List<Server> upList = lb.getReachableServers();
- // 获取所有服务列表
- List<Server> allList = lb.getAllServers();
- int serverCount = allList.size();
- if (serverCount == 0) {
- // 没有获取到服务
- return null;
- }
- //int index = chooseRandomInt(serverCount);
- //server = upList.get(index);
- if(total <5)
- {
- server = upList.get(index);
- total++;
- }else {
- total = 0;
- index++;
- if(index>= upList.size())
- {
- index = 0;
- }
- }
- if (server == null) {
- // 释放线程
- Thread.yield();
- continue;
- }
- if (server.isAlive()) {
- return (server);
- }
- server = null;
- Thread.yield();
- }
- return server;
- }
- protected int chooseRandomInt(int serverCount) {
- return ThreadLocalRandom.current().nextInt(serverCount);
- }
- @Override
- public Server choose(Object key) {
- return choose(getLoadBalancer(), key);
- }
- @Override
- public void initWithNiwsConfig(IClientConfig clientConfig) {
- }
- }
修改 IRule , 返回 MyRule
- @Bean
- public IRule roundRobinRule() {
- return new MyRule();
- }
附录:
ok, 本博客参考官方教程进行实践, 仅仅作为入门的学习参考资料, 详情可以参考 Spring Cloud 官方文档
代码例子下载: code download
优质学习资料参考:
Ribbon 负载均衡 -> 源码剖析: Ribbon 负载均衡 -> 源码剖析
https://juejin.im/post/6854573215587500045
方志鹏大佬系列 Spring Cloud 博客: https://www.fangzhipeng.com/spring-cloud.html
使用 Spring Cloud 与 Docker 实战微服务:
程序员 DD 大佬系列 Spring Cloud 博客: http://blog.didispace.com/spring-cloud-learning/
来源: https://www.cnblogs.com/mzq123/p/13411111.html