本文所构建的代码已上传至 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是如何实现的:
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());
}
复制代码