Spring-Cloud-Ribbon作为微服务框架的负载均衡组件,默认使用RestTemplate接口调用外部服务接口。
原有项目中调用外部服务接口大多数采用的是使用了第三方的HttpClient库,如:Apache HttpClient或Asynchronous Http Client。
为了保证不影响现有业务接口的稳定性和无须修改原有业务接口代码,接下来本文会介绍如何改造。
在使用Ribbon进行服务消费的时候,我们用到了RestTemplate
,但是熟悉Spring的同学们是否产生过这样的疑问:RestTemplate
不是Spring自己就有的吗?跟Ribbon的客户端负载均衡又有什么关系呢?我们来看RestTemplate
和Ribbon
是如何联系起来并实现客户端负载均衡的。
在实际的消费者示例中,我们只是简单的通过添加@LoadBalanced注解,那么它是如何实现客户端负载均衡的?仔细观察一下代码之前的代码,我们可以发现在消费者的例子中,可能就是这个注解@LoadBalanced
是之前没有接触过的,并且从命名上来看也与负载均衡相关。我们不妨以此为线索来看看源码实现的机制。
从@LoadBalanced
注解源码的注释中,我们可以知道该注解用来给RestTemplate
标记,以使用负载均衡的客户端(LoadBalancerClient
)来配置它。
通过搜索LoadBalancerClient
,我们可以发现这是Spring Cloud中定义的一个接口:
public interface LoadBalancerClient extends ServiceInstanceChooser {
/**
* execute request using a ServiceInstance from the LoadBalancer for the specified
* service
* @param serviceId the service id to look up the LoadBalancer
* @param request allows implementations to execute pre and post actions such as
* incrementing metrics
* @return the result of the LoadBalancerRequest callback on the selected
* ServiceInstance
*/
T execute(String serviceId, LoadBalancerRequest request) throws IOException;
/**
* execute request using a ServiceInstance from the LoadBalancer for the specified
* service
* @param serviceId the service id to look up the LoadBalancer
* @param serviceInstance the service to execute the request to
* @param request allows implementations to execute pre and post actions such as
* incrementing metrics
* @return the result of the LoadBalancerRequest callback on the selected
* ServiceInstance
*/
T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException;
/**
* Create a proper URI with a real host and port for systems to utilize.
* Some systems use a URI with the logical serivce name as the host,
* such as http://myservice/path/to/service. This will replace the
* service name with the host:port from the ServiceInstance.
* @param instance
* @param original a URI with the host as a logical service name
* @return a reconstructed URI
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
从该接口中,我们可以通过定义的抽象方法来了解到客户端负载均衡器中应具备的几种能力:
ServiceInstance choose(String serviceId)
:根据传入的服务名serviceId
,从负载均衡器中挑选一个对应服务的实例。T execute(String serviceId, LoadBalancerRequest request) throws IOException
:使用从负载均衡器中挑选出的服务实例来执行请求内容。T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest request,) throws IOException
:使用从负载均衡器中挑选出的服务实例来执行请求内容。URI reconstructURI(ServiceInstance instance, URI original)
:为系统构建一个合适的“host:port”形式的URI。在分布式系统中,我们使用逻辑上的服务名称作为host来构建URI(替代服务实例的“host:port”形式)进行请求,比如:http://myservice/path/to/service
。在该操作的定义中,前者ServiceInstance
对象是带有host和port的具体服务实例,而后者URI对象则是使用逻辑服务名定义为host的URI,而返回的URI内容则是通过ServiceInstance
的服务实例详情拼接出的具体“host:post”形式的请求地址。顺着LoadBalancerClient
接口的所属包org.springframework.cloud.client.loadbalancer
,我们对其内容进行整理,可以得出如下类的关系图:
从类的命名上我们初步判断LoadBalancerAutoConfiguration
为实现客户端负载均衡器的自动化配置类。通过查看源码,我们可以验证这一点假设:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
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);
}
@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 new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setThrowLastExceptionOnExhausted(true);
return template;
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory() {
return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory() {
return new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
LoadBalancerRequestFactory requestFactory,
LoadBalancedBackOffPolicyFactory backOffPolicyFactory,
LoadBalancedRetryListenerFactory retryListenerFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
lbRetryPolicyFactory, requestFactory, backOffPolicyFactory, retryListenerFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}
从LoadBalancerAutoConfiguration
类头上的注解可以知道Ribbon实现的负载均衡自动化配置需要满足下面两个条件:
@ConditionalOnClass(RestTemplate.class)
:RestTemplate
类必须存在于当前工程的环境中。@ConditionalOnBean(LoadBalancerClient.class)
:在Spring的Bean工程中有必须有LoadBalancerClient
的实现Bean。在该自动化配置类中,主要做了下面几件事:
LoadBalancerInterceptor
的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。RestTemplateCustomizer
的Bean,用于给RestTemplate
增加LoadBalancerInterceptor
拦截器。@LoadBalanced
注解修饰的RestTemplate
对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer
的实例来给需要客户端负载均衡的RestTemplate
增加LoadBalancerInterceptor
拦截器。接下来,我们看看LoadBalancerInterceptor
拦截器是如何将一个普通的RestTemplate
变成客户端负载均衡的:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@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);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
通过源码以及之前的自动化配置类,我们可以看到在拦截器中注入了LoadBalancerClient
的实现。当一个被@LoadBalanced
注解修饰的RestTemplate
对象向外发起HTTP请求时,会被LoadBalancerInterceptor
类的intercept
函数所拦截。
由于我们在使用RestTemplate时候采用了服务名作为host,所以直接从HttpRequest
的URI对象中通过getHost()就可以拿到服务名,然后调用execute
函数去根据服务名来选择实例并发起实际的请求,具体的实现代码如下:
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);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
通过源码以及之前的自动化配置类,我们可以看到在拦截器中注入了LoadBalancerClient
的实现。当一个被@LoadBalanced
注解修饰的RestTemplate
对象向外发起HTTP请求时,会被LoadBalancerInterceptor
类的intercept
函数所拦截。
由于我们在使用RestTemplate时候采用了服务名作为host,所以直接从HttpRequest
的URI对象中通过getHost()就可以拿到服务名,然后调用execute
函数去根据服务名来选择实例并发起实际的请求,具体的实现代码如下:
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);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
分析到这里,LoadBalancerClient
还只是一个抽象的负载均衡器接口,所以我们还需要找到它的具体实现类来进一步分析。通过查看ribbon的源码,我们可以很容易的在org.springframework.cloud.netflix.ribbon
包下找到对应的实现类:RibbonLoadBalancerClient
。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
Server server = new Server(instance.getHost(), instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig,
serverIntrospector, server);
return context.reconstructURIWithServer(server, uri);
}
@Override
public ServiceInstance choose(String serviceId) {
Server server = getServer(serviceId);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
@Override
public T execute(String serviceId, LoadBalancerRequest request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
@Override
public T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException {
Server server = null;
if(serviceInstance instanceof RibbonServer) {
server = ((RibbonServer)serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
private boolean isSecure(Server server, String serviceId) {
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
return RibbonUtils.isSecure(config, serverIntrospector, server);
}
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
public static class RibbonServer implements ServiceInstance {
private final String serviceId;
private final Server server;
private final boolean secure;
private Map metadata;
public RibbonServer(String serviceId, Server server) {
this(serviceId, server, false, Collections. emptyMap());
}
public RibbonServer(String serviceId, Server server, boolean secure,
Map metadata) {
this.serviceId = serviceId;
this.server = server;
this.secure = secure;
this.metadata = metadata;
}
@Override
public String getServiceId() {
return this.serviceId;
}
@Override
public String getHost() {
return this.server.getHost();
}
@Override
public int getPort() {
return this.server.getPort();
}
@Override
public boolean isSecure() {
return this.secure;
}
@Override
public URI getUri() {
return DefaultServiceInstance.getUri(this);
}
@Override
public Map getMetadata() {
return this.metadata;
}
public Server getServer() {
return this.server;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("RibbonServer{");
sb.append("serviceId='").append(serviceId).append('\'');
sb.append(", server=").append(server);
sb.append(", secure=").append(secure);
sb.append(", metadata=").append(metadata);
sb.append('}');
return sb.toString();
}
}
}
可以看到,在execute
函数的实现中,第一步做的就是通过传入的服务名serviceId
去获得具体的服务实例:
@Override
public T execute(String serviceId, LoadBalancerRequest request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
通过getServer
函数的实现源码,我们可以看到这里获取具体服务实例的时候并没有使用LoadBalancerClient
接口中的choose
函数,而是使用了ribbon自身的ILoadBalancer
接口中定义的chooseServer
函数。
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
为了搞清楚具体的逻辑,先来认识一下ILoadBalancer
接口:
public interface ILoadBalancer {
/**
* Initial list of servers.
* This API also serves to add additional ones at a later time
* The same logical server (host:port) could essentially be added multiple times
* (helpful in cases where you want to give more "weightage" perhaps ..)
*
* @param newServers new servers to add
*/
public void addServers(List newServers);
/**
* Choose a server from load balancer.
*
* @param key An object that the load balancer may use to determine which server to return. null if
* the load balancer does not use this parameter.
* @return server chosen
*/
public Server chooseServer(Object key);
/**
* To be called by the clients of the load balancer to notify that a Server is down
* else, the LB will think its still Alive until the next Ping cycle - potentially
* (assuming that the LB Impl does a ping)
*
* @param server Server to mark as down
*/
public void markServerDown(Server server);
/**
* @deprecated 2016-01-20 This method is deprecated in favor of the
* cleaner {@link #getReachableServers} (equivalent to availableOnly=true)
* and {@link #getAllServers} API (equivalent to availableOnly=false).
*
* Get the current list of servers.
*
* @param availableOnly if true, only live and available servers should be returned
*/
@Deprecated
public List getServerList(boolean availableOnly);
/**
* @return Only the servers that are up and reachable.
*/
public List getReachableServers();
/**
* @return All known servers, both reachable and unreachable.
*/
public List getAllServers();
}
可以看到,在该接口中定义了一个软负载均衡器需要的一系列抽象操作(未例举过期函数):
addServers
:向负载均衡器中维护的实例列表增加服务实例。chooseServer
:通过某种策略,从负载均衡器中挑选出一个具体的服务实例。markServerDown
:用来通知和标识负载均衡器中某个具体实例已经停止服务,不然负载均衡器在下一次获取服务实例清单前都会认为服务实例均是正常服务的。getReachableServers
:获取当前正常服务的实例列表。getAllServers
:获取所有已知的服务实例列表,包括正常服务和停止服务的实例。在该接口定义中涉及到的Server
对象定义的是一个传统的服务端节点,在该类中存储了服务端节点的一些元数据信息,包括:host、port、zone、schema以及一些部署信息等。
而对于该接口的实现,我们可以整理出如上图所示的结构。我们可以看到BaseLoadBalancer
类实现了基础的负载均衡,而DynamicServerListLoadBalancer
和ZoneAwareLoadBalancer
在负载均衡的策略上做了一些功能的扩展。
那么在整合Ribbon的时候Spring Cloud默认采用了哪个具体实现呢?我们通过spring-cloud-netflix-core包的RibbonClientConfiguration
配置类,可以知道在整合时默认采用了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);
}
下面,我们再回到RibbonLoadBalancerClient
的execute
函数逻辑,在通过ZoneAwareLoadBalancer
的chooseServer
函数获取了负载均衡策略分配到的服务实例对象Server
之后,将其内容包装成RibbonServer
对象(该对象除了存储了服务实例的信息之外,还增加了服务名serviceId、是否需要使用HTTPS等其他信息)。然后使用该对象再回调LoadBalancerInterceptor
请求拦截器中LoadBalancerRequest
的apply(final ServiceInstance instance)
函数,向一个实际的具体服务实例发起请求,从而实现一开始以服务名为host的URI请求,到实际访问host:post形式的具体地址的转换。
apply(final ServiceInstance instance)
函数中传入的ServiceInstance
接口是对服务实例的抽象定义。在该接口中暴露了服务治理系统中每个服务实例需要提供的一些基本信息,比如:serviceId、host、port等,具体定义如下:
public interface ServiceInstance {
/**
* @return the service id as registered.
*/
String getServiceId();
/**
* @return the hostname of the registered ServiceInstance
*/
String getHost();
/**
* @return the port of the registered ServiceInstance
*/
int getPort();
/**
* @return if the port of the registered ServiceInstance is https or not
*/
boolean isSecure();
/**
* @return the service uri address
*/
URI getUri();
/**
* @return the key value pair metadata associated with the service instance
*/
Map getMetadata();
}
而上面提到的具体包装Server
服务实例的RibbonServer
对象就是ServiceInstance
接口的实现,可以看到它除了包含了Server
对象之外,还存储了服务名、是否使用https标识以及一个Map类型的元数据集合。
public static class RibbonServer implements ServiceInstance {
private final String serviceId;
private final Server server;
private final boolean secure;
private Map metadata;
public RibbonServer(String serviceId, Server server) {
this(serviceId, server, false, Collections. emptyMap());
}
public RibbonServer(String serviceId, Server server, boolean secure,
Map metadata) {
this.serviceId = serviceId;
this.server = server;
this.secure = secure;
this.metadata = metadata;
}
}
那么apply(final ServiceInstance instance)
函数,在接收到了具体ServiceInstance
实例后,是如何通过LoadBalancerClient
接口中的reconstructURI
操作来组织具体请求地址的呢?
public LoadBalancerRequest createRequest(final HttpRequest request,final byte[] body, final ClientHttpRequestExecution execution) {
return new LoadBalancerRequest() {
@Override
public ClientHttpResponse apply(final ServiceInstance instance) throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);
if (transformers != null) {
for (LoadBalancerRequestTransformer transformer : transformers) {
serviceRequest = transformer.transformRequest(serviceRequest, instance);
}
}
return execution.execute(serviceRequest, body);
}
};
}
从apply
的实现中,我们可以看到它具体执行的时候,还传入了ServiceRequestWrapper
对象,该对象继承了HttpRequestWrapper
并重写了getURI
函数,重写后的getURI
会通过调用LoadBalancerClient
接口的reconstructURI
函数来重新构建一个URI来进行访问。
public class ServiceRequestWrapper extends HttpRequestWrapper {
private final ServiceInstance instance;
private final LoadBalancerClient loadBalancer;
public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance,LoadBalancerClient loadBalancer) {
super(request);
this.instance = instance;
this.loadBalancer = loadBalancer;
}
@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}
}
在LoadBalancerInterceptor
拦截器中,ClientHttpRequestExecution
的实例具体执行execution.execute(serviceRequest, body)
时,会调用InterceptingClientHttpRequest
下InterceptingRequestExecution
类的execute
函数,具体实现如下:
@Override
public ClientHttpResponse execute(HttpRequest request, final byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
for (Map.Entry> entry : request.getHeaders().entrySet()) {
List values = entry.getValue();
for (String value : values) {
delegate.getHeaders().add(entry.getKey(), value);
}
}
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(final OutputStream outputStream) throws IOException {
StreamUtils.copy(body, outputStream);
}
});
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
可以看到在创建请求的时候requestFactory.createRequest(request.getURI(), request.getMethod());
这里request.getURI()
会调用之前介绍的ServiceRequestWrapper
对象中重写的getURI
函数。此时,它就会使用RibbonLoadBalancerClient
中实现的reconstructURI
来组织具体请求的服务实例地址。
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
Server server = new Server(instance.getHost(), instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig,serverIntrospector, server);
return context.reconstructURIWithServer(server, uri);
}
从reconstructURI
函数中,我们可以看到,它通过ServiceInstance
实例对象的serviceId
,从SpringClientFactory
类的clientFactory
对象中获取对应serviceId
的负载均衡器的上下文RibbonLoadBalancerContext
对象。然后根据ServiceInstance
中的信息来构建具体服务实例信息的Server
对象,并使用RibbonLoadBalancerContext
对象的reconstructURIWithServer
函数来构建服务实例的URI。
为了帮助理解,简单介绍一下上面提到的SpringClientFactory
和RibbonLoadBalancerContext
:
SpringClientFactory
类是一个用来创建客户端负载均衡器的工厂类,该工厂会为每一个不同名的ribbon客户端生成不同的Spring上下文。RibbonLoadBalancerContext
类是LoadBalancerContext
的子类,该类用于存储一些被负载均衡器使用的上下文内容和Api操作(reconstructURIWithServer
就是其中之一)。从reconstructURIWithServer
的实现中我们可以看到,它同reconstructURI
的定义类似。只是reconstructURI
的第一个保存具体服务实例的参数使用了Spring Cloud定义的ServiceInstance
,而reconstructURIWithServer
中使用了Netflix中定义的Server
,所以在RibbonLoadBalancerClient
实现reconstructURI
时候,做了一次转换,使用ServiceInstance
的host和port信息来构建了一个Server
对象来给reconstructURIWithServer
使用。从reconstructURIWithServer
的实现逻辑中,我们可以看到,它从Server
对象中获取host和port信息,然后根据以服务名为host的URI
对象original中获取其他请求信息,将两者内容进行拼接整合,形成最终要访问的服务实例的具体地址。
由于原有业务服务接口中使用的是第三方HttpClient库来调用外部服务接口的,调用代码示例如下:
@Component
public class AccountApi {
private static Logger log = LoggerFactory.getLogger(AccountApi.class);
@Autowired
private Setting setting;
@Autowired
private HttpComponent httpComponent;
public User getUserBySsoid(Integer ssoid) {
User user = new User();
String url = MessageFormat.format(setting.API_User_Profile, String.valueOf(ssoid));
log.info(MessageFormat.format("通过ssoid:[{0}] 获取用户信息,url:[{1}]", ssoid, url));
try {
Response response = httpComponent.syncHttpRequest(url, null, RequestMethod.GET);
if (response.getStatusCode() != 200) {
throw new Exception("查找用户信息接口出错,code: " + response.getStatusCode());
}
String data = response.getResponseBody();
JSONObject object = JSONObject.parseObject(data);
JSONObject userObject = object.getJSONObject("data");
user = JSON.parseObject(userObject.toString(), User.class);
} catch (Exception e) {
log.error(MessageFormat.format("通过ssoid:[{0}] 获取用户信息出错,url:[{1}],错误信息:{2}", ssoid, url, e.toString()));
}
reporter.report(Span.builder().name(url).build());
return user;
}
那如何进行改造呢?其实了解了Ribbon负载均衡组件的基本原理后,我们知道原有业务代码中最核心要解决的就是请求URL地址的转换问题,RibbonLoadBalancerClient最终会将逻辑地址(service-id)转换为真实地址+端口号将请求发送出去,所以我们要做的就是将原有的URL地址根据规则进行解析出service-id,然后根据service-id从注册中心中获取真实的主机名和端口号即可,示例如下:
private String reconstructURL(String original) {
Assert.notNull(original, "original can not be null");
StringBuilder builder = new StringBuilder("http://");
String uri1 = original.substring(original.indexOf("//")+2);
String uri2 = uri1.substring(uri1.indexOf("/"));
String[] originals = uri1.split("/");
ServiceInstance instance = lbClient.choose(originals[0]);
Server server = new Server(instance.getHost(), instance.getPort());
return builder.append(server.getHostPort()).append(uri2).toString();
}
reconstructURL函数就是根据源URL按照一定的规则进行解析后重新生成新的URL地址,改造后的代码如下所示:
public User getUserBySsoid(Integer ssoid) {
String requestUrl = reconstructURL(setting.API_User_Profile);
User user = new User();
String url = MessageFormat.format(requestUrl, String.valueOf(ssoid));
log.info(MessageFormat.format("通过ssoid:[{0}] 获取用户信息,url:[{1}]", ssoid, url));
try {
Response response = httpComponent.syncHttpRequest(url, null, RequestMethod.GET);
if (response.getStatusCode() != 200) {
throw new Exception("查找用户信息接口出错,code: " + response.getStatusCode());
}
String data = response.getResponseBody();
JSONObject object = JSONObject.parseObject(data);
JSONObject userObject = object.getJSONObject("data");
user = JSON.parseObject(userObject.toString(), User.class);
} catch (Exception e) {
log.error(MessageFormat.format("通过ssoid:[{0}] 获取用户信息出错,url:[{1}],错误信息:{2}", ssoid, url, e.toString()));
}
reporter.report(Span.builder().name(url).build());
return user;
}
上述只是具体的业务逻辑代码,在业务处理接口中我们需要通过@Autowired注解将LoadBalancerClient接口自动装配。