Spring Cloud(07)——Ribbon负载均衡服务的介绍和使用

Spring Cloud(07)——Ribbon负载均衡服务的介绍和使用

Spring Cloud(04)——Eureka的简介和部署使用Spring Cloud(05)——Spring cloud整合Zookeeper代替EurekaSpring Cloud(06)——Consul的介绍和使用中,我们分别使用了Eureka、Zookeeper和Consul实现了服务注册与发现的功能,并分析了三个注册中心的异同点。下面开始实现服务消费者向远程的服务提供者发送请求时,实现负载均衡算法和服务调用。

1、Ribbon简介

  • Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。

  • Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API 网关那样需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的。

负载均衡(LB:LoadBalancer)

负载均衡:将用户的请求平摊的分配到多个服务上,从而达到系统的高可用(HA)。

Ribbon本地负载均衡客户端 VS Ngisx服务端负载均衡区别:

  • Nginx是服务器负载均衡,客户单虽有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是有服务端实现的。
  • Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

2、Ribbon的负载均衡和RestTemplate调用

Ribbon一句话总结就是:负载均衡 + RestTemplate调用

Ribbon的负载均衡演示

Ribbon的负载均衡我们在Spring Cloud(04)——Eureka的简介和部署使用中就演示了,那时我们实现的负载均衡策略是轮询,为什么在eureka中就可以实现ribbon负载均衡呢?

那是因为我在引入spring-cloud-starter-netflix-eureka-client依赖时,自动帮我们引入了ribbon依赖:

在这里插入图片描述

RestTemplate

RestTemplate中**getForObject()getForEntity()**的区别:

  • getForObject():返回对象为响应体中数据转化成的对象,基本可以理解为json
  • getForEntity():返回对象为ResponseEntity对象,包含了相应中的一些中要信息,比如响应头。响应状态码、响应体等

3、Ribbon核心组件IRule

Ribbon 作为一款客户端负载均衡框架,默认的负载策略是轮询,同时也提供了很多其他的策略,能够让用户根据自身的业务需求进行选择。

IRule:根据特点算法从服务列表中选取一个要访问的服务

整体负载策略如下表所示:

策略类命名描述
RandomRule随机策略随机选择server
RoundRobinRule轮询策略轮询选择, 轮询index,选择index对应位置的Server;
RetryRule重试策略对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server;
BestAvailableRule最低并发策略逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server
ResponseTimeWeightedRule响应时间加权重策略根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间
AvailabilityFilteringRule可用过滤策略过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值)或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个Server的运行状态;
ZoneAvoidanceRule区域权重策略综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server

4、替换负载均衡策略

1、修改cloud-consumer-order80模块

自定义一个配置类,注意:这个自定义配置类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有Robbon 客户端所共享,达不到特殊化的目的了。

​ 目录结构如下:

在这里插入图片描述

2、编写自定义的配置类

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        return new RandomRule();//定义负载策略为随机
    }
}

3、主启动类加注解@RibbonClient

@SpringBootApplication
@EnableEurekaClient
//name = "CLOUD-PAYMENT-SERVICE"服务提供者名称 configuration = MySelfRule.class 使用客户端自己选择的负载策略
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

4、测试

  1. 启动cloud-eureka-server7001和cloud-eureka-server7002模块
  2. 启动cloud-provider-payment8001和cloud-provider-payment8002模块
  3. 启动cloud-consumer-order80模块

访问请求:http://localhost/consumer/payment/get/1

第一次访问:8002端口
在这里插入图片描述

第二次访问:还是8002

在这里插入图片描述
第三次访问:8001

在这里插入图片描述

到这里我们就实现了自定义负载策略 / 算法,如果想用其他几种策略,稍作修改即可。

5、深入理解Ribbon负载均衡算法

5.1、默认负载策略轮询的原理

轮询原理:rest接口第几次请求数 % 服务集群总数量 = 实际调用服务器位置下标

注:每次服物重启后,rest接口计数从1开始

在这里插入图片描述

5.2、RoundRobinRule轮询源码

RoundRobinRule的源码如下(只粘贴实现负载的代码):

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

    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {
        List<Server> reachableServers = lb.getReachableServers();//获得活着的服务
        List<Server> allServers = lb.getAllServers();//获得全部的服务
        
        int upCount = reachableServers.size();//获得活着的服务数量
        int serverCount = allServers.size();//获得全部的服务的数量

        //如果活着的服务和全部的服务为0
        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

        int nextServerIndex = incrementAndGetModulo(serverCount);//CAS + 自旋锁获得服务下标
        server = allServers.get(nextServerIndex);

        if (server == null) {
            /* Transient. */
            Thread.yield();
            continue;
        }

        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // Next.
        server = null;
    }

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

5.3、自定义实现轮询算法

分析完源码后,我们按照源码的思路来自定义实现轮询算法。

1、启动cloud-eureka-server7001和cloud-eureka-server7002模块

2、修改cloud-provider-payment8001和cloud-provider-payment8002模块的controller

在两个模块的controller上都增加下面方法:

@GetMapping(value = "/payment/lb")
public String getPaymentLb(){
    return serverPort;
}

3、修改cloud-consumer-order80模块

  1. 注释掉@LoadBalanced注解:不使用ribbon的负载均衡策略

    @Configuration
    public class ConfigBean {
    
        @Bean
        //@LoadBalanced 
        public RestTemplate getRestTemplate(){
            return new RestTemplate();
        }
    }
    

4、新建LoadBalancer接口

LoadBalancer接口:

public interface LoadBalancer {

    //获得服务所有的实例
    ServiceInstance instances(List<ServiceInstance> serviceInstanceList);

}

编写LoadBalancer接口的实现类LoadBalancerImpl:

package com.cheng.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class LoadBalancerImpl implements LoadBalancer{

    //AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。初始值为0
    private AtomicInteger atomicInteger =  new AtomicInteger(0);

    public final int getAndIncrement(){
        int current;
        int next; 
        do {
            current = this.atomicInteger.get();//获得当前的值
            next = current >= 2147483647 ? 0 : current + 1;//2147483647int的最大值  如果current>=1194525857,就让next为0,否则就让next=current + 1
            //利用CSA和自旋操作取到我们想要的值
        }while(!this.atomicInteger.compareAndSet(current,next));
        System.out.println("==============next="+next);
        return next;//返回rest接口第几次请求数

    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstanceList) {
//实际调用服务器位置下标 = rest接口第几次请求数 % 服务集群总数量
        int index =  getAndIncrement() % serviceInstanceList.size();

        return serviceInstanceList.get(index);//通过下标返回具体的服务实例
    }
}

CSA和自旋操作博主还不是很了解,有错误请指点。

5、编写controller

@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLb(){
    //通过服务名获得该服务的所有服务实例列表
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    if (instances == null || instances.size() <= 0){
        return null;
    }
    //调用loadBalancer接口中的instances方法,参数为服务实例列表instances,返回的是一个具体的实例serviceInstance
    ServiceInstance serviceInstance = loadBalancer.instances(instances);
    URI uri = serviceInstance.getUri();
    return restTemplate.getForObject(uri+"/payment/lb",String.class);


}

访问请求:http://localhost/consumer/payment/lb

第一次访问:

在这里插入图片描述

第二次访问:

在这里插入图片描述

自定义实现轮询算法成功!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万里顾—程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值