Spring Cloud Ribbon客户端负载均衡实现过程源码分析

8 篇文章 0 订阅
3 篇文章 0 订阅

一.意义

客户端在生产者--消费者模式中担当资源服务的消费者,当使用Eureka注册服务中心集群各个服务项目形成大型的服务集群时,或者在分布式系统环境中,往往服务端不再是冗繁臃肿的单个web项目,为了适应于高可用,高并发的服务性能需求,适应于易开发,易维护的上线环境.做到服务器的负载均衡的同时,客户端同样需要负载均衡.

二.启动负载均衡

1.RestTemplate

1)四种请求类型:GET/POST/PUT/DELETE

(1)GET请求:在RestTemplate中,由RestTemplate对象实例可对GET请求通过如下两个方法进行调用而实现.

当需要指定响应返回boby体时:使用getForEntity ()函数。该方法返回的是 ResponseEntity对象, 该对象是 Spring对 HTTP 请求响应的封装, 其中主要存储了 HTTP 的几个重要元素, 比如 HTTP 请求状态码的枚举对象 HttpStatus (状态码) ,另外,其父类HttpEn巨ty 中还存储着 HTTP 请求的头信息对象 HttpHeaders 以及泛型类型的请求体对象.

getForEntity(String url, Class responseType, Object... urlVariables):args:  url 为携带参数的request的地址, responseType 为请求响应体body 的包装类型,rlVariables为 url 中的参数绑定.

getForEntity(String url, Class responseType, Map urlVariables):args:urlVariables为Map类型,url中传入Key-Value对.

getForEntity(UR工 url, Class responseType):args:该方法使用URL对象(统一资源标识符引用)指定访问地址和传入参数.

当不需要关注请求响应除boby体外的其他内容时:使用getForObject ()函数。该方法通过 HttpMessageConverterExtractor 对 HTTP 的请求响应体 body内容进行对象转换,直接将请求响应的boby内容包装成对象来返回使用, 从而实现请求直接返回包装好的对象内容。 

getForObject (String url, Class responseType, Object... urlVariables):args:与 getForEntity ()方法类似, url 参数指定访问的地址, responseType 参数定义该方法的返回类型, urlVariables 参数为 url 中占位符对应的参数。

getForObject(String url, Class responseType, Map urlVariables):args:使用 Map 类型的 urlVariables 替代上面数组形式的 urlVariables,使用时需要在 url 中将占位符的名称与 Map 类型中的 key一一对应设置从而组成Key-Value对。

getForObject(UR工 url, Class responseType): 该方法使用 URI 对象来替代之前的 url和urlVariables 参数使用。

 

(2)POST请求:在 Res七Template中, 对 POST 请求时可以通过如下三个方法进行调用实现:

 

postForEntity ()函数:

• postForEntity(String url, Objec七 request, Class responseType,Object... uriVariables)

• pos七ForEntity(String url, Object request, Class responseType,Map uriVariables)

• postForEntity(URI url, Object request, Class responseType)

postForObject() 函数:

• postForObject(String url, Object request, Class responseType,Object... uriVariables)

• postForObject(String url, Object request, Class responseType,Map uriVariables)

• postForObject(URI url, Object reques七, Class responseType)

postForLocation()函数:该方法实现了以POST请求提交资源, 并返回新资源的URI.

•postForLocation(String url, Object request, Object...urlVariables)

•postForLocation(String url, Object request, Map urlVariables)

•postForLocation(URI url, Object request)

 

(3)PUT请求:在RestTemplate中, 对PUT请求可以通过put方法 进行调用实现.

• put(String url, Object request, Object ... urlVariables)
• put(String url, Object request, Map urlVariables)
• put(URI url, Object request)

注意:put()函数为void类型函数,所以没有返回内容,也就没有请求响应的responseType参数.

(4)DELETE请求:在RestTemplate中,对DELETE请求可以通过delete方法进行调用实现.

• delete(String url, Object ... urlVariables)
• delete(String url, Map urlVariables)
• delete(URI url)

由于我们在进行REST请求时, 通常都将DELETE请求的唯 一标识拼接在url中, 所以DELETE请求也不需要request的body体信息.url指定DELETE请求的位置, urlVariables绑定url中的参数.

 

2.配置在RestTemplate中以@LoadBalanced注解做标记从而开启客户端负载均衡以使用负载均衡的客户端LoadBalancerClient(接口)来配置它.

该接口定义了三种能力(函数/方法):

• Servicelnstance choose(String serviceid) : 根据传入的 服 务名serviceld,从负载均衡器中挑选一个对应服务 的实例 。
• T execute(String serviceid, LoadBalancerRequest request) throws IOException: 使用从负载均衡器中挑选出的服务实例来执行请求内容 。
• UR I reconstructURI(Serviceinstance instance , URI original ):为系统构建 一个合适的host:post形式的 URI.

3.LoadBalancerAu七oConfiguration为实现客户端负载均衡器的自动化配置类:

@Configuration
@Conditiona!OnClass(RestTemplate.class)
@Conditiona!OnBean(LoadBalancerClient.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartinitializingSingleton loadBalancedRestTemplateinitializer(final List<RestTemplateCustomizer> customizers) {
return new SmartinitializingSingleton() {
@Override
public void afterSingletonsinstantiated() {
for (RestTemplate restTemplate: LoadBalancerAutoConfiguration.this.restTemplates) {

for (RestTemplateCustomizer customizer: customizers) {
customizer.customize(restTemplate);
}

 }

 }

  };

  }
@Bean
@Condi七ionalOnMissingBean
public RestTempla七eCustomizer restTemplateCustomizer(final LoadBalancerinterceptor loadBalancerinterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestinterceptor> list= new ArrayList<ClientHttpRequestinterceptor>(restTemplate. getInterceptors());
list.add(loadBalancerinterceptor);
restTemplate.setinterceptors(list);

}

};

}
@Bean
public LoadBalancerinterceptor ribboninterceptor(LoadBalancerClient loadBalancerClient) {
return new LoadBalancerinterceptor(loadBalancerClient);
}

}

源码分析:Ribbon实现的负载均衡自动化配置 需要满足下面两个条件。
• @ConditionalOnClass(RestTemplate.class): RestTemplate 类必须存在当前工程的环境中。
• @ConditionalOnBean(LoadBalancerClient.class): 在Spring的Bean工程中必须有LoadBalancerClient的实现 Bean。

在该自动化配置类中, 主要做了下面三件事:
• 创建了一个LoadBalancerInterceptor的Bean, 用于实现对客户端发起请求时进行拦截, 以实现客户端负载均衡。
• 创建了 一个RestTemplateCustomizer的Bean, 用于给RestTemplate增加LoadBalancerInterceptor拦截器。
• 维护了 一个被@LoadBalanced 注解修饰的RestTemplate对象列表, 并在这里进行初始化, 通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerinterceptor拦截器。

4.LoadBalancerinterceptor 拦 截 器是如何将 一个普通的RestTemplate变成客户端负载均衡的呢?

public class LoadBalancerinterceptor implements ClientHttpRequestinterceptor {
private LoadBalancerClient loadBalancer;

public LoadBalancerinterceptor(LoadBalancerClient loadBalancer) {
this.loadBalancer = 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();
return this.loadBalancer.execute(serviceName,
new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientH七tpResponse apply(final Serviceinstance instance)
throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request,instance);
return execution.execute(serviceRequest, body);

}
}) ;

}
private class ServiceRequestWrapper extends HttpRequestWrapper {
private final Serviceinstance instance;
public ServiceRequestWrapper(HttpRequest request, Serviceinstance instance) {
super(request);
this.instance = instance;

}
@Override
public URI getURI() {
URI uri = LoadBalancerinterceptor.this.loadBalancer.reconstructURI(
this.instance, getRequest() .getURI());
return uri;
}

}

源码分析:在拦截器中注入了 LoadBalancerClient的实现.当 一个被 @LoadBalanced 注解修饰的 RestTemplate 对象向外发起 HTTP 请求时, 会被 LoadBalancerinterceptor 类的 intercept() 函数所拦截.通过 HttpRequest 的URI对象获取服务名,调用 execute ()函数去根据服务名来选择实例并发起实际的请求。
然而LoadBalancerClient是个接口,那么是谁实现了他的功能呢?
5.org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient类实现了LoadBalancerClient接口

 

public <T> T execute(String serviceid, LoadBalancerRequest<T> request) throwsIOException {
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));
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceid);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(ribbonServer);
statsRecorder.recordStats(returnVal);
return returnVal;

}
catch (IOException ex) {
StatsRecorder.recordStats(ex);
throw ex;

}
catch (Exception ex) {
StatsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);

}
return null;

}


源码分析:

在 execute() 函数的实现中,通过 getServer() 根据传入的服务名 serviceId 去获得具体的服务实例:

protected Server getServer(ILoadBalancer load.Balancer) {
if (load.Balancer == null) {
return null;

}
return loadBalancer.chooseServer("default");
}


在该execute()函数实现中的getServer()方法使用了Netflix Ribbon 自身的 ILoadBalancer 接口中定义的 chooseServer() 函数.

在该接口中定义了 一个客户端负载均衡器需要的一 系列抽象操作:
• addServers(): 向负载均衡器中维护的实例列表增加服务实例。
• chooseServer(): 通过某种策略, 从负载均衡器中挑选出 一个具体的服务实例。
• rnarkServerDown(): 用来通知和标识负载均衡器中某个具体实例已经停止服务, 不然负载均衡器在下 一次获取服务实例清单前都会认为服务实例均是正常服务的。
• getReachableServers(): 获取当前正常服务的实例列表。
• getAllServers(): 获取所有已知的服务实例列表, 包括正常服务和停止服务的实例

6.通过RibbonClien七Con巨gura巨on 配置类, 可以知道在整合时默认采用了 ZoneAwareLoadBalancer 来实现负载均衡器.

 

@Bean
@ConditionalOnMissingBean
public !Load.Balancer ribbonLoad.Balancer(IC让entConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
!Rule rule, !Ping ping) {
ZoneAwareLoad.Balancer<Server> balancer = LoadBalancerBuilder.newBuilder()
.withClientConfig(config).withRule(rule) .withPing(ping)
.withServerLis七Filter(serverListFilter).withDynamicServerLis七(serverList)
.buildDynamicServerListLoad.Balancer();
return balancer;
}


在execute 函数逻辑中, 通过ZoneAwareLoadBalancer 的 chooseServer 函数获取了负载均衡策略分配到的服务实例对象 Server 之后, 将其内容包装成RibbonServer 对象,该对象除了存储了服务实例的信息之外, 还增加了服务名 serviceId、 是否需要使用 HTTPS 等其他信息, 然后使用该对象再回调 LoadBalancerinterceptor 请求拦截器中 LoadBalancerRequestapply(final Serviceinstance instance) 函数, 向 一个实际的具体服务实例发起请求,从而实现一开始以服务名为 host 的 URI 请求到 host:post 形式的实际访问地址的转换。




 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值