SpringCloud(H版&Alibaba)技术(五) —— Ribbon负载均衡服务调用

本文介绍了如何使用Ribbon实现客户端负载均衡,包括其与RestTemplate的整合,以及如何通过自定义IRule实现轮询算法的替换。详细步骤涉及Ribbon配置、自定义规则类和eureka集群的实践。
摘要由CSDN通过智能技术生成

目录

简介​

        Ribbon能干什么?

        LB(负载均衡)​​​Ribbon :负载均衡+RestTemplate调用

        Ribbon负载均衡演示

                RestTemplate的使用        

        Ribbon核心组件IRule

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

替换 

        Ribbon负载均衡算法

原理:​

RoundRobinRule源码

手写一个轮询自定义配置类


恢复eureka集群环境,并启动,以便接下来的练习。

检查环境:eureka7001.com:7001

简介

官网:https://github.com/Netflix/ribbon/wiki/Getting-Started
Ribbon目前也进入维护模式

未来替换方案:

Ribbon能干什么?

LB(负载均衡)



Ribbon :负载均衡+RestTemplate调用

Ribbon负载均衡演示

Ribbon是客户端(消费者)负载均衡的工具。

总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。




 spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-netflix-ribbon

也可以单独加依赖 

  <!--Ribbon的依赖-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  </dependency>

 RestTemplate的使用

 官网:RestTemplate (Spring Framework 5.2.2.RELEASE API)

getForObject方法/getForEntity方法


postForObject/postForEntity

GET请求方法  - 测试getForEntity方法

在消费者cloud-consumer-order80的OrderController方法中添加:

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
       
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);

        //getStatusCode获取状态码,is2xxSuccessful如果是状态码是2xx
        if(entity.getStatusCode().is2xxSuccessful()){
            //返回body
            return entity.getBody();
        }else{
            return new CommonResult<>(444, "操作失败");
        }
    }

测试:


POST请求方法

    @GetMapping("/consumer/payment/createEntity")
    public CommonResult<Payment> create2(Payment payment){
        log.info("********插入的数据:" + payment);
        //postForObject分别有三个参数:请求地址,请求参数,返回的对象类型
//        return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
        ResponseEntity<CommonResult> entity = restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else{
            return new CommonResult<>(444, "操作失败");
        }
    }

 输入http://localhost//consumer/payment/createEntity?serial=emily,插入一条数据。

Ribbon核心组件IRule

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

IRule接口的实现类:(快捷键double shift 弹出search everywhere)

主要的落地实现类:


默认为RoundRobinRule轮询。

替换 

    1.修改cloud-consumer-order80
    2.注意配置细节    

所以Ribbon的自定义配置类不能放在springcloud包下,要在aurora包下再新建一个myrule包。
1.新建package  com.aurora.myrule

2.在myrule包下新建MySelfRule规则类

@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule(){
        return new RandomRule();    //负载均衡机制改为随机
    }
}

3.在主启动类添加@RibbonClient 

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class
name为指定的服务名(服务名必须与注册中心显示的服务名大小写一致)
configuration为指定服务使用自定义配置(自定义负载均衡机制)

4.测试 
   启动80  浏览器输入:http://localhost/consumer/payment/get/31 
  8001 8002 随机出现 替换了默认的轮询

Ribbon负载均衡算法

原理:

RoundRobinRule源码



RoundRobinRule的核心为choose方法:
 

public class RoundRobinRule extends AbstractLoadBalancerRule {
	//AtomicInteger原子整形类
    private AtomicInteger nextServerCyclicCounter;
	...
    public RoundRobinRule() {
    	//此时nextServerCyclicCounter是一个原子整形类,并且value为0
        nextServerCyclicCounter = new AtomicInteger(0);
    }
	...
	//ILoadBalancer选择的负载均衡机制,这里lb为轮询
    public Server choose(ILoadBalancer lb, Object key) {
    	//如果传入的lb没有负载均衡,为空
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        //还没选到执行的server,并且选择的次数没超过10次,进行选择server
        while (server == null && count++ < 10) {
        	//lb.getReachableServers获取所有状态是up的服务实例
            List<Server> reachableServers = lb.getReachableServers();
            //lb.getAllServers获取所有服务实例
            List<Server> allServers = lb.getAllServers();
            //状态为up的服务实例的数量
            int upCount = reachableServers.size();
            //所有服务实例的数量
            int serverCount = allServers.size();
			
			//如果up的服务实例数量为0或者服务实例为0,打印日志log.warn并返回server=null
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
			
			//获取到接下来server的下标
            int nextServerIndex = incrementAndGetModulo(serverCount);
            //获取下一个server
            server = allServers.get(nextServerIndex);

			//如果
            if (server == null) {
                //线程让步,线程会让出CPU执行权,让自己或者其它的线程运行。(让步后,CPU的执行权也有可能又是当前线程)
                Thread.yield();
                //进入下次循环
                continue;
            }
			
			//获取的server还活着并且还能工作,则返回该server
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            //否则server改为空
            server = null;
        }

		//选择次数超过10次,打印日志log.warn并返回server=null
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }


    private int incrementAndGetModulo(int modulo) {
    	//CAS加自旋锁
    	//CAS(Conmpare And Swap):是用于实现多线程同步的原子指令。CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
    	//自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。 
        for (;;) {
        	//获取value,即0
            int current = nextServerCyclicCounter.get();
            //取余,为1
            int next = (current + 1) % modulo;
            //进行CAS判断,如果此时在value的内存地址中,如果value和current相同,则为true,返回next的值,否则就一直循环,直到结果为true
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }
    ...
}

 AtomicInteger的compareAndSet方法:

public class AtomicInteger extends Number implements java.io.Serializable {
	...
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    ...
}

手写一个轮询自定义配置类

1.7001/7002集群启动

2.8001和8002微服务改造

在8001和8002的PaymentController中加上这个方法,用于测试我们的自定义轮询:

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

80订单微服务改造
  1.去掉ApplicationContextConfig里restTemplate方法上的@LoadBalanced注解。

  2.在springcloud包下新建lb.ILoadBalancer接口(自定义负载均衡机制(面向接口))

public interface LoadBalancer {
    //传入具体实例的集合,返回选中的实例
    ServiceInstance instances(List<ServiceInstance> serviceInstance);
}

   3.在lb包下新建自定义ILoadBalancer接口的实现类MyLB

@Component  //加入容器
public class MyLB implements LoadBalancer {

    //新建一个原子整形类
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement(){
        int current;
        int next;
        do{
            current = this.atomicInteger.get();
            //如果current是最大值,重新计算,否则加1(防止越界)
            next = current >= Integer.MAX_VALUE ? 0 : current + 1;

            //进行CAS判断,如果不为true,进行自旋
        }while (!this.atomicInteger.compareAndSet(current, next));  //第一个参数是期望值,第二个参数是修改值是
        System.out.println("****第几次访问,次数next:" + next);

        return next;
    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstance) {   //得到机器的列表
        //非空判断
      /*  if(serviceInstance.size() <= 0){
            return null;
        }*/
        //进行取余
        int index = getAndIncrement() % serviceInstance.size(); //得到服务器的下标位置
        //返回选中的服务实例
        return serviceInstance.get(index);
    }
}

  4.OrderController

    @Resource
    private LoadBalancer loadBalancer;
    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("/consumer/payment/lb")
    public String getPaymentLB(){
        //获取CLOUD-PAYMENT-SERVICE服务的所有具体实例
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if(instances == null || instances.size() <= 0){
            return null;
        }

        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();
        System.out.println(uri);

        return restTemplate.getForObject(uri + "/payment/lb", String.class);
    }

      5.测试
         浏览器输入:http://localhost/consumer/payment/lb

下一篇:SpringCloud(H版&Alibaba)技术(六) —— OpenFeign服务接口调用​​​​​​​ 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值