1、引言
随着用户量的增加,应用的访问量也会随之增加,单台服务器已经远不能满足高并发的业务需求,这时需要多台服务器组成集群应对高并发带来的压力,但服务的消费者选择服务提供者的多个实例中的哪一个又成为了一个问题。
负载均衡
策略的两种实现方式:
- 服务器端负载均衡机制(如Nginx)。
- 客户端负载均衡机制(如Ribbon,运行时以 SDK 形式内嵌到每一个微服务实例中,为微服务间通信提供负载均衡与高可用支持)。
2、Ribbon的使用
2.1、@Loadbalanced实现负载均衡
由于Alibaba Nacos内置集成了Ribbon,在 RestTemplate 上添加@Loadbalanced注解既可以实现服务治理(发现服务),又可实现负载均衡,默认是
轮询
的方式//proxyBeanMethods = false:轻量级模式,用来优化配置性能 @Configuration(proxyBeanMethods = false) public class RestTemplateConfig { //发现服务,并赋予策略(Ribbon默认轮询) @LoadBalanced //使用时注入该bean对象即可使用其方法访问远程服务 @Bean public RestTemplate restTemplate(RestTemplateBuilder builder){ //切换底层通信组件为okhttp3 注意restTemplateBuilder为不可变对象,每次修改会返回新的builder,需要重新赋值 builder = builder.requestFactory(()->{ OkHttp3ClientHttpRequestFactory httpRequestFactory = new OkHttp3ClientHttpRequestFactory(); httpRequestFactory.setConnectTimeout(3000);//设置建立连接的超时时间 httpRequestFactory.setReadTimeout(5000);//设置读取数据的超时时间 httpRequestFactory.setWriteTimeout(5000);//设置写出数据的超时时间 return httpRequestFactory; }); //配置拦截器 //builder=builder.additionalInterceptors(new MyClientHttpRequestInterceptor()); return builder.build(); } }
2.2、定制化负载均衡策略
Ribbon 作为一款优秀的客户端负载均衡框架,内置了很多经典实用的算法,可以直接拿来用
如图所示:IRule 是算法的接口。AbstractLoadBalancerRule 是实现了 IRule 接口的抽象类,所有内置的算法都是继承 AbstractLoadBalancerRule 来实现的。
- RoundRobinRule:轮询策略,以轮询的方式选择服务实例
- RandomRule:随机策略,随机从服务实例列表中选择一个进行访问。若随机到的 server 为 null 或者不可用的话,会不停地循环选取。
- RetryRule:重试策略,对其它策略进行装饰,提供500ms(可以设置)内如果获取的实例为null或不可用,则不断重试。
- BestAvailableRule:最大可用策略(最小连接),选择一个可用且当前连接数最小的服务实例。
- AvailabilityFilteringRule:可用过滤策略,先过滤掉有故障或者并发请求大于阈值的服务实例,然后以轮询的方式从过滤后的实例列表中选一个实例。
- WeightedResponseTimeRule:带有加权的轮询策略,根据各个服务实例响应时间进行加权处理,然后在采用轮询的方式获取响应的服务实例。
- NacosRule:Nacos专属策略,优先选择同集群的服务实例,且支持在nacos平台中自定义权重控制访问。
负载均衡策略配置:#配置格式如下: #<provider-service-id>: #要调用的服务的id # ribbon: # NFLoadBalancerRuleClassName: 全类名 user-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
2.3、Rabbion和Nacos的整合
微服务互相访问时,应该尽可能访问 同一集群 下的服务实例,因为处于同一集群下的本地访问速度更快。当本集群内不可用时,才会访问其它集群。
使用NacosRule负载均衡策略
- 配置服务所属的集群
spring: application: name: 服务名 cloud: nacos: discovery: server-addr: 管理服务的nacosIp:8848 username: nacos password: nacos namespace: ...... group: ...... #配置集群 cluster-name: 集群名
- 配置负载均衡器为NacosRule
服务名: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
Nacos提供的服务实例的权重配置
由于同一集群的服务器设备性能有差异,希望性能好的机器承担更多的用户请求。Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
- Nacos控制台可设置实例的权重值。权重为浮点数,范围为 0-10000。
- 同集群内的多个实例,权重越高被访问的频率越高。
- 权重设置为0则完全不会被访问。
注意
:Nacos权重复杂均衡依赖于NacosRule,负载均衡策略要配置为NacosRule!!!
2.4、自定义负载均衡算法(用的较少)
- 编码
public class MyRandomRule extends AbstractLoadBalancerRule { //clientConfig:ribbon的配置对象,保存着配置信息 @Override public void initWithNiwsConfig(IClientConfig clientConfig) { //获取ribbon的配置信息 clientConfig.getProperties().forEach((k, v) -> { System.out.println(k + " = " + v); }); } //负载均衡策略的核心实现方法 @Override public Server choose(Object key) { //ILoadBalancer负载均衡器,可以获取所有的服务实例信息 ILoadBalancer loadBalancer = getLoadBalancer(); if (loadBalancer == null) return null; //获取所有的服务列表,包括可达的和不可达的 List<Server> allServers = loadBalancer.getAllServers(); if (allServers.isEmpty()) return null; //获取所有可达的服务列表 List<Server> reachableServers = loadBalancer.getReachableServers(); if(reachableServers.isEmpty()) return null; //获取随机下标,ThreadLocalRandom比Random在多线程环境下性能更好 int index = ThreadLocalRandom.current().nextInt(reachableServers.size()); Server server = reachableServers.get(index); //如果server不可用则返回null if (server == null || !server.isAlive()) return null; return server; } }
- 配置
服务名: ribbon: NFLoadBalancerRuleClassName:包名.MyRandomRule
2.5、Ribbon的饥饿加载模式
Ribbon默认是
懒加载模式
:即Ribbon在进行客户端负载均衡时是在第一次请求时才去创建,第一次调用会比较慢,有可能会引起调用超时。
开启饥饿加载模式
:通过指定 Ribbon 客户端的服务名称,在启动时加载这些子应用程序上下文的方式,就可以解决这个问题。ribbon: eager-load: #开启饥饿加载模式 enabled: true #配置要提前加载哪些服务的clent clients: 服务名1,服务名2
2.6 、超时重试
微服务在实际生产环境中,可能出现一种情况:某些服务的其中部分实例临时不可用,如网络波动或者服务实例宕机,Ribbon为我们提供了超时重试机制能够通过重试的方式自动化的解决。
- 引入spring-retry依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
- 在restTemplate的配置类中设置超时时间
//proxyBeanMethods = false:轻量级模式,用来优化配置性能 @Configuration(proxyBeanMethods = false) public class RestTemplateConfig { //发现服务,并赋予策略(Ribbon默认轮询) @LoadBalanced //使用时注入该bean对象即可使用其方法访问远程服务 @Bean public RestTemplate restTemplate(RestTemplateBuilder builder){ //切换底层通信组件为okhttp3 builder = builder.requestFactory(()->{ OkHttp3ClientHttpRequestFactory httpRequestFactory = new OkHttp3ClientHttpRequestFactory(); httpRequestFactory.setConnectTimeout(3000);//设置建立连接的超时时间 httpRequestFactory.setReadTimeout(5000);//设置读取数据的超时时间 httpRequestFactory.setWriteTimeout(5000);//设置写出数据的超时时间 return httpRequestFactory; }); //配置拦截器 //builder=builder.additionalInterceptors(new MyClientHttpRequestInterceptor()); return builder.build(); } }
- 在application.yml中配置重试机制
- 全局的重试配置
ribbon: MaxAutoRetries: 1 #单服务器重试次数(不包含第一次请求),这里失败则更换服务器 MaxAutoRetriesNextServer: 2 #更换服务器的次数(不包含第一次) OkToRetryOnAllOperations: false #默认只对GET请求重试, 当设置为true时, 对POST等所有类型请求都重试
- 特定服务的重试配置
服务名: ribbon: MaxAutoRetries: 2 MaxAutoRetriesNextServer: 1 OkToRetryOnAllOperations: false