前面已经分析了Ribbon各个组件详细的源码,以及整体的流程
SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析:
示例项目
我们首先在127.0.0.1:8222,127.0.0.1:8221启动两个进程,一个是正常工作的127.0.0.1:8221:
@RestController @SpringBootApplication public class TestService { @RequestMapping("/test") public String test() { return "test"; } public static void main(String[] args) { SpringApplication.run(TestService.class, args); } }
令一个是一定会抛出异常(http响应码为500)的127.0.0.1:8222:
@RestController @SpringBootApplication public class TestService { @RequestMapping("/test") public String test() { throw new IllegalArgumentException(); } public static void main(String[] args) { SpringApplication.run(TestService.class, args); } }
之后在我们的项目中引入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <!--以下依赖是启动Spring cloud应用需要--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
定义一个RibbonClient:
@Configuration @RibbonClient(name = "default-test") public class DefaultConfiguration { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
在这个Configuration中,我们定义了一个名为“default-test”的RibbonClient(就是IClient),一个负载均衡的RestTemplate(因为@LoadBalanced注解的缘故,普通的Restemplate变成了可以负载均衡的RestTemplate,之后我们会分析这个注解的作用)
再定义一个Service,让它定时调用default-test这个微服务:
@Service public class DefaultService { @Autowired private RestTemplate restTemplate; @Scheduled(fixedDelay = 5000) public void testDefaultRibbon() { String forObject = restTemplate.getForObject("http://default-test/test", String.class); System.out.println("**********************"); System.out.println(forObject); } }
编写application.properties:
default-test.ribbon.listOfServers=127.0.0.1:8222,127.0.0.1:8221 # ribbon连接超时 default-test.ribbon.ConnectTimeout=500 # ribbon读超时 default-test.ribbon.ReadTimeout=8000 ######## management ######## management.security.enabled=false endpoints.health.sensitive=false
启动:
@EnableScheduling @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
我们可以观察到127.0.0.1:8222,127.0.0.1:8221被依次调用。
示例项目分析
@LoadBalanced
注解的源码:
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }
注意有@Qualifier
这个注解,这个注解用在另外一个注解上(这里是LoadBalanced)时,代表所有含有@LoadBalanced
注解的bean都会被标记起来,如果这时有:
@LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList();
代表所有被LoadBalanced修饰的restTemplates会被装载到这里
在我们的应用启动时,LoadBalancerAutoConfiguration会给所有被@LoadBalanced注解修饰的RestTemplate增加一个LoadBalanceInterceptor:
@Configuration @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) //所有被LoadBalanced修饰的restTemplates会被装载到这里 private List<RestTemplate> restTemplates = Collections.emptyList(); //SmartInitializingSingleton代表所有非lazy单例Bean实例化完成后的回调方法,即所有被LoadBalanced修饰的restTemplates会被初始化并装载到这里之后,这个afterSingletonsInstantiated会被回调 @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer( final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { @Override public void afterSingletonsInstantiated() { //所有restTemplate被customizers处理 for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } } }; } @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, transformers); } //在没有包含spring-retry这个依赖时,以下会被初始化,我们上面的项目就是没有加入spring-retry这个依赖 @Configuration @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 new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } //在包含spring-retry这个依赖时,以下会被初始化,之后我们会讲这个重试 @Configuration @ConditionalOnClass(RetryTemplate.class) public static class RetryAutoConfiguration { @Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); template.setThrowLastExceptionOnExhausted(true); return template; } @Bean @ConditionalOnMissingBean public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); } } }
在执行请求(restTemplate.getForObject("http://default-test/test", String.class);
)时,会先经过所有的Interceptor,其中这个LoadBalanceInterceptor,其实就是利用loadBalancer将原请求转化成一个负载均衡请求并执行:
LoadBalancerInterceptor.java
@Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); //利用loadBalancer将原请求转化成一个负载均衡请求并执行 return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); }
RibbonLoadBalancerClient是SpringCloud对于Ribbon的封装,在这里会初始化Ribbon的配置,所以其实Ribbon的配置是懒加载的:
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { //根据serviceId(这里是default-test)获取loadBalancer,如果不存在就创建 ILoadBalancer loadBalancer = getLoadBalancer(serviceId); //从LoadBalancer中获取一个Server Server server = getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } //封装成为RibbonServer RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); //执行 return execute(serviceId, ribbonServer, request); }
其实这里我们就能看出,Ribbon在这里只是提供一个Server,之后的执行请求并不是Ribbon的IClient负责的。所以,我们之前讨论的Ribbon8大元素,在SpringCloud的环境下,其实只用到了其中七个。SpringCloud实现了自己的负载均衡器RibbonLoadBalancerClient。
总结下,流程大概就是如下图所示:
这里getLoadBalancer是初始化RibbonClientConfiguration这个懒加载的Configuration:
@SuppressWarnings("deprecation") @Configuration @EnableConfigurationProperties //Order is important here, last should be the default, first should be optional // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653 @Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class}) public class RibbonClientConfiguration { //Ribbonclient名称,这里是default-test @Value("${ribbon.client.name}") private String name = "client"; @Autowired private PropertiesFactory propertiesFactory; //从配置文件(application.properties)中读取default-test这个RibbonClient的配置 @Bean @ConditionalOnMissingBean public IClientConfig ribbonClientConfig() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.loadProperties(this.name); return config; } //如果没设置,IRule默认为ZoneAvoidanceRule @Bean @ConditionalOnMissingBean public IRule ribbonRule(IClientConfig config) { if (this.propertiesFactory.isSet(IRule.class, name)) { return this.propertiesFactory.get(IRule.class, config, name); } ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; } //如果没设置,IPing默认为DummyPing @Bean @ConditionalOnMissingBean public IPing ribbonPing(IClientConfig config) { if (this.propertiesFactory.isSet(IPing.class, name)) { return this.propertiesFactory.get(IPing.class, config, name); } return new DummyPing(); } //如果没设置,ServerList默认为ConfigurationBasedServerList @Bean @ConditionalOnMissingBean @SuppressWarnings("unchecked") public ServerList<Server> ribbonServerList(IClientConfig config) { if (this.propertiesFactory.isSet(ServerList.class, name)) { return this.propertiesFactory.get(ServerList.class, config, name); } ConfigurationBasedServerList serverList = new ConfigurationBasedServerList(); serverList.initWithNiwsConfig(config); return serverList; } //ServerListUpdater是PollingServerListUpdater,这个只能通过Bean替换,不能通过配置文件配置 @Bean @ConditionalOnMissingBean public ServerListUpdater ribbonServerListUpdater(IClientConfig config) { return new PollingServerListUpdater(config); } //如果没设置,ILoadBalancer默认为ZoneAwareLoadBalancer @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) { return this.propertiesFactory.get(ILoadBalancer.class, config, name); } return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); } //如果没设置,ServerListFilter默认为ZonePreferenceServerListFilter @Bean @ConditionalOnMissingBean @SuppressWarnings("unchecked") public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) { if (this.propertiesFactory.isSet(ServerListFilter.class, name)) { return this.propertiesFactory.get(ServerListFilter.class, config, name); } ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); filter.initWithNiwsConfig(config); return filter; } @Bean @ConditionalOnMissingBean public RibbonLoadBalancerContext ribbonLoadBalancerContext( ILoadBalancer loadBalancer, IClientConfig config, RetryHandler retryHandler) { return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler); } @Bean @ConditionalOnMissingBean public RetryHandler retryHandler(IClientConfig config) { return new DefaultLoadBalancerRetryHandler(config); } @Bean @ConditionalOnMissingBean public ServerIntrospector serverIntrospector() { return new DefaultServerIntrospector(); } @PostConstruct public void preprocess() { setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name); } static class OverrideRestClient extends RestClient { private IClientConfig config; private ServerIntrospector serverIntrospector; protected OverrideRestClient(IClientConfig config, ServerIntrospector serverIntrospector) { super(); this.config = config; this.serverIntrospector = serverIntrospector; initWithNiwsConfig(this.config); } @Override public URI reconstructURIWithServer(Server server, URI original) { URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector, server); return super.reconstructURIWithServer(server, uri); } @Override protected Client apacheHttpClientSpecificInitialization() { ApacheHttpClient4 apache = (ApacheHttpClient4) super .apacheHttpClientSpecificInitialization(); apache.getClientHandler() .getHttpClient() .getParams() .setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES); return apache; } } }
SpringCloud环境下纯Ribbon配置分析
总结起来,在Spring Cloud环境下Ribbon默认的实现类如下所示:
- 服务实例列表维护机制实现的接口ServerList: ConfigurationBasedServerList
- 负责选取Server的接口ILoadBalancer:ZoneAwareLoadBalancer
- 负载均衡选取规则实现的接口IRule:ZoneAvoidanceRule
- 检查实例是否存活实现的接口IPing:DummyPing
- 服务实例列表更新机制实现的接口ServerListUpdater:PollingServerListUpdater
- 服务实例列表过滤机制ServerListFilter:ZonePreferenceServerListFilter
Ribbon的配置有很多,例如上面我们用到的ribbon配置:
# ribbon连接超时 default-test.ribbon.ConnectTimeout=500 # ribbon读超时 default-test.ribbon.ReadTimeout=8000
这些配置是基于Archaius的,我们可以看DefaultClientConfigImpl这个类:
public void loadProperties(String restClientName){ enableDynamicProperties = true; setClientName(restClientName); //载入默认值 loadDefaultValues(); //载入配置值,包括“微服务名.ribbon.配置”和“ribbon.配置”这两种的所有配置 Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName); for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){ String key = keys.next(); String prop = key; try { if (prop.startsWith(getNameSpace())){ prop = prop.substring(getNameSpace().length() + 1); } setPropertyInternal(prop, getStringValue(props, key)); } catch (Exception ex) { throw new RuntimeException(String.format("Property %s is invalid", prop)); } } }
像这种配置,是基于Archaius的,可以是微服务名.ribbon.配置
这么去配置,或者ribbon.配置
这么去配置。
但是像默认实现类的配置,只能通过微服务名.ribbon.配置
这么去配(通过PropertiesFactory配置的),或者通过自定义同类型Bean去替换:
PropertiesFactory.java:
public class PropertiesFactory { @Autowired private Environment environment; private Map<Class, String> classToProperty = new HashMap<>(); public PropertiesFactory() { classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName"); classToProperty.put(IPing.class, "NFLoadBalancerPingClassName"); classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName"); classToProperty.put(ServerList.class, "NIWSServerListClassName"); classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName"); } public boolean isSet(Class clazz, String name) { return StringUtils.hasText(getClassName(clazz, name)); } public String getClassName(Class clazz, String name) { if (this.classToProperty.containsKey(clazz)) { String classNameProperty = this.classToProperty.get(clazz); String className = environment.getProperty(name + "." + NAMESPACE + "." + classNameProperty); return className; } return null; } @SuppressWarnings("unchecked") public <C> C get(Class<C> clazz, IClientConfig config, String name) { String className = getClassName(clazz, name); if (StringUtils.hasText(className)) { try { Class<?> toInstantiate = Class.forName(className); return (C) instantiateWithConfig(toInstantiate, config); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name); } } return null; } }
修改这些默认实现类Ribbon的配置我们可以通过在自己的configuration添加对应的Bean,或者通过配置文件中添加如下配置实现:
#配置ILoadBalancer default-test.ribbon.NFLoadBalancerClassName= #配置IPing default-test.ribbon.NFLoadBalancerPingClassName= #配置IRule default-test.ribbon.NFLoadBalancerRuleClassName= #配置ServerList default-test.ribbon.NIWSServerListClassName= #配置ServerListFilter default-test.ribbon.NIWSServerListFilterClassName=