Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
Ribbon本地负载均衡和Nginx负载均衡的区别
- Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务器实现的。
- Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到本地,从而在本地实现RPC远程服务调用技术。由客户端进行负载,根据负载均衡策略发送请求。
Ribbon引入
Eureka Client自动带着Ribbon的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
自己手动添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<!--<version>2.1.1.RELEASE</version>-->
</dependency>
注册RestTemplate的bean上添加@LoadBalanced注解开启负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
Ribbon负载均衡规则
- 轮询(RoundRobinRule):com.netflix.loadbalancer.RoundRobinRule
- 随机(RoundRobinRule):com.netflix.loadbalancer.RoundRobinRule
- RetryRule:默认使用了RoudRobinRule实例,如果服务获取失败则在指定时间内进行重试,获取可用的服务
- WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule:会先过滤掉由于多次访问故障而处于断路跳闸状态的服务,然后选择一个并发最小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:判断server所在区域的性能和server的可用性选择服务
Ribbon负载均衡规则替换
自定义值类不能放在@ComponentScan所扫描的当前包下以及子包下。否则自定义的这个配置类会被所有的Ribbon客户端所共享,达不到特殊化定制的目的。
-
自定义配置类,配置类所在的包不要在**@ComponentScan所扫描的当前包下以及子包下**
@Configuration public class MyRibbonRule { @Bean public IRule selfRule(){ return new RandomRule(); } }
-
启动类型添加@RibbonClient注解,配置调哪个服务使用什么负载均衡规则
@SpringBootApplication @EnableEurekaClient @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyRibbonRule.class) //@RibbonClients public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class,args); } }
负载均衡算法(轮询):调用接口的次数(t) % 服务器集群的总数量c = 实际调用服务器位置下标(i)。(取模) t % c= i。服务重启 t = 0
手写轮询算法
-
创建接口,定义传入全部服务信息方法
public interface LoadBalancer { /** * 传入全部服务实例 * @param instanceList * @return */ ServiceInstance instances(List<ServiceInstance> instanceList); }
-
创建算法实现类,集成定义的LoadBalancer接口
@Component //注册成spring的bean public class MyRoundRobinRule implements LoadBalancer{ /** * 线程安全的变量 */ private AtomicInteger atomicInteger = new AtomicInteger(0); private int getAndIncrement(){ int current,next; do { current = this.atomicInteger.get(); //防止int月结,把下个值赋给next,去做比较交换cas next = current >= Integer.MAX_VALUE ? 0 : current + 1; //cas比较交换,不成功继续循环 }while (!this.atomicInteger.compareAndSet(current,next)); return next; } /** * @param instanceList 服务信息 * @return */ @Override public ServiceInstance instances(List<ServiceInstance> instanceList) { int times = 0; //没有服务信息重试,还没有报错 while (instanceList == null || instanceList.size() == 0){ System.out.println("没有服务信息,三秒后重试======"); times ++; try { Thread.sleep(3 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } if(times > 2){ throw new RuntimeException("No service information"); } } //取模,算出请求的服务的下标 int index = getAndIncrement() % instanceList.size(); return instanceList.get(index); } }
-
调用服务测试
@Resource private LoadBalancer loadBalancer; /** * 获取注册中心服务信息,需要springboot启动类添加@EnableDiscoveryClient注解 */ @Resource private DiscoveryClient discoveryClient; @GetMapping(value = "payment") public String getPayment(){ List<ServiceInstance> instanceList = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); ServiceInstance serviceInstance = loadBalancer.instances(instanceList); //获取轮序算法返回的服务的uri URI uri = serviceInstance.getUri(); //restTemplate(去掉@LoadBalanced注解)发送请求 return restTemplate.getForObject(uri + "/payment",String.class); }