Ribbon

在微服务架构中,通常服务注册中心以及服务提供者大都是集群部署,当客户端与众多微服务交互过程中,如何保证服务的高可用性(避免出现某些服务出现请求过载,某些服务比较"闲"),也就是常说的负载均衡,而Ribbon就是在客户端实现软负载均衡的一种组件,通常与Eureka结合使用。简单总结就是RestTemplate+负载均衡

Ribbon负载均衡和Rest调用

这里我事先准备两个订单微服务和一个消费者服务并集群注册到Eureka集群

微服务如何承担海量数据 微服务实现_负载均衡


在消费者服务中通过RestTemplate远程请求两个订单微服务并实现负载均衡

  • RestTemplate配置
/**
 * @Description RestTemplate配置类
 * @Author Fangchenjiang
 * @Date 2021/4/3 14:53
 */
@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){

        return new RestTemplate();
    }
}

其中标有@LoadBalanced注解,说明该RestTemplate客户端默认采用轮训的负载均衡算法,当消费者端通过Rest远程访问服务提供者,就能看到负载均衡的效果

/**
* @Description 消费者COntroller
* @Author Fangchenjiang
* @Date 2021/4/3 23:03
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

 private  static String  URL="http://xf-order/";
 @Autowired
 RestTemplate restTemplate;
 @GetMapping("/getById/{id}")
 public String getOrderByID(@PathVariable("id")Long id){

     String object = restTemplate.getForObject(URL + "/order/getPay/"+id, String.class);
     return object;
 }
}

负载均衡核心组件

在NetFlix提供的Ribbon中,其中实现的核心组件是com.netflix.loadbalancer包下的IRule接口,该接口有多个实现类,每个实现类就对应着具体的负载均衡算法,常用的有比如有轮训,随机,重试,权重,对应实现类如下:

微服务如何承担海量数据 微服务实现_微服务如何承担海量数据_02


若需要去掉默认的轮训规则,只需要新建一个自定义配置类,构造返回具体的实现规则即可。注意有一点,这个替换规则配置类不能与SpringBoot在同一个@Component扫描包以及其子包下,配置类如下:

/**
 * @Description Ribbon规则替换类
 * @Author Fangchenjiang
 * @Date 2021/4/4 17:19
 */
@Configuration
public class MyRule {

    @Bean
    public IRule iRule(){
        //替换成随机算法
        return  new RandomRule();
    }
}

接着在客户端启动类中加上Ribbon客户端配置

/**
 * @Description 消费者客户端
 * @Author Fangchenjiang
 * @Date 2021/4/3 22:51
 */
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(value = "xf-order",configuration = MyRule.class) //指定具体的负载均衡替换类和key
public class ConsumerApplication {
    public static void main(String[] args) {

        SpringApplication.run(ConsumerApplication.class,args);
    }
}

当客户端发起Rest请求,就可以看到由原来的的轮训算法替换成了随机算法

负载均衡轮训算法原理

原理:Reset接口第几次请求%服务器集群总数=实际调用的服务器位置下标,每次服务器重启接口默认又从第一次请求开始计数,而远程服务器集群实例是保存在一个List中,每次通过模运算获取到下标index后,通过list.get(index)来作用到具体的服务器。RoundRobinRule源码如下:

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (server == null && count++ < 10) {
                    List<Server> reachableServers = lb.getReachableServers();
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
                    if (upCount != 0 && serverCount != 0) {
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        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;
            }
        }
    }
		//通过CAS获取合适的数值
		private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextServerCyclicCounter.get();
            next = (current + 1) % modulo;
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
    }

手写负载均衡轮训算法

根据上面了解到负载均衡轮训原理,现在我们参照上面的源码手写一遍具体实现过程:

  • 自定义一个LoadBalancer
public interface XfLoadBalancer {

   ServiceInstance getInstance(List<ServiceInstance>serviceInstanceList);
}
  • 具体实现
/**
 * @Description 自定义负载均衡类
 * @Author Fangchenjiang
 * @Date 2021/4/4 18:47
 */
@Component
public class MyLB  implements XfLoadBalancer{

    @Autowired
    DiscoveryClient discoveryClient;
    public AtomicInteger serverCount=new AtomicInteger(0);

    @Override
    public ServiceInstance getInstance(List<ServiceInstance> serviceInstanceList) {
        int index=getNextIndex()%serviceInstanceList.size();
        return serviceInstanceList.get(index);
    }



    public final  int getNextIndex(){

        int current;
        int next;

        do{
            current=this.serverCount.get();
            next=current>=2147483167?0:current+1;
        }while (!this.serverCount.compareAndSet(current,next));
            System.out.println("第"+next+"几次访问");
            return next;

    }
}
  • Rest调用
/**
 * @Description 消费者COntroller
 * @Author Fangchenjiang
 * @Date 2021/4/3 23:03
 */
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    MyLB myLB;
    private  static String  URL="http://xf-order/";
    @Autowired
    RestTemplate restTemplate;
    @GetMapping("/getById/{id}")
    public String getOrderByID(@PathVariable("id")Long id){
        List<ServiceInstance> instances = discoveryClient.getInstances("xf-order");
        ServiceInstance instance = myLB.getInstance(instances);
        URI uri = instance.getUri();
        String  url=uri+"/order/getPay/"+id;
        String object = restTemplate.getForObject(url, String.class);
        return object;
    }
}

如果在浏览器请求消费者接口,能看到负载均衡效果,说明我们成功的实现了轮训的实现过程

总结

Ribbon是客户端实现负载均衡的一种方式,具体到其里面的IRule组件,提供不同的实现类,也就对应着不同的负载均衡算法。其中我们由Eureka集群到服务端集群搭建,以及到轮训的原理。通过这一次过程,相信应该对Ribbon有了更深刻的认识以及负载均衡的运用场景。