负载均衡
在刚才的案例中,我们启动了一个classes-service,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。
但是实际环境中,我们往往会开启很多个eureka-service的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?
一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。
什么是Ribbon:
接下来,我们就来使用Ribbon实现负载均衡。
启动两个服务实例
首先我们启动两个classes-service实例,一个9010,一个9011。
步骤一:为两个端口号配置yml文件
application-9010.yml
application-9011.yml
步骤二:配置两个启动项
测试
开启负债均衡
因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。直接修改代码:
在RestTemplate的方法上添加@LoadBalanced注解即可
修改 ClassesController ,添加标识内容
package com.czxy.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; /** * @author 桐叔 * @email liangtong@itcast.cn */ @RestController @RequestMapping("/classes") public class ClassesController { @Resource private HttpServletRequest request; @GetMapping public List<String> findAll(){ List<String> list = new ArrayList<>(); list.add("Java12班"); list.add("Java34班"); list.add("服务端端口:" + request.getServerPort()); return list; } }
访问“学生服务”进行测试:
扩展:源码追踪
为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。
显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor
我们进行源码跟踪:
继续跟入execute方法:发现获取了8082端口的服务
继续跟入execute方法:发现获取了8082端口的服务
优化:负载均衡策略
为什么我们只输入了service名称就可以访问了呢? 显然有人帮我们根据service名称,获取到了服务实例的ip和端口。
通过对底层原理的分析,Ribbon采用是负载均衡策略进行的处理。
负载均衡策略初体验
第一步】添加整合Junit的坐标
<!--测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
【第二步】编写测试类
package com.czxy; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; /** * @author 桐叔 * @email liangtong@itcast.cn */ @RunWith(SpringRunner.class) @SpringBootTest(classes = StudentApplication.class) public class TestRibbon { @Resource private RibbonLoadBalancerClient client; @Test public void test(){ for (int i = 0; i < 100; i++) { ServiceInstance instance = this.client.choose("classes-service"); System.out.println(instance.getHost() + ":" + instance.getPort()); } } }
结果:
负载均衡的配置
SpringBoot提供了修改负载均衡规则的配置入口:
{服务名称}.ribbon.NFLoadBalancerRuleClassName=具体策略
例如:
# 指定服务设置负载均衡策略 classes-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 指定服务设置负载均衡策略
classes-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
再次测试,发现结果变成了随机:
常见策略的配置(注意:下面提供了3个策略,同时只能使用一个。
优化:重试机制
CAP原则
注册中心通常需要遵循CAP原则,CAP指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),往往三者不可兼得。
Eureka的服务治理主要强调是AP:即可用性和可靠性。
Eureka为了实现更高的服务可用性,牺牲了一定的一致性,极端情况下它宁愿接收故障实例也不愿丢掉健康实例,正如我们上面所说的自我保护机制。
但是,此时如果我们调用了这些不正常的服务,调用就会失败,从而导致其它服务不能正常工作!这显然不是我们愿意看到的。
效果演示
【第一步】我们现在关闭一个classes-service 9011实例:
【第二步】因为服务剔除的延迟,student-service并不会立即得到最新的服务列表,此时再次访问你会得到错误提示
【第三步】整合Spring Retry
此时,9010服务其实是正常的。
重试机制:Retry
因此Spring Cloud 整合了Spring Retry 来增强RestTemplate的重试能力,当一次服务调用失败后,不会立即抛出一次,而是再次重试另一个服务。
引入spring-retry依赖
<!--重试--> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
只需要简单配置即可实现Ribbon的重试:
server: port: 9020 spring: application: name: student-service cloud: loadbalancer: retry: enabled: true eureka: client: service-url: #注册中心位置,多个地址以','隔开 defaultZone: http://localhost:10086/eureka/,http://localhost:10087/eureka registry-fetch-interval-seconds: 5 #从注册中心,获得列表的间隔时间 instance: #web页面显示效果和访问路径 instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port} prefer-ip-address: true lease-renewal-interval-in-seconds: 5 #服务续约(renew)的间隔,默认值90秒 lease-expiration-duration-in-seconds: 10 #服务失效时间,默认为30秒 # 指定服务设置负载均衡策略 #classes-service: # ribbon: # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # NFLoadBalancerRuleClassName : com.netflix.loadbalancer.BestAvailableRule #并发最少 # NFLoadBalancerRuleClassName : com.netflix.loadbalancer.WeightedResponseTimeRule #请求时间权重 classes-service: ribbon: ConnectTimeout: 250 # Ribbon的连接超时时间 ReadTimeout: 1000 # Ribbon的数据读取超时时间 OkToRetryOnAllOperations: true # 是否对所有操作都进行重试 MaxAutoRetriesNextServer: 1 # 切换实例的重试次数 MaxAutoRetries: 1 # 对当前实例的重试次数