本文所构建的代码已上传至 Github (注意切换分支) ,所有代码均亲测有效,祝食用愉快。
之前的项目 springcloud-demo 中,我们使用了单机的服务提供者(user-service),而在实际的生产环境中,服务提供方肯定会以多台部署(集群)的方式提供以保障服务高可用,这种情况下,很容易想到需要写一个负载均衡算法来调用。而Spring Cloud Eureka已提供了负载均衡组件Robbin,只需要少量代码、配置即可快速投入使用
@Configuration public class SysConfiguration { // 添加@LoadBalanced注解 @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } } 复制代码
@RestController @RequestMapping("consumer/sysUser/") public class SysUserController { @Resource private RestTemplate restTemplate; @RequestMapping("selectById") public SysUser selectById(Integer id) { String url = "http://user-service/sysUser/selectById?id=" + id; return restTemplate.getForObject(url, SysUser.class); } } 复制代码
@RequestMapping("sysUser") @RestController public class SysUserController { @Resource private SysUserService sysUserService; @Resource private HttpServletRequest request; @RequestMapping("selectById") public SysUser selectById(Integer id ){ System.err.println(request.getRequestURL()); return sysUserService.selectById(id); } } 复制代码
http://127.0.0.1:8081/sysUser/selectById http://127.0.0.1:8082/sysUser/selectById http://127.0.0.1:8081/sysUser/selectById http://127.0.0.1:8082/sysUser/selectById 复制代码
2个重点自动配置类:
RibbonLoadBalancerClient
注入Spring容器 LoadBalancerRequestFactory
对象、 LoadBalancerInterceptor
对象注入Spring容器,获取所有被 @LoadBalance
标记的 RestTemplate
对象,并挨个添加 LoadBalancerInterceptor
对象。 LoadBalancerClient
的 RestTemplate
对象 @Configuration @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class) @RibbonClients @AutoConfigureAfter( name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class }) @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class }) public class RibbonAutoConfiguration { ... @Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); } ... } 复制代码
HttpRequest
转化为 LoadBalancerRequest
并提供给 LoadBalancerRequests
或 LoadBalancerInterceptor
。(如果有提供自定义 LoadBalancerRequestTransformers
实现,也可在转化中执行) LoadBalancerRequestFactory
实例在 LoadBalancerAutoConfiguration
中注入: @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { // 获取到所有被@LoadBalanced标记的RestTemplate对象 @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); ... @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } ... } 复制代码
ClientHttpRequestInterceptor
的javadoc:Intercepts client-side HTTP requests. Implementations of this interface can be registered with the RestTemplate, as to modify the outgoing ClientHttpRequest and/or the incoming ClientHttpResponse. HttpRequest
转化成 ClientHttpRequest
,并最终返回 ClientHttpResponse
loadBalancerInterceptor
在 LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig#restTemplateCustomizer
添加到 restTemplate
的拦截器列表。而创建 loadBalancerInterceptor
所需的 loadBalancerClient
、 requestFactory
就是上面Ribbon默认注入的。 @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { ... @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } ... } ... } 复制代码
上一章节中,服务调用者在代码中请求的地址是" http://user-service/sysUser/selectById ",盲猜是Ribbon将地址中的“user-service”替换成了实际的请求地址,实际上是 LoadBalancerInterceptor
帮助我们做了这件事。
通过前一小结的分析 现在我们来简单地追踪一下源码,看一下Ribbon是如何实现的:
其中的loadBalancer、requestFactory就是上面提到的RibbonLoadBalancerClient
与
LoadBalancerRequestFactory
对象。
追踪源码可以发现,是 BaseLoadBalancer
中的 rule
对象,默认注入的是轮询类型的Rule。
public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnections.PrimeConnectionListener, IClientConfigAware { private final static IRule DEFAULT_RULE = new RoundRobinRule(); ... protected IRule rule = DEFAULT_RULE; ... 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; } } } ... } 复制代码
也可以写个单元测试来验证:
@SpringBootTest(classes = ConsumerDemoApplication.class) public class LoadBalanceTest { @Autowired RibbonLoadBalancerClient client; @Test public void test(){ for (int i = 0; i < 10; i++) { ServiceInstance instance = this.client.choose("user-service"); System.out.println(instance.getHost() + ":" + instance.getPort()); } } } 复制代码
可以看到,缺失是轮询请求的:
127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8081 127.0.0.1:8082 复制代码
AbstractLoadBalancerRule.java AvailabilityFilteringRule.java BestAvailableRule.java ClientConfigEnabledRoundRobinRule.java PredicateBasedRule.java RandomRule.java ResponseTimeWeightedRule.java RetryRule.java RoundRobinRule.java WeightedResponseTimeRule.java ZoneAvoidanceRule.java 复制代码
{服务名称}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的实现类。 user-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule 复制代码
127.0.0.1:8082 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8081 127.0.0.1:8081 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8081 127.0.0.1:8081 127.0.0.1:8081 复制代码
Eureka的服务治理强调了CAP原则中的AP,即可用性和可靠性。它与Zookeeper这一类强调CP(一致性,可靠性)的服务治理框架最大的区别在于:Eureka为了实现更高的服务可用性,牺牲了一定的一致性,极端情况下它宁愿接收故障实例也不愿丢掉健康实例,正如我们上面所说的自我保护机制。 但是,此时如果我们调用了这些不正常的服务,调用就会失败,从而导致其它服务不能正常工作!这显然不是我们愿意看到的。如果现在 user-service
注册了8081、8082台实例, consumer-demo
启动Ribbon负载均衡,此时我们停掉 user-service
的8082示例。 eureka-server
中针对 user-service
中还记录着8082实例,此时 consumer-demo
轮询到8082将报如下错误信息,但 user-service
的8081实例是可以提供服务的:
在这种情况下,我们希望Ribbon能自动重试其他可用实例,如果其余实例都宕机,再返回连接不可用的信息
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> 复制代码
# 开启Spring Cloud的重试功能 spring.cloud.loadbalancer.retry.enabled=true # Ribbon的连接超时时间 user-service.ribbon.ConnectTimeout=250 # Ribbon的数据读取超时时间 user-service.ribbon.ReadTimeout=1000 # 是否对所有操作都进行重试 user-service.ribbon.OkToRetryOnAllOperations=true # 切换实例的重试次数 user-service.ribbon.MaxAutoRetriesNextServer=1 # 对当前实例的重试次数 user-service.ribbon.MaxAutoRetries=1 复制代码
可以看到,Ribbon能够实现自动重试而不会返回8082实例的错误信息。
在使用 @Autowire
或 @Resource
获取Spring容器中对象时,同时写上自定义注解,将或获取到被自定义注解标记的对象,下面用代码来简单演示一下:
Anno1.java
、 Anno2.java
注意需要在注解上添加@Qualifier注解 @Documented @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.FIELD}) public @interface Anno1 { } 复制代码
@Documented @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.FIELD}) public @interface Anno2 { } 复制代码
MyService.java
public class MyService { } 复制代码
MyService
实例: MyConfig.java
@Configuration public class MyConfig { @Bean @Anno1 public MyService instance11(){ return new MyService(); } @Bean @Anno1 public MyService instance12(){ return new MyService(); } @Bean @Anno2 public MyService instance21(){ return new MyService(); } @Bean @Anno2 public MyService instance22(){ return new MyService(); } @Bean @Anno2 public MyService instance23(){ return new MyService(); } @Bean public MyService instance01(){ return new MyService(); } @Bean public MyService instance02(){ return new MyService(); } } 复制代码
MyService
实例 @Autowired private List<MyService> instanceList00; @Anno1 @Autowired private List<MyService> instanceList01; @Autowired @Anno2 private List<MyService> instanceList02; @Test public void AutowireTest(){ System.err.println(instanceList00.size()); System.err.println(instanceList01.size()); System.err.println(instanceList02.size()); } 复制代码