理解 Ribbon 并自己实现负载均衡

3 minute

负载均衡(LB)是什么

对于用户的某个请求,将有多个相同功能的服务点服务该请求,某个服务点挂了,其他服务点还是可以进行服务,这样就实现了系统的高可用。

关于集中式 LB 和进程内 LB

集中式 LB

在服务的消费方和提供方之间使用独立的 LB 设施,(软硬件均可,软件如 Nginx,硬件如 F5),由该设施负责把访问请求通过某种策略(可自行指定)转发至服务的提供方。

进程内 LB

将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务点进行服务。

Ribbon 属于进程内 LB,它只是一个类库,集成于消费方进程,消费方通过它获取服务提供方的地址。

使用 Ribbon 实现负载均衡

关于导包

1<dependency>
2    <groupId>org.springframework.cloud</groupId>
3    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <!-- 已经包含了ribbon -->
4</dependency>

开启注解

1@Configuration
2public class ApplicationContextConfig {
3    @Bean
4    @LoadBalanced // 赋予负载均衡能力
5    public RestTemplate getRestTemplate() {
6        return new RestTemplate();
7    }
8}

访问相同服务名地址即可。

修改 Ribbon 负载均衡规则

所有规则均实现了 IRule 接口,通过查看接口实现类即可知道规则的种类。

默认是 RoundRobinRule(轮询)这一规则。

下面修改为 RandomRule(随机)这一规则:

在启动类扫描不到的包下创建规则:

1@Configuration
2public class MyRibbonRule {
3    @Bean
4    public IRule myRule() {
5        return new RandomRule();
6    }
7}

在启动类指定规则:

1@SpringBootApplication
2@EnableEurekaClient
3@RibbonClient(name="CLOUD-PAYMENT-SERVICE", configuration = MyRibbonRule.class)
4public class OrderMain80 {
5    public static void main(String[] args) {
6        SpringApplication.run(OrderMain80.class, args);
7    }
8}

自己实现负载均衡

编写 LB 接口即实现类

要实现负载均衡,首先应获取得到所有的服务实例 ServiceInstance

1public interface LoadBalancer {
2    ServiceInstance getServiceInstance(List<ServiceInstance> instances);
3}

通过自旋锁获取新值,取余 ServiceInstance 个数,得到目标 ServiceInstance 下标。

 1@Component
 2public class MyLoadBalancer implements LoadBalancer {
 3    private AtomicInteger aint = new AtomicInteger(0);
 4    public final int myCAS() {
 5        int expect, next;
 6        for (;;) {
 7            expect = aint.get();
 8            next = (expect + 1) % Integer.MAX_VALUE;
 9            if (aint.compareAndSet(expect, next)) return next;
10        }
11    }
12    @Override
13    public ServiceInstance getServiceInstance(List<ServiceInstance> instances) {
14        if (instances == null || instances.size() <= 0) return null;
15        return instances.get(myCAS() % instances.size());
16    }
17}

编写 Controller

注意需要通过获取得到的 ServiceInstance 的 uri 作为访问前缀。

 1@Resource
 2private RestTemplate restTemplate;
 3
 4@Resource
 5private EurekaDiscoveryClient discoveryClient;
 6
 7@Resource
 8private LoadBalancer loadBalancer;
 9
10@GetMapping("/consumer/payment/lb")
11public String lbTest() {
12    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
13    ServiceInstance instance = loadBalancer.getServiceInstance(instances);
14    log.info("lbtest: " + instance.getUri().toString());
15
16    return restTemplate.getForObject(instance.getUri() + "/payment/lb", String.class);
17}

接着访问 /consumer/payment/lb 接口即可完成负载均衡的测试。