Ribbon 基于 Netflix Ribbon 实现的一套客户端负载均衡的工具 。负载均衡(Load Balance - LB)就是将用户请求通过特定策略分配到多个服务上,从而达到系统的高可用(HA)。
与 Nginx 的不同之处 Nginx 是服务器负载均衡,客户端所有请求都会交给 Nginx,然后由 Nginx 实现请求转发。即负载均衡是由服务端实现的。 Ribbon 则是本地负载均衡,在调用微服务接口时,会在注册中心上获取注册信息服务列表,然后缓存到本地 JVM,从而在本地实现远程服务调用。
集中式 LB 即在服务的消费方和提供方之间使用独立的 LB 设施(可以是硬件,如 F5;也可以是软件,如 Nginx),由该设施负责把请求通过某种策略转发至服务的提供方。
进程内 LB 将 LB 逻辑集成到消费方,消费方从服务注册中心获知哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon 就属于进程内 LB ,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
RestTemplate 请求方法 getForObject() / getForEntity():GET 请求 postForObject() / postForEntity():POST 请求 ForObject 和 ForEntity 的区别在于
ForObject():返回对象为响应体中数据转化成的对象,可以理解为 json。 ForEntity():返回对象为 ResponseEntity 对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。 自带的负载策略 RoundRobinRule:轮询 RandomRule:随机 RetryRule:先按照轮询的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务。 WeightedResponseTimeRule:对轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择。 BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。 AvailabilityFilteringRule:先过滤掉故障实例,再选择并发量较小的实例。 ZoneAvoidanceRule:复合判断 server 所在区域的性能和 server 的可用性选择服务器。 负载策略替换(订单模块) 自定义负载均衡器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package ml.guest997.config; import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;import org.springframework.context.annotation.Bean;import org.springframework.core.env.Environment;public class CustomLoadBalancerConfiguration { @Bean ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String property = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(property , ServiceInstanceListSupplier.class), property ) ; } }
修改配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package ml.guest997.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.loadbalancer.annotation .LoadBalancerClient;import org.springframework.context.annotation .Bean;import org.springframework.context.annotation .Configuration;import org.springframework.web.client.RestTemplate;@Configuration @LoadBalancerClient(name = "PAYMENT" ,configuration = CustomLoadBalancerConfiguration.class) public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
测试 分别启动 Eureka-Server、Payment、Payment2 和 Order 模块后,浏览器多次访问:127.0.0.1/consumer/payment/add?serial=Guest005,会发现端口已经不再是互相切换,而是随机的。
默认的轮询策略原理 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后接口计数从头开始 。
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 private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances ) { if (instances.isEmpty() ) { if (log.isWarnEnabled() ) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse() ; } int pos = Math . abs(this.position.incrementAndGet() ); ServiceInstance instance = instances.get(pos % instances.size() ); return new DefaultResponse(instance ) ; } public final int incrementAndGet() { return unsafe.getAndAddInt(this , valueOffset , 1) + 1 ; } public final int getAndAddInt(Object var1 , long var2 , int var4 ) { int var5; do { var5 = this.getIntVolatile(var1 , var2 ) ; } while (!this.compareAndSwapInt(var1 , var2 , var5 , var5 + var4 ) ); return var5; }
手动实现轮询策略 注释掉原先的策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package ml.guest997.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.loadbalancer.annotation .LoadBalancerClient;import org.springframework.context.annotation .Bean;import org.springframework.context.annotation .Configuration;import org.springframework.web.client.RestTemplate;@Configuration public class ApplicationContextConfig { @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
实现轮询策略 1 2 3 4 5 6 7 8 9 package ml.guest997.config;import org.springframework.cloud.client.ServiceInstance;import java.util.List;public interface LoadBalancer { ServiceInstance getInstance (List<ServiceInstance> serviceInstances) ; }
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 package ml.guest997.config; 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 before , current ; do { before = atomicInteger.get (); //获取之前的访问次数,如果是第一次进行访问,就是0 。 current = before >= 2147483647 ? 0 : before + 1 ; //这个数字是 Integer 的最大值,如果将要超出这个数值就重置为0 ,否则+1 。 } while (!atomicInteger.compareAndSet(before , current )); //为了并发安全,调用这个方法。将 before 在内存中偏移量为 x 位置的值与期望值 current 作比较,相等就把 current 赋值给偏移量为 x 位置的值并返回 true 。 System .out .println("当前是第几次访问:" + current ); return current ; } @Override public ServiceInstance getInstance(List<ServiceInstance> serviceInstances) { int index = getAndIncrement() % serviceInstances.size(); //取余得到下标 return serviceInstances.get (index ); } }
添加支付 Controller 1 2 3 4 @GetMapping (value = "/payment/lb" )public String getPaymentLB ( ) { return serverPort; }
添加订单 Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Resource private LoadBalancer loadBalancer;@Resource private DiscoveryClient discoveryClient;@GetMapping(value = "/consumer/payment/lb" ) public String getPaymentLB() { List<ServiceInstance> instances = discoveryClient.getInstances("PAYMENT" ); if (instances == null instances.size() <= 0 ) { return null ; } ServiceInstance serviceInstance = loadBalancer.getInstance(instances); URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri + "/payment/lb" , String.class ); }
测试 分别启动 Eureka-Server、Payment、Payment2 和 Order 模块后,浏览器多次访问:127.0.0.1/consumer/payment/lb,会发现端口又变回互相切换的了。
Order 模块下的控制台也会输出当前的访问次数。