一、简介
1、Ribbon 简介
Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon 客户端组件提供一系列完善的配置项如连接超时、重试等。简单的说就是在配置文件中列出Load Balancer 后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法(负载均衡+服务调用)。
github 地址 https://github.com/Netflix/ribbon ,现在项目处于维护状态中。
2、LB负载均衡(Load Balance)
- 将用户的请求平摊到多个服务上,从而达到系统的高可用(HA)。常见的负载均衡软件有Nginx、LVS等。
- Ribbon 本地负载均衡与Nginx 服务端负载均衡的区别
- Nginx 是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡由服务端实现。
- Ribbon本地负载均衡在调用服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。Ribbon属于进程内负载均衡(将LB逻辑集成到消费方,消费方从服务注册中心获知有那些地址可用,然后自己再从这些地址中选出一个合适的服务器)。
3、工作步骤
-
选择Eureka Server,优先选择同一个区域内负载较少的Server;
-
根据用户指定的策略(轮询、随机、根据响应时间加权),在server取到的服务注册列表中选择一个地址。
二、Ribbon负载均衡演示
1、在和Eureka集成的时候,只需要引入最新的Eureka的版本即可,因为Eureka以及集成了Ribbon。
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
然后结合RestTemplate使用即可。
三、Ribbon核心组件IRule
1、简介
根据特定算法从服务列表中选取一个要访问的服务。接口类图如下:
- com.netflix.loadbalacer.RoudRobinRule:轮询;
- com.netflix.loadbalacer.RandomRule:随机;
- com.netflix.loadbalacer.RetryRule:先按照RoudRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务。
- WeightedResponseRule:对RoudRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;
- BestAvailableRule:会优过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器。
2、负载规则配置
- 自定义的规则配置不能放在 @ConponentScan 能扫描到的包及其子包,否则定义的配置类就会被所有的Ribbon客户端使用,起不到特殊化定制的效果。
- 自定义配置类的编写
@SpringBootConfiguration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule(); //规则定义为随机
}
}
- 在主启动类上加上注解 @RibbonClient(value = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)。
四、Ribbon负载均衡算法
1、轮询原理
rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest从1开始重新计数。
2、轮询源码
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while (true) {
if (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) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server) allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
3、实现一个负载均衡算法(JUC+自旋锁)
- 定义一个接口;
public interface LoadBalancer {
/**
* 获取执行当前请求的服务器
* @param serviceInstances
* @return
*/
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
- 实现接口,需要添加一个 @Component;
@Component //让容器能够扫描到
public class MyLB implements LoadBalancer {
//原子类,初始值0
private AtomicInteger atomicInteger = new AtomicInteger(0);
/**
* 获得第几次访问的数
* @return
*/
public final int getAndIncrement(){
int current;
int next;
//自旋锁
do {
current = this.atomicInteger.get();
// 获取下一个请求次数,整型最大为2147483647
next = current >= 2147483647 ? 0 : current + 1;
}while (!this.atomicInteger.compareAndSet(current,next));
return next;
}
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
//访问到次数与集群服务器数量取模,获得该由哪个服务器执行该请求
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
- 服务调用
@Resource
private RestTemplate restTemplate;
@Resource
private LoadBalancer loadBalancer;//引入自定义的负载均衡算法
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/consumer/payment/lb")
public String getPaymentLB(){
//获取服务集合
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-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);
}