- 背景:
最近项目中要求实现请求转发的中间层,其中包含负载均衡和超时重试的功能。具体场景是:我们的服务作为客户端调用第三方服务,第三方服务提供了一个服务的IP和端口号的列表。我们需要根据不同的负载均衡算法来进行服务的调用。 - 实现方式:
在restTemplate,中使用注解@loadbalance,配合合理的ribbon的配置进行实现。 - 核心实现摘要:
restTemplate的bean
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
ribbon的配置
peer-coupon: #服务的ID
ribbon:
listOfServers: 10.10.11.10:7221,12.92.11.36:6221 # 节点列表
ReadTimeout: 300 # 读超时时间
ConnectTimeout: 500 # 连接超时时间
MaxAutoRetries: 1 # 访问当前的节点重试次数,例如选择了节点A ,A访问不通,则重试一次。
MaxAutoRetriesNextServer: 2 #当重试完当前节点之后,选择下一个节点的次数。
OkToRetryOnAllOperations: true # 所有的请求都失败重试
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 其他负载均衡算法,参考相应包下的类。
#NFLoadBalancerRuleClassName: com.umpay.uchains.proxy.loadbalance.rule.IpHashRule #这是自己实现的负载均衡算法
NFLoadBalancerClassName: com.netflix.loadbalancer.ZoneAwareLoadBalancer #这是负载均衡的一个控制器。
spring:
cloud:
loadbalancer:
retry:
enabled: true #这个配置项 配置了 支持重试
核心代码
String url = "http://"+serviceId+uri; //serviceId 对应的是配置文件中的 服务的ID 也就是 例子中的 peer-coupon
logger.debug("post={}, json={}",url,object);
return restTemplate.postForObject(url,object,JSONObject.class);
- 原理分析:
1、restTemplate继承了 InterceptingHttpAccessor
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations
2、InterceptingHttpAccessor 内部持有拦截器的列表
public abstract class InterceptingHttpAccessor extends HttpAccessor {
private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
3、类LoadBalancerAutoConfiguration 实现了对 restTemplate中拦截器的加载。
内部静态类 LoadBalancerInterceptorConfig 首先生成LoadBalancerInterceptor 再往restTemplate内添加拦截器。
LoadBalancerInterceptorConfig 的生成依赖于 LoadBalancerRequestFactory 和 LoadBalancerClient 。
LoadBalancerRequestFactory由LoadBalancerClient和transformers构造完成。
LoadBalancerClient依赖于注解@ConditionalOnBean(LoadBalancerClient.class)。
另外该类的配置中还支持 RetryInterceptorAutoConfiguration具有重试能力的拦截器配置。
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@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);
}
}
});
}
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}
@Configuration
@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);
};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
4、具体的LoadBalancerClient是如何生成的?
参考类RibbonClientConfiguration。
生成的LoadBalancerClient 被封装在 RibbonLoadBalancerContext 内部。
@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({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
@RibbonClientName
private String name = "client";
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@Autowired
private PropertiesFactory propertiesFactory;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
return config;
}
@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;
}
@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();
}
@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;
}
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);
}
@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);
}
@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 = updateToSecureConnectionIfNeeded(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;
}
}
}