Spring cloud入门-9:服务调用-Ribbon-轮询算法原理&源码解析&自定义负载均衡算法

1、Ribbon默认负载轮询算法原理

  通过上一篇博文的测试,应该很容易理解轮询算法的调用表现,也就是8001,8002服务器依次处理请求。
  轮询负载均衡算法
  rest接口请求次数(reqNum) % 服务器集群数量(serverNum) = 实际调用服务器位置下标(index)
  每次服务重启后,rest接口请求次数从1开始计数。
  以上一篇博文的例子说明。通过服务名称,可以获取到总服务实例个数为2个:

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

例如:服务器下标(index)为0,请求由8001服务器处理;
   服务器下标(index)为1,请求由8002服务器处理;

  1. 第1次请求,reqNum(1) % serverNum(2) = index(1),调用8001
  2. 第2次请求,reqNum(2) % serverNum(2) = index(0),调用8002
  3. 第3次请求,reqNum(3) % serverNum(2) = index(1),调用8001
  4. 第4次请求,reqNum(4) % serverNum(2) = index(0),调用8002

2、RoundRobinRule源码解析

  根据上一篇博文的方式,可以通过自定义的MySelfRule类,选择实现类为RoundRobinRule,然后我们调试RoundRobinRule类。
1、构造函数中,设定了rest接口请求次数nextServerCyclicCounter的初始值为0

public RoundRobinRule() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }

2、找到该服务名称下的所有服务实例列表:allServers

List<Server> allServers = lb.getAllServers();
int serverCount = allServers.size();

3、找到服务实例下标index,确定本次调用哪一个服务实例来处理请求
  传入服务实例总个数:serverCount,然后计算服务实例的下标index。
  这里为了防止多线程情况下,其他线程修改了请求次数nextServerCyclicCounter的值,这里使用了自旋锁compareAndSet方法

private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            // 获取当前请求的总次数reqNum
            current = this.nextServerCyclicCounter.get();
            // 获取处理请求服务器index = (reqNum+1) % serverNum
            // 使用自旋锁,比较并交换,
            // 防止其他线程已经修改了请求总次数的值:nextServerCyclicCounter
            next = (current + 1) % modulo;
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
    }

轮询负载均衡RoundRobinRule的源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RoundRobinRule extends AbstractLoadBalancerRule {
    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;
    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        this.setLoadBalancer(lb);
    }

    public Server choose(ILoadBalancer lb, Object key) {
        // 如果没有负载均衡算法,进行警告,直接返回null
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                // 如果尝试了10次,还无法获取可用的服务器,那么就直接返回null
                if (server == null && count++ < 10) {
                    // 获取可用服务实例个数,在我们这里是2
                    List<Server> reachableServers = lb.getReachableServers();
                    // 获取服务实例总个数,在我们这里是2
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
                    // 如果可用服务实例个数和总个数都为0,则进行一轮尝试
                    if (upCount != 0 && serverCount != 0) {
                        // 传入当前服务实例总个数,获取下一个调用服务器的index
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        // 根据index,选择服务器实例server
                        // 在我们这里的值是:192.168.5.112:8002
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }

    private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            // 获取当前请求的总次数reqNum
            current = this.nextServerCyclicCounter.get();
            // 获取处理请求服务器index = (reqNum+1) % serverNum
            // 使用自旋锁,比较并交换,
            // 防止其他线程已经修改了请求总次数的值:nextServerCyclicCounter
            next = (current + 1) % modulo;
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

3、Ribbon自定义负载均衡算法

  理解了Ribbon的轮询负载均衡算法之后,尝试自定义负载均衡算法。这里我们的自定义负载均衡采用的是随机调用。

3.1 ApplicationContextBean去掉@LoadBalanced注解

  首先将消费服务80模块的RestTemplate去掉@LoadBalanced注解,即不采用ribbon自带的负载均衡算法。
在这里插入图片描述

3.2 新建LoadBalancer接口

1、新建lb.LoadBalancer接口:
入参是该服务的所有服务实例列表,返回值是该次调用所使用的服务实例。
在这里插入图片描述

package com.example.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

public interface LoadBalancer {
    ServiceInstance getInstance(List<ServiceInstance> serviceInstanceList);
}

2、构建MyLoadBalancer类,实现LoadBalancer接口:
  在这里演示的随机负载均衡。实际的算法可以根据具体的业务逻辑进行构建
在这里插入图片描述

package com.example.springcloud.lb.impl;

import com.example.springcloud.lb.LoadBalancer;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Random;

@Component
public class MyLoadBalancer implements LoadBalancer {
    @Override
    public ServiceInstance getInstance(List<ServiceInstance> serviceInstanceList) {
        // 在这里演示的随机负载均衡。实际的算法可以根据具体的业务逻辑进行构建。
        // 获取服务实例总数:
        int serviceNum = serviceInstanceList.size();
        int next = new Random().nextInt(serviceNum);
        return serviceInstanceList.get(next);
    }
}

3.3 业务类

1、首先是引入自定义的负载均衡算法:
在这里插入图片描述
2、新建getPaymentLB方法
  仿照getPayment方法,新建一个基于自定义的负载均衡算法获取订单信息的getPaymentLB方法。
  getPayment方法中获取服务实例是交给了eureka中的ribbon的服务均衡算法,由ribbon的负载均衡算法选择服务实例来处理请求。
  而getPaymentLB方法则是根据自定义的负载均衡算法选择服务实例(uri)来处理请求。
在这里插入图片描述

package com.example.springcloud.controller;

import com.example.springcloud.entities.CommonResult;
import com.example.springcloud.entities.Payment;
import com.example.springcloud.lb.LoadBalancer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@Slf4j
public class OrderController {
//    public static final String PAYMENT_URL = "http://localhost:8001";
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DiscoveryClient discoveryClient;

    @Resource
    private LoadBalancer loadBalancer;

    /**
     * 服务发现接口
     */
    @GetMapping("/consumer/discovery")
    public CommonResult discovery() {
        List<Map<String, Object>> resMap = new ArrayList<>();
        // 获取所有服务
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            log.info("已注册的服务: " + service);
            // 获取每一个服务的所有实例
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            for (ServiceInstance instance : instances) {
                log.info("\t" + instance.getServiceId() + "\t" + instance.getHost() + "\t"
                        + instance.getPort() + "\t" + instance.getUri());
                Map<String, Object> map = new HashMap<>();
                map.put("服务名称", service);
                map.put("实例名称", instance.getInstanceId());
                map.put("主机地址", instance.getHost());
                map.put("端口号", instance.getPort());
                map.put("uri地址", instance.getUri());
                resMap.add(map);
            }
        }
        return new CommonResult(200, "服务发现成功", resMap);
    }

    @GetMapping(value = "/consumer/payment/create")
    public CommonResult create(Payment payment) {
        return restTemplate.postForObject(PAYMENT_URL+"/payment/create", payment, CommonResult.class);
    }

    // 返回对象为响应体中数据转化的对象
    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult getPayment(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, CommonResult.class);
    }

    // 基于自定义的负载均衡算法,返回对象为响应体中数据转化的对象
    @GetMapping(value = "/consumer/payment/lb/get/{id}")
    public CommonResult getPaymentLB(@PathVariable("id") Long id) {
        //获取服务的所有实例
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if (instances==null || instances.size()<=0) {
            return null;
        }

        // 获取自定义负载均衡算法得到的服务实例
        ServiceInstance instance = loadBalancer.getInstance(instances);
        URI uri = instance.getUri();
        return restTemplate.getForObject(uri+"/payment/get/"+id, CommonResult.class);
    }

    // 返回对象为ResponseEntity对象,包含了响应中的重要信息。也就是除了除响应体外,还包含响应头、响应状态码等。
    @GetMapping(value = "/consumer/payment/getForEntity/{id}")
    public CommonResult getPayment2(@PathVariable("id") Long id) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id, CommonResult.class);
        if (entity.getStatusCode().is2xxSuccessful()) {
            return entity.getBody();
        } else {
            return new CommonResult(500, "获取订单信息失败!");
        }
    }
}

3.4 测试

  启动eureka集群7001,7002,启动服务提供模块集群8001,8002,然后启动消费订单服务80模块。
  然后测试刚刚创建的基于自定义负载均衡算法的接口:http://localhost:8080/consumer/payment/lb/get/3。可以看到服务请求现在已经是随机的由8001,8002来处理请求了。可以看到,刚刚自定义的负载均衡算法已经生效。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值