Ribbon

一、 Ribbon:负载均衡及Ribbon

ribbon是什么?

  • SpringCloud Ribbon是基于NetFlix Ribbon实现的一套客户端负载均衡的工具(轮询、随机两种算法)

ribbon能干嘛?

  • LB,即负载均衡
  • 负载均衡简单的说就是将用户请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
  • 常见的负载均衡软件有Nginx、Lvs等
  • Spring Cloud的负载均衡算法可以自定义
  • 负载均衡简单分类:
  • 集中式LB
  • 即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由该设施负责把访问请求通过某种策略转发至服务的提供方
  • 进程式LB
  • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器
  • Ribbon就属于进程内LB,他只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址
  1. 在springcloud-consumer-dept-80子模块pom增加Ribbon和Eureka的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud</artifactId>
        <groupId>com.giao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-dept-80</artifactId>
    <dependencies>
<!--        Ribbon-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
<!--        eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.giao</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>

</project>
  1. springcloud-consumer-dept-80子模块增加Eureka配置
server:
  port: 80

#Eureka配置
eureka:
  client:
    register-with-eureka: false #不像Eureka中注册自己
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
  1. 启动类加Enable注解
package com.giao.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

//Ribbon和Eureka整合以后客户端可以直接调用,不用关心ip地址
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
    public static void main(String[] args) {

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

4.配置负载均衡实现RestTemplate增加一个注解@@LoadBalanced即可

package com.giao.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {//@Configuration---Spring的applicaContext.xml
    //配置负载均衡实现RestTemplate
    @Bean
    @LoadBalanced//Ribbon
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}

5.通过ribbon实现,我们这里的地址应该是一个变量,通过服务名来访问

package com.giao.springcloud.controller;

import com.giao.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class DeptConsumerController {
    //理解:消费者,不应该有service层
    //RestTemplate供我们调用,注册到spring中
    //(url,s实体:Map,Class<T> responseType)
    @Autowired
    private RestTemplate restTemplate;//提供多种便捷访问远程http 服务的方法,简单的的restful服务模版
   //通过ribbon实现,我们这里的地址应该是一个变量,通过服务名来访问
//    private static final String REST_URL_PREFIX = "http://localhost:8001";
   private static final String REST_URL_PREFIX = "SPRINGCLOUD-PROVIDER-DEPT";

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get" + id, Dept.class);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
    }
}

二、 Ribbon:使用Ribbon实现负载均衡

springcloud yml中本机ip springcloud lb_spring

1.新建两个子项目springcloud-provider-dept-8002、springcloud-provider-dept-8003springcloud-eureka-7003

2.将springcloud-provider-dept-8001的pom中的依赖复制粘贴至springcloud-provider-dept-8003、springcloud-provider-dept-8003

3.将springcloud-provider-dept-8001的resource文件夹复制粘贴至springcloud-provider-dept-8002、springcloud-provider-dept-8003,分别修改application.yaml的端口号为8002,8003,并将数据库配置修改为db02,db03,修改eureka上的默认描述信息分别为 instance-id: springcloud-privider-dept-8002, instance-id: springcloud-privider-dept-8003

4.复制springcloud-provider-dept-8001的com.giao.springcloud

包下的所有文件至两个子项目springcloud-provider-dept-8002,springcloud-provider-dept-8003,修改启动类为对应项目,图示,8003同理,三个服务提供者唯一不同的是数据库连的分别是db01,db02,db03

springcloud yml中本机ip springcloud lb_ide_02


springcloud yml中本机ip springcloud lb_负载均衡_03

三、Ribbon:自定义负载均衡算法

springcloud yml中本机ip springcloud lb_spring_04

  • 配置负载均衡实现RestTemplate
  • IRule
  • RoundRobinRule 轮询
  • RandomRule 随机
  • AvailabilityFilteringRule:会先过滤掉跳闸、访问故障的服务,对剩下的进行轮询
  • RetryRule:会先按照轮询获取服务 如果服务获取失败,则会在指定时间内进行重试

1.新建一个com.giao.myrule包,调自己写的随机算法

springcloud yml中本机ip springcloud lb_spring_05

package com.giao.myrule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration

public class GiaoRule {
    @Bean
    public IRule myRule(){
        return new GiaoRandomRule(); //默认是轮询,调自己写的随机算法
    }
}
package com.giao.myrule;




import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;


public class GiaoRandomRule extends AbstractLoadBalancerRule {

//每个服务访问五次,换下一个服务(3个)
    //total=0,默认=0,如果=5,我们指向下一个服务节点
    //index=0,默认=0,如果total=5,index+1
    private int total=0;//被调用的次数
    private int currentIndex=0;//当前是谁在提供服务

//    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }

                List<Server> upList = lb.getReachableServers();//获得活着的服务
                List<Server> allList = lb.getAllServers();//获得全部的服务
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }

//                int index = this.chooseRandomInt(serverCount);//生成区间随机数
//                server = (Server)upList.get(index);//从活着的服务随机获取一个

                //-=================================================================
                   if(total<5){
                      server = upList.get(currentIndex);
                      total++;
                   }else{
                       total=0;
                       currentIndex++;
                       if(currentIndex>upList.size()){
                           currentIndex=0;
                       }
                       server = upList.get(currentIndex);//从活着的服务中,获取指定的服务来进行操作
                   }



                //-=================================================================
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

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

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}