Nacos 服务列表管理
Nacos 提供了开放 API 可通过 /nacos/v1/ns/instance/list
获取服务列表。如果我们采用 spring-cloud 方式去获取服务,最终会通过 Nacos Client
+ loadbalancer
的方式进行客户端负载均衡。
环境介绍:
Jdk 1.8
nacos-server-1.4.2
spring-boot-2.3.5.RELEASE
spring-cloud-Hoxton.SR8
spring-cloiud-alibab-2.2.5.RELEASE
Ribbon 源码解析
Ribbon 简介
Spring Cloud Ribbon 是 Netflix Ribbon 实现的一套客户端负载均衡工具 简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的复杂的算法和服务调用。 Ribbon 客户端组件提供一系列完善的配置项如超时、重试等。简单的说,就是配置文件中列出 load Balancer (简称 LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机链接等)去链接这些机器。我们很容易使用 Ribbon 自定义的负载均衡算法。
Ribbon 使用
- 首先需要定义 RestTemplate 使用 Ribbon 策略;
@Configuration public class RestTemplateConfig { @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
- 本地使用 RestTemplate 调用远程接口;
@Autowired private RestTemplate restTemplate; @RequestMapping(value = "/echo/{id}", method = RequestMethod.GET) public String echo(@PathVariable Long id) { return restTemplate.getForObject("http://member-service/member/get/" + id, String.class); }
Ribbon 源码分析
- RestTemplate 继承 InterceptingHttpAccessor 通过
interceptors
字段接受 HttpRequestInterceptor 请求拦截器。
- 对于 Ribbion 初始化类是
RibbonAutoConfiguration
中的, 它在spring-cloud-netflix-ribbon
中定义。
- 但是它在初始化之前,又需要加载
RibbonAutoConfiguration
配置,它是在spring-cloud-common
中。具体的代码如下:
@Configuration(proxyBeanMethods = false) // 工程中一定存在 RestTemplate 类 @ConditionalOnClass(RestTemplate.class) // 容器中一定存在 LoadBalancerClient 类 Bean 实例 @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { // 获取 Spring 容器中所有的 RestTemplate 实例 @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); // 获取 Spring 容器中 LoadBalancerRequestTransformer 实例 @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); // 在 Bean 初始化完成后会调用 afterSingletonsInstantiated 方法 // 这里是一个 lambda 表达式方式的实现, 主要是为 restTemplate 实例设置 RestTemplateCustomizer @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } // LoadBalancerRequestFactory 工厂类 // 主要是用来提供 LoadBalancerClient 实例和 LoadBalancerRequestTransformer @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } // LoadBalancerInterceptor 拦截器 @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { // 创建默认的拦截器 LoadBalancerInterceptor 的实例 @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } // 如果没有 RestTemplateCustomizer 实例才会创建 // 这里就就会为咱们所有的 restTemplate 实例添加 loadBalancerInterceptor 拦截器 @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } // ... }
针对下面的代码我们可以总结一下:
- 如果需要使用负载均衡,工程下面必须要有 RestTemplate 类, 然后Spring 容器中要有
LoadBalancerClient
的实例。
LoadBalancerClient
在spring-cloud-netflix-ribbon
中只有一个实现类:RibbonLoadBalancerClient
- 利用 Spring 的
SmartInitializingSingleton
拓展点,在restTemplateCustomizer()
中为所有的 RestTemplate 添加LoadBalancerInterceptor
拦截器
- 其实 LoadBalancer 的本质就是通过拦截器。利用
RestTemplate
的拓展点来实现请求服务的负载均衡。
LoadBalancerInterceptor
LoadBalancerInterceptor
拦截器会将请求交给 LoadBalancerClient
去处理,首先会选择一个 ILoadBalancer
的实现来处理获取和选择服务,然后通过 serviceName
和负载均衡算法去选择 Server
对象。最后执行请求。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { // 负载均衡 private LoadBalancerClient loadBalancer; // 构建请求 private LoadBalancerRequestFactory requestFactory; // ... @Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } }
RibbonLoadBalancerClient
我们通过跟踪 this.loadBalancer.execute
代码发现。最终所有的请求都交由 RibbonLoadBalancerClient
去处理。它实现了。LoadBalancerClient
接口, 代码如下:
public interface ServiceInstanceChooser { // 通过 serviceId 选择具体的服务实例 ServiceInstance choose(String serviceId); } public interface LoadBalancerClient extends ServiceInstanceChooser { <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException; <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException; // 将服务实例信息替换还具体的 IP 信息 URI reconstructURI(ServiceInstance instance, URI original); }