分布式专题-5、负载均衡-Ribbon
我们添加了@LoadBalanced 注解,即可实现负载均衡功能,这是什么原理呢?
1. 负载均衡原理
SpringCloud 底层其实是利用了一个名为 Ribbon 的组件,来实现负载均衡功能的。
那么我们发出的请求明明是 http://userservice/user/1,怎么变成了 http://localhost:8081 的呢?
2. 源码跟踪
为什么我们只输入了 service 名称就可以访问了呢?之前还要获取 ip 和端口。
显然有人帮我们根据 service 名称,获取到了服务实例的 ip 和端口。它就是 LoadBalancerInterceptor
,这个类会对 RestTemplate 的请求进行拦截,然后从 Eureka 中根据服务 id 获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务 id。
我们进行源码跟踪:
2.1. LoadBalancerIntercepor
可以看到这里的 intercept 方法,拦截了用户的 HttpRequest 请求,然后做了几件事:
request.getURI()
:获取请求 uri,本例中就是 http://user-service/user/8originalUri.getHost()
:获取 uri 路径的主机名,其实就是服务 id,user-service
this.loadBalancer.execute()
:处理服务 id,和用户请求。
这里的 this.loadBalancer
是 LoadBalancerClient
类型,我们继续跟入。
2.2. LoadBalancerClient
继续跟入 execute 方法:
代码是这样的:
- getLoadBalancer(serviceId):根据服务 id 获取 ILoadBalancer,而 ILoadBalancer 会拿着服务 id 去 eureka 中获取服务列表并保存起来。
- getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个 在本例中,可以看到获取了 8082 端口的服务
放行后,再次访问并跟踪,发现获取的是 8081:
果然实现了负载均衡。
2.3. 负载均衡策略 IRule
在刚才的代码中,可以看到获取服务使通过一个 getServer
方法来做负载均衡:
我们继续跟入:
继续跟踪源码 chooseServer 方法,发现这么一段代码:
我们看看这个 rule 是谁:
这里的 rule 默认值是一个 RoundRobinRule
,看类的介绍:
这不就是轮询的意思嘛。
到这里,整个负载均衡的流程我们就清楚了。
2.4. 总结
❕ ^pxs57b
SpringCloudRibbon 的底层采用了一个拦截器,拦截了 RestTemplate 发出的请求,对地址做了修改。用一幅图来总结一下:
基本流程如下:
LoadBalancerIntercepor
拦截我们的 RestTemplate 请求 http://userservice/user/1RibbonLoadBalancerClient
会从请求 url 中获取服务名称,也就是 user-serviceDynamicServerListLoadBalancer
根据 user-service 到 eureka 拉取服务列表Eureka
返回列表,localhost:8081、localhost:8082IRule
利用内置负载均衡规则,从列表中选择一个,例如 localhost:8081RibbonLoadBalancerClient
修改请求地址,用 localhost:8081 替代 userservice,得到 http://localhost:8081/user/1,发起真实请求
3. 负载均衡策略
3.1. 负载均衡策略
负载均衡的规则都定义在 IRule 接口中,而 IRule 有很多不同的实现类:
不同规则的含义如下:
默认的实现就是 ZoneAvoidanceRule,是一种轮询方案
3.2. 详细介绍
IRule
这是所有负载均衡策略的父接口,里边的核心方法就是 choose 方法,用来选择一个服务实例。
AbstractLoadBalancerRule
AbstractLoadBalancerRule 是一个抽象类,里边主要定义了一个 ILoadBalancer,这里定义它的目的主要是辅助负责均衡策略选取合适的服务端实例。
3.2.1. RandomRule
看名字就知道,这种负载均衡策略就是随机选择一个服务实例,看源码我们知道,在 RandomRule 的无参构造方法中初始化了一个 Random 对象,然后在它重写的 choose 方法又调用了 choose(ILoadBalancer lb, Object key) 这个重载的 choose 方法,在这个重载的 choose 方法中,==每次利用 random 对象生成一个不大于服务实例总数的随机数==,并将该数作为下标所以获取一个服务实例。
3.2.2. RoundRobinRule
RoundRobinRule 这种负载均衡策略叫做线性轮询负载均衡策略。这个类的 choose(ILoadBalancer lb, Object key) 函数整体逻辑是这样的:开启一个计数器 count,在 while 循环中遍历服务清单,获取清单之前先通过 incrementAndGetModulo 方法获取一个下标,这个下标是一个不断自增长的数先加 1 然后和服务清单总数取模之后获取到的(所以这个下标从来不会越界),拿着下标再去服务清单列表中取服务,每次循环计数器都会加 1,如果连续 10 次都没有取到服务,则会报一个警告 No available alive servers after 10 tries from load balancer: XXXX
3.2.3. RetryRule(在轮询的基础上进行重试)
看名字就知道这种负载均衡策略带有重试功能。首先 RetryRule 中又定义了一个 subRule,它的实现类是 RoundRobinRule,然后在 RetryRule 的 choose(ILoadBalancer lb, Object key) 方法中,每次还是采用 RoundRobinRule 中的 choose 规则来选择一个服务实例,如果选到的实例正常就返回,如果选择的服务实例为 null 或者已经失效,则在失效时间 deadline 之前不断的进行重试(重试时获取服务的策略还是 RoundRobinRule 中定义的策略),如果超过了 deadline 还是没取到则会返回一个 null。
3.2.4. WeightedResponseTimeRule
(权重 —nacos 的 NacosRule ,Nacos 还扩展了一个自己的基于配置的权重扩展)
WeightedResponseTimeRule 是 RoundRobinRule 的一个子类,在 WeightedResponseTimeRule 中对 RoundRobinRule 的功能进行了扩展,
WeightedResponseTimeRule 中会根据每一个实例的运行情况来给计算出该实例的一个权重,然后在挑选实例的时候则根据权重进行挑选,这样能
够实现更优的实例调用。WeightedResponseTimeRule 中有一个名叫 DynamicServerWeightTask 的定时任务,默认情况下每隔 30 秒会计算一次
各个服务实例的权重,权重的计算规则也很简单,如果一个服务的平均响应时间越短则权重越大,那么该服务实例被选中执行任务的概率也就越大。
3.2.5. ClientConfigEnabledRoundRobinRule
ClientConfigEnabledRoundRobinRule 选择策略的实现很简单,内部定义了 RoundRobinRule,choose 方法还是采用了 RoundRobinRule 的
choose 方法,所以它的选择策略和 RoundRobinRule 的选择策略一致,不赘述。
3.2.6. BestAvailableRule
BestAvailableRule 继承自 ClientConfigEnabledRoundRobinRule,它在 ClientConfigEnabledRoundRobinRule 的基础上主要增加了根据
loadBalancerStats 中保存的服务实例的状态信息来过滤掉失效的服务实例的功能,然后顺便找出并发请求最小的服务实例来使用。然而
loadBalancerStats 有可能为 null,如果 loadBalancerStats 为 null,则 BestAvailableRule 将采用它的父类即
ClientConfigEnabledRoundRobinRule 的服务选取策略(线性轮询)。
3.2.7. ZoneAvoidanceRule
默认规则,复合判断 server 所在区域的性能和 server 的可用性选择服务器
ZoneAvoidanceRule 是 PredicateBasedRule 的一个实现类,只不过这里多一个过滤条件,ZoneAvoidanceRule 中的过滤条件是以
ZoneAvoidancePredicate 为主过滤条件和以
AvailabilityPredicate 为次过滤条件组成的一个叫做 CompositePredicate 的组合过滤条件,过滤成功之后,继续采用线性轮询
(RoundRobinRule) 的方式从过滤结果中选择一个出来。
AvailabilityFilteringRule(先过滤掉故障实例,再选择并发较小的实例)
过滤掉一直连接失败的被标记为 circuit tripped 的后端 Server,并过滤掉那些高并发的后端 Server 或者使用一个 AvailabilityPredicate 来
包含过滤 server 的逻辑,其实就是检查 status 里记录的各个 Server 的运行状态
3.3. 自定义负载均衡策略
通过定义 IRule 实现可以修改负载均衡规则,有两种方式:
代码方式:在 order-service 中的 OrderApplication 类中,定义一个新的 IRule:
这是一种全局配置,不推荐1
2
3
4@Bean
public IRule randomRule(){
return new RandomRule();
}配置文件方式:在 order-service 的 application.yml 文件中,添加新的配置也可以修改规则:
按服务配置,更灵活1
2
3userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
注意,一般用默认的负载均衡规则,不做修改。
4. 饥饿加载
❕ ^x4qqqw
Ribbon 默认是采用懒加载,即第一次访问时才会去创建 LoadBalanceClient
,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
1 |
|
5. 重试机制
#todo6. RestTemplate
6.1. 是什么
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法, 能够大大提高客户端的编写效率。
调用 RestTemplate 的默认构造函数,RestTemplate 对象在底层通过使用 java.net 包下的实现创建 HTTP 请求,可以通过使用 ClientHttpRequestFactory 指定不同的 HTTP 请求方式。
ClientHttpRequestFactory 接口主要提供了两种实现方式
- 一种是 SimpleClientHttpRequestFactory,使用 J2SE 提供的方式(既 java.net 包提供的方式)创建底层的 Http 请求连接。
- 一种方式是使用 HttpComponentsClientHttpRequestFactory 方式,底层使用 HttpClient 访问远程的 Http 服务,使用 HttpClient 可以配置连接池和证书等信息。
RestTemplate 的核心之一 Http Client。
目前通过 RestTemplate 的源码可知,RestTemplate 可支持多种 Http Client 的 http 的访问,如下所示:
- 基于 JDK HttpURLConnection 的 SimpleClientHttpRequestFactory,默认。
- 基于 Apache HttpComponents Client 的 HttpComponentsClientHttpRequestFactory
- 基于 OkHttp3 的 OkHttpClientHttpRequestFactory。
- 基于 Netty4 的 Netty4ClientHttpRequestFactory。
其中 HttpURLConnection 和 HttpClient 为原生的网络访问类,OkHttp3 采用了 OkHttp3 的框架,Netty4 采用了 Netty 框架。
6.2. 配置
6.2.1. RestTempate 的访问的超时设置
例如,我用的是 Httpclient 的连接池,RestTemplate 的超时设置依赖 HttpClient 的内部的三个超时时间设置。
HttpClient 内部有三个超时时间设置:连接池获取可用连接超时,连接超时,读取数据超时:
1.setConnectionRequestTimeout 从连接池中获取可用连接超时:设置从 connect Manager 获取 Connection 超时时间,单位毫秒。
HttpClient 中的要用连接时尝试从连接池中获取,若是在等待了一定的时间后还没有获取到可用连接(比如连接池中没有空闲连接了)则会抛出获取连接超时异常。
2.连接目标超时 connectionTimeout,单位毫秒。
指的是连接目标 url 的连接超时时间,即客服端发送请求到与目标 url 建立起连接的最大时间。如果在该时间范围内还没有建立起连接,则就抛出 connectionTimeOut 异常。
如测试的时候,将 url 改为一个不存在的 url:“http://test.com” ,超时时间 3000ms 过后,系统报出异常: org.apache.commons.httpclient.ConnectTimeoutException:The host did not accept the connection within timeout of 3000 ms
3.等待响应超时(读取数据超时)socketTimeout ,单位毫秒。
连接上一个 url 后,获取 response 的返回等待时间 ,即在与目标 url 建立连接后,等待放回 response 的最大时间,在规定时间内没有返回响应的话就抛出 SocketTimeout。
测试时,将 socketTimeout 设置很短,会报等待响应超时。
我遇到的问题,restTemplate 请求到一个高可用的服务时,返回的超时时间是设置值的 2 倍,是因为负载均衡器返回的重定向,导致 httpClient 底层认为没有超时,又请求一次,如果负载均衡器下有两个节点,就耗费 connectionTimeout 的双倍时间。
6.3. 连接池
https://zhuanlan.zhihu.com/p/384627133
7. 实战经验
8. 参考与感谢
8.1. 黑马程序员
微服务开发框架 SpringCloud+RabbitMQ+Docker+Redis+ 搜索 + 分布式微服务全技术栈课程 ^4ps1v8
8.1.1. 视频
8.1.2. 资料
[[SpringCloud01]]