众所周知springcloud ribbon 是用来做负载均衡的。那么它是怎么做到使restTemplate拥有负载均衡呢?带着这个疑问咱们来源码分析。
本文会详细介绍阅读源码的方式。如果从多实现中找到具体实现。(非idea断点方式)
demo 配置文件
server:
port: 8081
spring:
application:
name: spring-cloud-order-service
# 没有依赖eureka注册中心 使用配置文件配置server
spring-cloud-user-service:
ribbon:
listOfServers: localhost:8082,localhost:8083
远程调用demo
@Bean
@LoadBalanced //负载均衡注解
public RestTemplate restTemplate(RestTemplateBuilderrestTemplateBuilder) {
return restTemplateBuilder.build();
}
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/test")
public Object test(){
return restTemplate.getForObject("http://spring-cloud-ser-service/getUserInfo",String.class);
}
上述代码可以看到在注册restTemplate时加上了@LoadBalanced,那么LoadBalanced的作用是什么呢?
/**
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a
* LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({
ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
我们可以看到LoadBalanced注解其实是继承了spring注解的Qualifier。
Qualifier相当于一个标记,它可以将所有被Qualifier标记的bean统一管理。有兴趣的同学可以深入研究一下。这里不做具体阐述
LoadBalanced这个注解在哪里用到的呢?让我们来看loadBalanced的自动装配类LoadBalancerAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = 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);
}
}
});
}
// 省略部分代码 ...
可以看到被LoadBalanced所标记的依赖注入是个列表。这个列表里所注入的是所有被@LoadBalanced标记的RestTemplate。
让我们思考一下,如果想让我们自己实现restTemplate拥有负载均衡,那么有什么方案呢?例如拦截器?是的。ribbon也是这么做的。让我们来分析初始化ribbonInterceptor。
还是在LoadBalancerAutoConfiguration我们发现有这样一个装配
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.suport.RetryTemplate")
static class LoadBalancerInterceptorConfig {
// 初始化LoadBalancerInterceptor
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return newLoadBalancerInterceptor(loadBalancerClient,requestFactory);
}
// 将所有restTemplate设置loadBalancerInterceptor
// 这块是RestTemplateCustomizer的匿名内部类
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptorloadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = newArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
这是LoadBalancerAutoConfiguration的内部类。
看到这里我们在反过来看刚刚留下伏笔的Bean
// ObjectProvider<List<RestTemplateCustomizer>> ObjectProvider是延迟加载 为了上述代码加载后这里再加载
@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);
}
}
});
}
restTemplateCustomizers.ifAvailable() 这个方法作用是如果找到这个RestTemplateCustomizer bean那么就执行其中的方法(lambda)
customizer.customize(restTemplate);这句话其实是调用的restTemplateCustomizer(final LoadBalancerInterceptorloadBalancerInterceptor)这个方法的匿名内部类。这里比较绕。
到这里自动装配基本就告一段落了。那么ribbonInterceptor配置完在哪里去用到呢?
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/test")
public Object test(){
return restTemplate.getForObject("http://spring-cloud-ser-service/getUserInfo",String.class);
}
让我们从RestTemplate.getForObject()方法入手
RestTemplate.getForObject() 会调用doExecute
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
// 创建request
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
// 执行获取响应结果
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
这个方法中有两个主线方法
了解设计模式的同学应该马上就能做出选择,没错是AbstractClientHttpRequest抽象类
@Override
public final ClientHttpResponse execute() throws IOException {
assertNotExecuted();
// 这里又是多个实现类
ClientHttpResponse result = executeInternal(this.headers);
this.executed = true;
return result;
}
// 模板方法
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput)
throws IOException;
上述代码中的executeInternal(this.headers);这块又是多个实现 如下:
那么具体用的是哪个呢?这回咱们在反过来看ClientHttpRequest request = createRequest(url, method);具体返回的是哪个实现类 如下:
org.springframework.http.client.support.HttpAccessor#createRequest
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
initialize(request);
if (logger.isDebugEnabled()) {
logger.debug("HTTP " + method.name() + " " + url);
}
return request;
}
// 省略部分代码 ...
getRequestFactory()调用的其实是HttpAccessor子类的InterceptingHttpAccessor中的getRequestFactory()
org.springframework.http.client.support.InterceptingHttpAccessor#getRequestFactory
private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
// 呼应LoadBalancerAutoConfiguration中的restTemplateCustomizer()方法
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
Assert.noNullElements(interceptors, "'interceptors' must not contain null elements");
// Take getInterceptors() List as-is when passed in here
if (this.interceptors != interceptors) {
this.interceptors.clear();
this.interceptors.addAll(interceptors);
AnnotationAwareOrderComparator.sort(this.interceptors);
}
}
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
// 因自动装配这里肯定不会空
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
// 实际创建的request对象(ClientHttpRequest子类)
// 将自动装配的拦截器放list入此类中后面的讲解会用到
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
// 省略部分代码
以上代码会发现看到了拦截器的影子。getInterceptors();现在我们回到createRequest()方法中。为了加深印象。下面的代码块是上一步骤
org.springframework.http.client.support.HttpAccessor#createRequest
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
// createRequest调用了AbstractClientHttpRequestFactoryWrapper.createRequest(url, method);
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
initialize(request);
if (logger.isDebugEnabled()) {
logger.debug("HTTP " + method.name() + " " + url);
}
return request;
}
// 省略部分代码 ...
注: 以上代码的createRequest方法又出现了多个实现类
同理先进AbstractClientHttpRequestFactoryWrapper.createRequest(url, method);
@Override
public final ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
// 模板方法InterceptingClientHttpRequestFactory.createRequest(uri, httpMethod, this.requestFactory);
return createRequest(uri, httpMethod, this.requestFactory);
}
上述代码其实就是调用了InterceptingClientHttpRequestFactory.createRequest(uri, httpMethod, this.requestFactory);
看到这里是不是感觉很绕?是的spring的源码确实很绕。但是看多了,就会找到技巧了。
好了让我们回到AbstractClientHttpRequest.execute();
@Override
public final ClientHttpResponse execute() throws IOException {
assertNotExecuted();
ClientHttpResponse result = executeInternal(this.headers);
this.executed = true;
return result;
}
上述代码executeInternal又出现了多个实现
同理我们还是先进入AbstractBufferingClientHttpRequest内部
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
byte[] bytes = this.bufferedOutput.toByteArray();
if (headers.getContentLength() < 0) {
headers.setContentLength(bytes.length);
}
ClientHttpResponse result = executeInternal(headers, bytes);
this.bufferedOutput = new ByteArrayOutputStream(0);
return result;
}
// 模板方法
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput)
throws IOException;
executeInternal() 方法会有以下实现类
InterceptingClientHttpRequest这个类有没有很熟悉?没错就是它。它就是createRequest返回的。所以我们进到InterceptingClientHttpRequest.executeInternal(headers, bytes);方法中
@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
return requestExecution.execute(this, bufferedOutput);
}
// InterceptingClientHttpRequest的内部类
private class InterceptingRequestExecution implements ClientHttpRequestExecution {
private final Iterator<ClientHttpRequestInterceptor> iterator;
public InterceptingRequestExecution() {
this.iterator = interceptors.iterator();
}
// 重点
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
// 将createRequest方法中放入的ClientHttpRequestInterceptor遍历
// 不为空执行拦截器
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
// 默认走此方法
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
}
上述重点代码我们会看到遍历后会执行nextInterceptor.intercept(request, body, this);实现类如下
会调用LoadBalancerInterceptor.intercept(request, body, this);为什么呢?
因为LoadBalancerAutoConfiguration自动装配装配的就是LoadBalancerInterceptor。大家可以回顾一下。
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,
this.requestFactory.createRequest(request, body, execution));
}
以上代码重点在this.loadBalancer.execute方法我们直接看
org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute(java.lang.String, org.springframework.cloud.client.loadbalancer.LoadBalancerRequest, java.lang.Object)
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 负载均衡(重点)
Server server = getServer(loadBalancer, hint);
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就是用负载均衡规则来获取服务地址的。
我们直接来看com.netflix.loadbalancer.BaseLoadBalancer#chooseServer
private final static IRule DEFAULT_RULE = new RoundRobinRule();
protected IRule rule = DEFAULT_RULE;
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
// 根据不同规则获取
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
上述代码可以看出来默认使用RoundRobinRule轮训规则进行负载
那么它是怎么知道所以注册的机器呢?
public class BaseLoadBalancer extends AbstractLoadBalancer implements
PrimeConnections.PrimeConnectionListener, IClientConfigAware {
private static Logger logger = LoggerFactory
.getLogger(BaseLoadBalancer.class);
private final static IRule DEFAULT_RULE = new RoundRobinRule();
private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
private static final String DEFAULT_NAME = "default";
private static final String PREFIX = "LoadBalancer_";
protected IRule rule = DEFAULT_RULE;
protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY;
protected IPing ping = null;
// 监听
@Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> allServerList = Collections
.synchronizedList(new ArrayList<Server>());
// 监听
@Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL)
protected volatile List<Server> upServerList = Collections
.synchronizedList(new ArrayList<Server>());
// 部分代码 ...
上述代码中的两个监听证明了有多少机器是UpServerList和全部机器allServerList
至于如何监听本章节不做阐述。留在注册中心eureka中做说明。
LoadBalancerRequest.intercept()
public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request,
final byte[] body, final AsyncClientHttpRequestExecution execution)
throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName,
new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
@Override
public ListenableFuture<ClientHttpResponse> apply(
final ServiceInstance instance) throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request,
instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
return execution.executeAsync(serviceRequest, body);
}
});
}
通过以上讲解我们发现其实ribbon就是用的拦截器对restTemplate做了支撑。如果有拦截器。那么就会执行拦截器中的逻辑。如果没有那么就会走正常逻辑(无负载等)下面我为大家整理一下主线流程
文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大
文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码
文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版
文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗
文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程
文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0
文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader
文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型
文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写
文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录
文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点
文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文