本文基于SpringCloud-Dalston.SR5
前面已经分析了Ribbon各个组件详细的源码,以及整体的流程
SpringCloud环境下纯Ribbon(不包含Eureka)使用与启动分析:
示例项目
以下项目可以参考:https://github.com/HashZhang/ScanfoldAll/tree/master/Scanfold-SpringCloud/Scanfold-SpringCloud-Ribbon/Scanfold-SpringCloud-RibbonOnly
我们首先在127.0.0.1:8222,127.0.0.1:8221启动两个进程,一个是正常工作的127.0.0.1:8221:
代码语言:javascript复制@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:
代码语言:javascript复制@RestController
@SpringBootApplication
public class TestService {
@RequestMapping("/test")
public String test() {
throw new IllegalArgumentException();
}
public static void main(String[] args) {
SpringApplication.run(TestService.class, args);
}
}
之后在我们的项目中引入依赖:
代码语言:javascript复制<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
定义一个RibbonClient:
代码语言:javascript复制@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这个微服务:
代码语言:javascript复制@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:
代码语言:javascript复制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
启动:
代码语言:javascript复制@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 restTemplates = Collections.emptyList();
代表所有被LoadBalanced修饰的restTemplates会被装载到这里
在我们的应用启动时,LoadBalancerAutoConfiguration会给所有被@LoadBalanced注解修饰的RestTemplate增加一个LoadBalanceInterceptor:
代码语言:javascript复制@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
//所有被LoadBalanced修饰的restTemplates会被装载到这里
private List restTemplates = Collections.emptyList();
//SmartInitializingSingleton代表所有非lazy单例Bean实例化完成后的回调方法,即所有被LoadBalanced修饰的restTemplates会被初始化并装载到这里之后,这个afterSingletonsInstantiated会被回调
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List 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 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 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
代码语言:javascript复制@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的配置是懒加载的:
代码语言:javascript复制public T execute(String serviceId, LoadBalancerRequest 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:
代码语言:javascript复制@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 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 serverList, ServerListFilter 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 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配置:
代码语言:javascript复制# ribbon连接超时
default-test.ribbon.ConnectTimeout=500
# ribbon读超时
default-test.ribbon.ReadTimeout=8000
这些配置是基于Archaius的,我们可以看DefaultClientConfigImpl这个类:
代码语言:javascript复制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:
代码语言:javascript复制public class PropertiesFactory {
@Autowired
private Environment environment;
private Map 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 get(Class 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,或者通过配置文件中添加如下配置实现:
代码语言:javascript复制#配置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=