负载均衡流程
上一节我们主要学习了什么是Eureka,服务注册与发现,以及负载均衡的简单实现。那你知道在Eureka中负载均衡是怎么实现的吗?我们能直接请求到http://userservice/user/1这个地址吗?
答案是不能的,这并不是一个直接可请求的地址。那为什么我们使用Eureka能成功请求呢,这就涉及到本章要学习的重点:Ribbon负载均衡。
目前主流的负载均衡有两种:
- 集中式负载均衡:代理方式进行负载,如nginx
- 客户端负载均衡:Ribbon就属于客户端自己做负载均衡,它和Eureka都属于Nefflix提供的服务于SpringCloud微服务的组件。
负载均衡大致流程:
- order-service发起调用请求http://userservice/user/1到Ribbon
- Ribbon询问Eureka注册中心是否有userservice这个服务,有的话从注册中心拉取
- Ribbon获取到服务列表localhost:8081和localhost:8082
- 最后通过负载均衡策略选择一个服务,比如这里选择了localhost:8081,那么http://userservice/user/1的请求实际是转发给了http://localhost:8081/user/1,所以成功获取到了数据。
Ribbon源码分析
前面我们知道,需要给RestTemplate加上@LoadBalanced注解,那么它有什么作用呢。它的作用是标记RestTemplate发起的请求需要被Ribbon拦截和处理。拦截动作是谁完成的呢,LoadBalancerInterceptor。
LoadBalancerInterceptor实现了ClientHttpRequestInterceptor接口,看这个接口的名字就知道,它是一个客户端http请求的拦截器,我们通过的RestTemplate发起的正是http请求,所以会被它拦截,而LoadBalancerInterceptor继承了这个接口并实现了自己的intercept拦截方法。
接下来,我们发起一个请求http://localhost:8080/order/101,打个断点看一下
- 进入LoadBalancerInterceptor的intercept拦截方法
- 下一步,进入RibbonLoadBalancerClient的execute方法,DynamicServerListLoadBalancer是一个动态服务列表负载均衡器,它会通过serviceId向Eureka注册中心拉取userservice,展开可以看到,这里已经获取到了userservice的服务列表
- 接下来下一步,进入getServer方法,它会判断loadBanlancer是否为空,不为空的话调用ZoneAwareLoadBalancer的chooseServer方法,ZoneAwareLoadBalancer继承了DynamicServerListLoadBalancer
- 进入ZoneAwareLoadBalancer的chooseServer方法,往下走,可以看到,它调用了父类的chooseServer方法
- 继续往下,进入BaseLoadBalancer的chooseServer方法
- 可以看到,这里有一个rule,跳转到rule变量,它是IRule类型,看名字可以明白它是一个规则接口,我们通过ctrl+h查看下它的实现类。主要有轮询、随机、重试等规则,默认是ZoneAvoidanceRule。
rule.choose(key)方法可以选择负载均衡的规则,上图可以看到key是default,因为我们没显示得指定规则,所以选择的是默认的ZoneAvoidanceRule规则。 - 继续往下,可以看到这里成功拿到一个userservice服务
- 接下来就是成功返回了,可以看到浏览器成功获取到了数据,并且请求是转发给了localhost:8081服务
- 这样,我们应该大概了解Ribbon负载均衡的请求流程了。大家可以多试几次,看看请求分发的效果。
不想看源码的可以看下下面的负载均衡流程图:

负载均衡策略
上面我们也看到了,负载均衡的规则主要是有IRule的实现类决定的。我们看下IRule的框架和各规则类的描述
我们看下ZoneAvoidanceRule(默认规则),这里的Zone可以理解为一个机房或区域,我们可以设置Zone值让服务消费者获取最佳区域的服务列表,再做轮询请求访问。但是,对绝大部分中小型公司来说,是没有这么多的资源的,基本就是默认设置,等同于RoundRobinRule轮询策略。
那么我们怎么改变服务中的负载均衡规则呢,有两种方式:
- 代码方式:自定义一个IRule(当然,也可以重新定义一个配置类返回bean,这里写在启动类里为了方便)
@MapperScan("cn.lsy.order.mapper") @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } @Bean public IRule rule(){ //随机策略 return new RandomRule(); } }
-
文件方式:order-service的application.yml里添加负载均衡规则配置:
userservice: ribbion: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandonRule #负载均衡规则
(两者有什么区别呢?代码方式配置灵活,全局规则,但修改时需要重新打包发布。配置方式更直观、方便,无需重新打包发布,但是需要指定服务名,如userservice,无法全局配置。)
大家自己去测试一下吧,看看是不是随机请求的效果~
自定义负载均衡规则的话可以自定义一个规则类,实现IRule接口或者继承AbstractLoadBalancerRule类,这里就先不展开了。
饥饿加载
Ribbon默认是懒加载,第一次请求访问比较慢,因为它会判断请求的服务列表是否为空,然后进行去Eureka注册中心拉取的初始化动作。
需要加快首次请求速度的话可以在配置文件开启饥饿加载,order-service的application.yml中配置
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: #指定服务名称
- userservice
重启order-service服务,可以看到,order-service启动时就已经完成了userservice服务列表的拉取。所以,后续请求速度就会比较快了。