1.Ribbon 简介: Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端 负载均衡的工具。
Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出 Load Balancer(简称LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询、随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
官网资料:https://github.com/Netflix/ribbon/wiki/Getting-started
PS: Ribbon目前也进入维护模式,SpringCloud 想用Spring Cloud LoadBalancer 替代 Netflix 的Ribbon ,但现在 Ribbon 在生产环境中大规模部署,一时半会替不掉
作用 : LB负载均衡(Load Balance)
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡有软件 Nginx,LVS,硬件F5 等。
集中式B 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方
进程内LB 将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内 LB ,它只是一个类库,集成与消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon 本地负载均衡客户端 和 Nginx 服务端负载均衡 区别:
Nginx 是服务器负载均衡 ,客户端所有请求都会交给 nginx ,然后由 nginx 实现转发请求。即负载均衡是由服务端实现的。
Ribbon 本地负载均衡 ,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现 RPC 远程服务调用技术。
总之一句话 : Ribbon 就是 负载均衡 + RestTemplate调用,最终实现RPC的远程调用。
Ribbon 是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和 eureka 结合只是其中的一个实例。
架构说明:
Ribbon 在工作时分成两步:
第一步先选择 EurekaServer,它优先选择在同一个区域内负载较少的server
第二步再根据用户指定的策略,在从server 取到的服务注册列表中选择一个地址
其中Ribbon 提供了多种策略:比如轮询、随机和根据响应时间加权
新版eureka引入了ribbon,所以不用自己引入也可以使用负载均衡
RestTemplate使用:
官网 :https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
getForObject 方法 / getForEntity方法
2.Ribbon核心组件IRule IRule : 根据特定算法从服务列表中选取一个要访问的服务
实现类:
com.netflix.loadbalancer.RoundRobinRule 轮询
com.netflix.loadbalancer.RandomRule 随机
com.netflix.loadbalancer.RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器
3.Ribbon 负载规则替换 3.1添加规则类: 注意 : 官方文档明确给出了警告:
这个自定义配置类不能放在 @ComponentScan 所扫描的当前包下以及子包下,否则自定义的配置类就会被所有的 Ribbon 客户端所共享,达不到特殊化定制的目的了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.atguigu.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 MySelfRule { @Bean public IRule myRule () { return new RandomRule (); } }
3.2 主启动类添加 @RibbonClient 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.atguigu.springcloud;import com.atguigu.myrule.MySelfRule;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.cloud.netflix.ribbon.RibbonClient;@SpringBootApplication @EnableEurekaClient @RibbonClient(name = "CLOUD-PROVIDER-SERVICE",configuration = MySelfRule.class) public class OrderMain80 { public static void main (String[] args) { SpringApplication.run(OrderMain80.class,args); } }
4.3 测试
多次刷新,是随机出现 serverPort ,负载规则就更改为随机了。
5.Ribbon 默认负载均衡算法 原理 + 手写 默认负载均衡算法: 轮询
rest 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
每次服务器重启后rest接口数从1开始
Listinstances = discoveryClient.getInstances(“CLOUD-PROVIDER-SERVICE”)
如:
1 2 List[0 ] instances = 127.0 .0 .1 :8001 List[1 ] instances = 127.0 .0 .1 :8002
8001 + 8002 组合成为集群,它们共计2台机器,集群总数为2,按照轮询算法原理:
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface IRule { public Server choose (Object key) ; public void setLoadBalancer (ILoadBalancer lb) ; public ILoadBalancer getLoadBalancer () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class RoundRobinRule extends AbstractLoadBalancerRule { private AtomicInteger nextServerCyclicCounter; private static final boolean AVAILABLE_ONLY_SERVERS = true ; private static final boolean ALL_SERVERS = false ; private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class); public RoundRobinRule () { nextServerCyclicCounter = new AtomicInteger (0 ); } public RoundRobinRule (ILoadBalancer lb) { this (); setLoadBalancer(lb); } 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(); if ((upCount == 0 ) || (serverCount == 0 )) { log.warn("No up servers available from load balancer: " + lb); return null ; } int nextServerIndex = incrementAndGetModulo(serverCount); server = allServers.get(nextServerIndex); if (server == null ) { Thread.yield(); continue ; } if (server.isAlive() && (server.isReadyToServe())) { return (server); } 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) { for (;;) { int current = nextServerCyclicCounter.get(); int next = (current + 1 ) % modulo; if (nextServerCyclicCounter.compareAndSet(current, next)) return next; } } @Override public Server choose (Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig (IClientConfig clientConfig) { } }
5.1手写轮询算法: 7001/7002集群启动
8001/8002微服务改造(8002也一样不截图了)
80订单微服务改造
1.不使用ribbon自带的负载均衡,ApplicationContextConfig 去掉注解@LoadBalanced 1 2 3 4 5 6 7 8 9 @Configuration public class ApplicationContextConfig { @Bean public RestTemplate getRestTemplate () { return new RestTemplate (); } }
2.LoadBalancer 接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.atguigu.springcloud.lb;import org.springframework.cloud.client.ServiceInstance;import java.util.List;public interface LoadBalancer { ServiceInstance instances (List<ServiceInstance> serviceInstanceList) ; }
3.MyLB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.atguigu.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 MyLB implements LoadBalancer { 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 ; }while (!this .atomicInteger.compareAndSet(current,next)); System.out.println("*****第几次访问,次数next:" +next); return next; } @Override public ServiceInstance instances (List<ServiceInstance> serviceInstance) { int index = getAndIncrement() % serviceInstance.size(); return serviceInstance.get(index); } }
4.OrderController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.atguigu.springcloud.controller;import com.atguigu.springcloud.entities.CommonResult;import com.atguigu.springcloud.entities.Payment;import com.atguigu.springcloud.lb.LoadBalancer;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.DiscoveryClient;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import javax.annotation.Resource;import java.net.URI;import java.util.List;@RestController @Slf4j public class OrderController { private static final String PAYMENT_URL = "http://CLOUD-PROVIDER-SERVICE" ; @Resource private RestTemplate restTemplate; @Resource private LoadBalancer loadBalancer; @Resource private DiscoveryClient discoveryClient; @GetMapping(value = "/consumer/payment/lb") public String getPaymentLB () { List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-SERVICE" ); if (instances == null || instances.size() <= 0 ){ return null ; } ServiceInstance serviceInstance = loadBalancer.instances(instances); URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri+"/payment/lb" ,String.class); } }
5.启动80 测试
多次刷新,会发现8001、8002依次出现,控制台也打印消息