微服务架构 | 负载均衡 - [Ribbon]

本文介绍了Spring Cloud Ribbon作为本地进程负载均衡器的工作原理,包括其简易使用方法,预设的负载均衡策略如轮询、随机等,并详细解析了Ribbon的通用工作流程。此外,还探讨了如何通过自定义规则替换默认负载均衡策略,以及设置超时控制。最后,展示了RoundRobinRule策略的源码实现,强调在多服务器下可能存在的问题及解决思路。
摘要由CSDN通过智能技术生成

§1 简介

Ribbon 是 Springcloud 原配的负载均衡器
与 nginx 不同,它是本地进程负载均衡,由消费端进行(nginx 本质是个反向代理服务器,可以实现服务端的负载均衡

§2 简易使用

依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  <version>2.2.1.RELEASE</version>
  <scope>compile</scope>
</dependency>

启用负载均衡

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplateBuilder().messageConverters(new GsonHttpMessageConverter(new GsonBuilder().serializeNulls().create())).build();
    }
}

Ribbon 预设的负载均衡规则
Ribbon 的负载均衡策略由 IRule 接口定义,其预设实现类图如下:
在这里插入图片描述

  • RoundRobinRule:轮询
  • RandomRule:随机
  • NacosRule:同集群优先
  • RetryRule:轮询加强,先按照RoundRobinRule(轮询)策略获取服务,如果获取服务失败则在指定时间内进行重试
  • AvailabilityFilteringRule:可用过滤轮询,轮询前先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务
  • WeightedResponseTimeRule:响应时间权重轮询,根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大
  • BestAvailableRule:最佳可用性,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • ZoneAvoidanceRule默认,复合判断Server所在区域的性能和Server的可用性选择服务器

Ribbon 负载均衡规则的替换和自定义
负载均衡策略的替换可以通过自定义 RibbonClient 进行
需注意:此配置不应该至于 @SpringBootApplication@ComponentScan 两种注解的扫描路径下,否则此配置将应用于全局,而不能分业务定制

自定义规则可以在项目或公司级别的common包及其子包中进行定义(需要引用ribbon的相关依赖)
自定义规则

package com.fc.common.ribbon;
import com.netflix.loadbalancer.RandomRule;
public class CustomizedRibbonRule extends RandomRule {
}

完成自定义规则配置

package com.fc.common.ribbon;

//@Configuration 不需要此注解,因为是通过 @RibbonClient 指定的
public class CustomizedRibbonRuleConfigration {
    @Bean
    public IRule customizedRibbonRule(){
        return new CustomizedRibbonRule();
    }
}

使用 @RibbonClient 使生效,示例为对指定的服务(payment-service)使用指定的规则配置

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@RibbonClient(name="payment-service",configuration = CustomizedRibbonRuleConfigration.class)
public class OrderComsummerApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderComsummerApplication.class,args);
    }
}

默认负载均衡规则
有些资料说默认规则是 RoundRobinRule,但从 2.2.1 的源码追溯,应该是 ZoneAvoidanceRule
可能是版本升级了,RoundRobinRule 在多服务器下按他默认的逻辑可能有坑
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
超时控制
因为 ribbon 的 resttemplate 会在通过 RibbonClientConfiguration 进行初始化,而下面的代码写死了 ribbon 超时的参数
因此,直接在 yml 文件中配置超时时间是不会生效的

@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
	DefaultClientConfigImpl config = new DefaultClientConfigImpl();
	config.loadProperties(this.name);
	config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);//默认连接时间
	config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);//默认的业务响应读取时间
	config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
	return config;
}

有效的方式是通过自定义 resttemplate 实例进行配置

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplateBuilder()
                .setConnectTimeout(Duration.ofSeconds(2)) //连接时间
                .setReadTimeout(Duration.ofSeconds(2)) //响应时间
                .messageConverters(new GsonHttpMessageConverter(new GsonBuilder().serializeNulls().create())).build();
    }
}

超时后会出现如下报错
在这里插入图片描述

§3 Ribbon 负载均衡通用工作原理

  • 通过拦截器 LoadBalancerInterceptor 对请求拦截
  • 通过 ILoadBalancer 接口的实现获取
    //ILoadBalancer
    public Server chooseServer(Object key);
    
    //BaseLoadBalancer
    public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        if (rule == null) {
            return null;
        } else {
            try {
                //通过不同的负载均衡策略
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }
    
    具体 choose 的流程见下面源码示例
  • ServerListUpdateer 会启动一个定时任务
    • 30s 一次
    • 从 nacos 客户端获取服务列表数据
  • ILoadBalancer 的实现通过定时 Timer 10 秒一次检查 Server 是否可用
    • Ribbon 其实提供了一个 IPing 接口,但只作为钩子使用(空实现)

§4 负载均衡策略源码

RoundRobinRule

private AtomicInteger nextServerCyclicCounter;//调用次数计数器

public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }

    Server server = null;//被选中的server,默认是没有找到
    int count = 0;//计数器

//只进行10轮choose
    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) {
            /* Transient. */
            Thread.yield();
            continue;
        }
		//线程存活且空闲可用时返回
        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // 选中的服务器不能用时置空重选
        server = null;
    }

	//超过10轮就罢工(感觉服务器如果很多的话,这里很可能是个坑)
    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    return server;
}

private int incrementAndGetModulo(int modulo) {
    for (;;) {//注意这里有个循环,会一直+1取余然后比较,值得这个线程不受干扰的拿到一个索引
        int current = nextServerCyclicCounter.get();
        int next = (current + 1) % modulo;//核心是这个,获取取值范围0到modulo-1的索引值
        //保证多线程同步,有其他线程插队会失败
        if (nextServerCyclicCounter.compareAndSet(current, next))
            return next;
    }
}

传送门:
微服务架构 | 组件目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值