SpringCloud学习笔记(四、SpringCloud Netflix Ribbon)
目录:
- Ribbon简介
- Ribbon的应用
- RestTemplate简介
- Ribbon负载均衡源码分析
Ribbon简介:
1、负载均衡是什么
负载均衡,根据其字面意思来说就是让集群服务具有共同完成工作的能力,通过负载均衡可以在多个应用实例之间自动分配程序对外服务的能力;从而通过消除单点机器的故障,提升应用的容错能力,让应用更加高效、稳定、安全。
2、SpringCloud Ribbon是什么
SpringCloud Ribbon是基于Http和TCP的一种负载工具,基于Netflix Ribbon实现;它可以将基于服务的rest请求自动转换成客户端负载均衡的服务调用。
Ribbon的应用:
1、Ribbon配置
)添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
)重新构造RestTemplate(使得RestTemplate的请求能够实现负载均衡)
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
)配置properties
## 局部配置-- 单独制定客户端(eureka-provider客户端)
eureka-provider.ribbon.listOfServers=localhost:8072,localhost:8073
配置格式:<clientName>.<nameSpace>.<propertyName>=<value>
propertyName见com.netflix.client.config.CommonClientConfigKey
2、Ribbon重试机制
说到Ribbon的重试机制就需要先看看Ribbon的一些基本配置:
然后我们看最后两个配置,MaxAutoRetriesNextServer、MaxAutoRetries,这两个分别是切换实例的重试次数和实例的重试次数;例如我们有三个实例A、B、C,如果第一次请求A失败,会继续请求A(这便是对实例的重试),此时还是请求失败的话就会去重试实例B,以此类推,直到请求C失败两次后便算是失败,也就是说如上图的重试配置的话请求 2 * 3 = 6次,6次失败就算真的失败。
当然,如果你配置了短路器超时时间,也就是上图的第一个配置的话,那么你总体的重试时间加上第一次正常请求的时间也不能超时1秒。
RestTemplate简介:
RestTemplate封装了多个Http客户端,如HttpClient、OKHttp3等。
详见:org.springframework.web.client.RestTemplate#RestTemplate(org.springframework.http.client.ClientHttpRequestFactory)
1、构造自己的RestTemplate
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate(new OkHttp3ClientHttpRequestFactory(okHttpClient()));
} @Bean
public OkHttpClient okHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10,TimeUnit.SECONDS)
.retryOnConnectionFailure(true);
return builder.build();
}
Ribbon负载均衡源码分析:
我们知道要想是使客户端的请求能够负载均衡的话,只需要重新构造RestTemplate,并为其加上@LoadBalanced注解即可,那为什么加上这个注解就行了呢?
1、首先我们要知道@LoanBalanced是对RestTemplate增强,而RestTemplate是对Http请求的一个封装,所以我们猜测增强时使用的应该是拦截器
)我们进入RestTemplate,并找到父类org.springframework.http.client.support.InterceptingHttpAccessor发现其有一个属性private List<ClientHttpRequestInterceptor> interceptors,这便是spring对http请求的封装,interceptors便是请求所需要经过的拦截器集合;
private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(); /**
* Sets the request interceptors that this accessor should use.
*/
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
this.interceptors = interceptors;
} /**
* Return the request interceptor that this accessor uses.
*/
public List<ClientHttpRequestInterceptor> getInterceptors() {
return interceptors;
}
)然后我们看看这个拦截器集合在哪set的 >>> public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors)
根据类名我们猜测不是上图中的第2个,就是第3个,然后我们分别查看,得知是第2个,我们现在看看第2个的实现:
@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<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
从18到21行代码我们可以看出其在原来http拦截器集合的基础上又增加了loadBalancerInterceptor这个拦截器,而这个拦截器正好就是第5行的那个bean。
根据上述结论我们可以得知:RestTemplate加上了@LoadBalanced注解后,其实就是为http增强了一个拦截器,也就是org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
)那么为什么添加了拦截器会生效呢,我们可以根据RestTemplate的一个请求debug(请求有点深)
如:String result = restTemplate.getForObject("http://eureka-provider/updateProduct/" + productName + "/" + num, String.class);
经过debug后我们可以找到这样一个方法 >>> org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute
@Override
public ClientHttpResponse execute(HttpRequest request, final byte[] body) throws IOException {
if (this.iterator.hasNext()) {
// 拿到当前拦截器集
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
// 执行当前拦截器的intercept方法,添加的LoanBalancerInterceptor拦截器就是这样执行的
return nextInterceptor.intercept(request, body, this);
} else {
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
List<String> 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();
}
}
2、然后我们来看看LoanBalancerInterceptor的实现
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));
}
}
)从LoanBalancerInterceptor的拦截方法intercept中可以看出execute执行的是loanBanlancer的execute
)这也验证了@LoadBalanced注解上的那句Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient(将RestTemplate bean标记为使用LoadBalancerClient的注解)
)然后我们来看看execute的实现 >>> public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
// 拿到可用的服务列表
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 根据负载均衡算法,从可以的服务列表中选出一个服务
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server));
// 得到服务后,最终执行请求
return execute(serviceId, ribbonServer, request);
}
获取可用服务列表、负载均衡算法这里就不讲解了,有兴趣的同学可以自己去看看 (*^▽^*)
最新文章
- maven dependencies 里面的包怎么导出
- android开子线程避免出现main错误
- linux内存回收 内核参数
- php请求URL中的参数有空格
- 第一部分 CLR基础:第2章 生成、打包、部署和管理应用程序及类型
- 多个MapReduce作业相互依赖时,使用JobControl进行管理
- php中json_decode()和json_encode()
- 《python基础教程》笔记之 字典
- Abstract Factory模式的几个要点
- (转)iOS7界面设计规范(2) - UI基础 - iOS应用解析
- Django的痛点
- Ubuntu中搭建强化学习平台(使用anaconda管理Python并安装tensorflow、opencv)
- 多重背包--java
- 20145308 《网络对抗》 MAL_免杀原理及实践 学习总结
- 力扣(LeetCode) 852. 山脉数组的峰顶索引
- Win7 VS2015环境编译Libpng
- 数据库的ACID
- day5模块学习 -- os模块学习
- LCTF wp简单复现
- jQuery给控件赋值....
热门文章
- MySQL之架构简单分析
- IOR and mdtest - measure parallel file system I/O performance at both the POSIX and MPI-IO level.
- 初级模拟电路:3-2 BJT的工作原理
- oracle xmltype + blob + clob
- day94_11_26爬虫find与findall
- matlab键盘快捷键无法使用的解决办法
- 【oracle】表和索引建立在不用表空间原因
- Python程序练习题(一)
- 使用OC实现单链表:创建、删除、插入、查询、遍历、反转、合并、判断相交、求成环入口
- 【Linux命令】工作目录切换命令(pwd,cd,ls)