当前位置:网站首页>微服务实战|负载均衡组件及源码分析
微服务实战|负载均衡组件及源码分析
2022-07-02 06:33:00 【_时光煮雨】
前言
上一篇文章中我们通过自己开发了一个负载均衡组件,实现了随机算法的负载均衡功能,如果要实现其他算法,还需要修改代码增加相应的功能。这一篇文章,我们将介绍一个更简单的负载均衡实现,使用**@LoadBalanced**注解实现负载均衡的功能。
项目实战
创建项目
同样的,我们的项目现在依然有一个registry注册中心,一个provider服务提供者,接下来,我们再次修改一下consumer服务消费者的代码:
/** * @Author:公众号:程序员965 * @create 2022-06-06 **/
@EnableEurekaClient
@SpringBootApplication
@RestController
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@GetMapping("/hello2")
public String hello2(String name) {
String returnInfo = restTemplate.getForObject( "http://provider/hello?name={1}", String.class, name);
return returnInfo;
}
}
在这个版本,同样的,还是创建RestTemplate Bean对象,不同的是上面仅仅增加了@LoadBalanced注解。
启动项目验证

依然正确返回了结果!
太神奇了吧,比我们自己开发的负载均衡组件简单太多了吧,仅仅在restTemplate() 方法上面增加了一个@LoadBalanced注解,怎么就实现的呢?废话不说,为了一探究竟,扒一扒源码吧!
源码分析
首先,点击@LoadBalanced注解进去,没有什么特别之处,那么我们在想想,Spring在创建Bean实例的时候,注解在什么地方起了作用?什么?不知道?翻一下这篇文章吧:
通过回顾Spring启动以及Bean的生命周期创建过程,我们就会发现加上@LoadBalancer注解后,项目启动时就会加载LoadBalancerAutoConfiguration这个配置类(通过spring-cloud-commons包下面的的spring.factories)。通过查看该配置类源码,发现其有个静态内部类LoadBalancerInterceptorConfig,其内部又创建了一个负载均衡拦截器:LoadBalancerInterceptor,该拦截器包含有一个loadBalancerClient参数:
@ConditionalOnMissingClass({
"org.springframework.retry.support.RetryTemplate"})
static class LoadBalancerInterceptorConfig {
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);
};
}
}
我们继续点击LoadBalancerInterceptor类进入,发现intercept方法,该方法中调用了LoadBalancerClient的execute方法,
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
LoadBalancerClient是一个接口,点击进去我们发现其实现类是RibbonLoadBalancerClient,查看其继承关系:
通过接口中的方法名称,我们可以猜想,choose方法就是选择其中服务列表中其中一个服务,reconstructURI方法就是重新构造请求的URI。
选择服务
choose方法是在RibbonLoadBalancerClient实现类中实现的
public ServiceInstance choose(String serviceId, Object hint) {
Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
}
在这个方法中,先调用 getServer 方法获取服务,这个方法最终会调用 ILoadBalancer 接口的 chooseServer 方法,而 ILoadBalancer 接口的实现类默认是ZoneAwareLoadBalancer。
ZoneAwareLoadBalancer 继承自 DynamicServerListLoadBalancer ,而在 DynamicServerListLoadBalancer 的构造方法中,调用了 this.restOfInit(clientConfig);在restOfInit这个方法中,通过 this.updateListOfServers()来获取服务列表;
而在chooseServer ()方法中,就会根据负载均衡算法,选择其中一个服务并返回:
BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
地址替换
选择其中一个服务信息后,怎么将接口从 http://provider/hello 变为 http://localhost:8003/hello 呢?还记得上面我们说的reconstructURI方法吗?通过配置类LoadBalancerAutoConfiguration加载后,会注入LoadBalancerInterceptor拦截器,该拦截器会拦截我们的请求,并对请求地址进行处理,重构方法的具体实现在 LoadBalancerContext 类的 reconstructURIWithServer 方法中
public URI reconstructURIWithServer(Server server, URI original) {
String host = server.getHost();
int port = server.getPort();
String scheme = server.getScheme();
if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
return original;
} else {
if (scheme == null) {
scheme = original.getScheme();
}
if (scheme == null) {
scheme = (String)this.deriveSchemeAndPortFromPartialUri(original).first();
}
try {
StringBuilder sb = new StringBuilder();
sb.append(scheme).append("://");
if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
sb.append(original.getRawUserInfo()).append("@");
}
sb.append(host);
if (port >= 0) {
sb.append(":").append(port);
}
sb.append(original.getRawPath());
if (!Strings.isNullOrEmpty(original.getRawQuery())) {
sb.append("?").append(original.getRawQuery());
}
if (!Strings.isNullOrEmpty(original.getRawFragment())) {
sb.append("#").append(original.getRawFragment());
}
URI newURI = new URI(sb.toString());
return newURI;
} catch (URISyntaxException var8) {
throw new RuntimeException(var8);
}
}
}
可以看到该方法中,将原始的请求地址original,替换成了选取的服务的IP和端口。并最终调用该服务的接口方法。
看到这里,再想想我们上一章的内容,是不是有异曲同工之妙?
总结
通过添加@LoadBalanced注解,就及其简单的实现了负载均衡的功能,与其说是Ribbon的强大,不如说是Spring的强大,Spring在整个上下文创建过程中,在不同的时机开放了一个又一个的接口,这就为各种组件的继承提供了遍历,同时也进一步促进了Spring生态的快速发展。
边栏推荐
- 以字节跳动内部 Data Catalog 架构升级为例聊业务系统的性能优化
- Programmers with ten years of development experience tell you, what core competitiveness do you lack?
- History of Web Technology
- Jd.com interviewer asked: what is the difference between using on or where in the left join association table and conditions
- Servlet全解:继承关系、生命周期、容器和请求转发与重定向等
- Gocv split color channel
- Cloud computing in my eyes - PAAS (platform as a service)
- Sentinel reports failed to fetch metric connection timeout and connection rejection
- Synchronize files using unison
- Taking the upgrade of ByteDance internal data catalog architecture as an example, talk about the performance optimization of business system
猜你喜欢

Dix ans d'expérience dans le développement de programmeurs vous disent quelles compétences de base vous manquez encore?

C language implementation of mine sweeping game

机器学习实战:《美人鱼》属于爱情片还是动作片?KNN揭晓答案

以字节跳动内部 Data Catalog 架构升级为例聊业务系统的性能优化

Matplotlib剑客行——初相识Matplotlib

C language - Blue Bridge Cup - 7 segment code

Minecraft plug-in service opening
![[staff] the lines and spaces of the staff (the nth line and the nth space in the staff | the plus N line and the plus N space on the staff | the plus N line and the plus N space below the staff | the](/img/dc/c0ea188ef353ded86759dbe9b29df3.jpg)
[staff] the lines and spaces of the staff (the nth line and the nth space in the staff | the plus N line and the plus N space on the staff | the plus N line and the plus N space below the staff | the

Data type case of machine learning -- using data to distinguish men and women based on Naive Bayesian method
![[go practical basis] how to install and use gin](/img/0d/3e899bf69abf4e8cb7e6a0afa075a9.png)
[go practical basis] how to install and use gin
随机推荐
Synchronize files using unison
How to realize asynchronous programming in a synchronous way?
图像变换,转置
Redis sorted set data type API and application scenario analysis
Matplotlib剑客行——没有工具用代码也能画图的造型师
Oracle修改表空间名称以及数据文件
Oracle related statistics
"Redis source code series" learning and thinking about source code reading
gocv边界填充
NPOI 导出Word 字号对应
"Interview high frequency question" is 1.5/5 difficult, and the classic "prefix and + dichotomy" application question
Move a string of numbers backward in sequence
Find the node with the smallest value range in the linked list and move it to the front of the linked list
[staff] time mark and note duration (staff time mark | full note rest | half note rest | quarter note rest | eighth note rest | sixteenth note rest | thirty second note rest)
Complete solution of servlet: inheritance relationship, life cycle, container, request forwarding and redirection, etc
一篇详解带你再次重现《统计学习方法》——第二章、感知机模型
概率还不会的快看过来《统计学习方法》——第四章、朴素贝叶斯法
[go practical basis] how to bind and use URL parameters in gin
概念到方法,绝了《统计学习方法》——第三章、k近邻法
「Redis源码系列」关于源码阅读的学习与思考