Ribbon

1. 负载均衡(Load Balance)

SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具

LB(负载均衡)简单的说就是讲用户的请求平摊的分配到多个服务上,从而达到系统HA(高可用).常见的有Nginx,LVS,硬件F5等.其分为:

  1. 进程内LB:将LB逻辑继承到消费方,消费方从服务注册中心获取那些地址可用,然后自己再从这些地址中选出一个合适的服务器
  2. 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件F5,也可以是软件Nginx),由该设施负责把访问请求通过某种策略转发至提供方

在相应中间件,例如dubbo和SpringCloud中均提供了负载均衡.SpringCloud的负载均衡算法可以自定义

2. pom.xml

<!--Ribbon需要跟Eureka整合-->
    <dependency>
        <groupId>org.springframework.cloud<groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
    </dependency>

3. application配置

//追加eureka的服务注册地址
    eureka:
        client:
            register-with-eureka:false
            service-url:
                defaultZone:http://eureka.com    #这里改成eureka服务地址

4. java代码

第一种:

RestTemplate restTemplate = new RestTemplate();
       String response = restTemplate.getForObject("http://localhost:8080/msg",String.class);

第二种:

@Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/girl/print")
    public String print() {
        RestTemplate restTemplate = new RestTemplate();
        ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");//服务名
        String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort());
        String response = restTemplate.getForObject(url, String.class);
        return "";
    }

第三种:

添加Bean

//客户端启动类添加
    @EnableEurekaClient

    //如果是Rest风格在RestTemplate上添加@LoadBalanced注解
    @Bean    
    @LoadBalanced        //开启负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate
    }

应用代码

@Autowired
    private RestTemplate restTemplate;

    @GetMapping("/girl/print")
    public String print() {
        String response = restTemplate.getForObject("http://PRODUCT/msg", String.class);//将调用地址改为服务端在EurekaServer注册的服务名,如果是集群在Eureka注册名要相同
        return "";
    }

注:RestTemplate 针对get、post、put、delete提供了不同的调用函数

springcloud引入lombok springcloud lb_自定义

 

 

 

5. IRule

根据特定算法从服务列表中选取一个要访问的服务,默认规则如下
  1. RoundRobinRule
    轮询
  2. RandomRule
    随机
  3. AvailabilityFilteringRule
    会过滤掉多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阀值的服务,然后对剩余服务列表按照轮询策略进行访问
  4. WeightedResponseTimeRule
    根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越高.刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到此算法
  5. RetryRule
    先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
  6. BestAvailableRule
    回显过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  7. ZoneAvoidanceRule
    默认规则,符合判断server所在区域的性能和server的可用性选择服务器
修改轮询算法

第一种

@Bean
    public IRule myRule(){
        return new RandomRule();    //自己创建实例后就,系统就不会自动配置相应的实例,达到实现要更换的算法
    }

第二种

pom.xml

<clientName>.<clientConfigNameSpace>.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.<className>

第三种:

@RibbonClient 主要用于配置RibbonClient客户端的,而且这个注解在我们服务发现中不是必须要配置的,如果我们使用SpringCloud中的服务发现机制,此时SpringCloud会给我们提供默认的Ribbon配置,甚至我们不需要配置@RibbonClient,不过当我们需要定义自己的RibbonClient或者不实用服务发现时,那么我们可以使用@RibbonClient注解

  使用例子:

  在我们的启动类上添加如下注解

@RibbonClient(name = "myservice")

然后我们在application.properties做如下配置:

myservice.ribbon.eureka.enabled=false
myservice.ribbon.listOfServers=http://localhost:5000, http://localhost:5001

 

自定义算法配置细节:
这个自定义配置类不能放在@ComponentScan所扫描的当前包以及子包下(不能与启动类在一块 ),否则我们定义的这个配置类会被所有Ribbon客户端所共享,从而达不到特殊化定制的目的

自定义算法示例:

//实现一个随机并且不能与上一次重复的负载均衡算法
    public class RandomRule extends AbstractLoadBalancerRule {
        //上一次的访问服务的下标        
        int prev = -1;

        @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
        public Server choose(ILoadBalancer lb, Object key) {
            if (lb == null) {
                return null;
            }
            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 = chooseRandomInt(serverCount);
                //server = upList.get(index);

                //自定义的逻辑
                int index = chooseRandomInt(serverCount);
                //记录第一次使用的服务器
                prev = prev == -1 ? index : prev;
                //如果与上一次相等跳出循环(第一次直接跳出循环)
                if(prev == index){
                    continue;
                } else {
                    //得到了符合逻辑的服务器
                    server = upList.get(index);
                    //重新赋值
                    prev = index;
                }
                
                if (server == null) {
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive()) {
                    return (server);
                }
    
                // Shouldn't actually happen.. but must be transient or a bug.
                server = null;
                Thread.yield();
            }
    
            return server;
    
        }
    
        protected int chooseRandomInt(int serverCount) {
            return ThreadLocalRandom.current().nextInt(serverCount);
        }
    
        @Override
        public Server choose(Object key) {
            return choose(getLoadBalancer(), key);
        }
    }