【微服务】Ribbon的实现原理

1、场景:这里有两个服务,user-server和store-server

1.1、user服务

接口:

复制代码
package com.lkx.user.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * @Author: lkx
 * @Date: 2023/8/23/13:20
 * @Description:
 */
@RestController
public class UserController {

    @GetMapping("/testUser")
    public Object testUser() {
        return "HelloWorld";
    }
}

1.2、store服务

配置类:

复制代码
package com.lkx.store.config;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.client.RestTemplate;

/**
 * @Author: lkx
 * @Date: 2023/8/23/13:18
 * @Description:
 */
@SpringBootConfiguration
@ComponentScan(basePackages = {"com.lkx.store"})
public class AppConfig {


    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

接口:

复制代码
package com.lkx.store.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;


/**
 * @Author: lkx
 * @Date: 2023/8/23/13:20
 * @Description:
 */
@RestController
public class StoreController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/testStore")
    public Object testStore() {
    	 /**
         * 原生调用方式实际上存在一种问题
         * 每多一次新地址的调用 都要维护一个全地址 这样维护起来相对就比较复杂了
         *
         * Ribbon :这个组件实际上就是为了来做服务的发现的
         * Ribbon最终服务的调用还是使用的是  restTemplate
    	 */
    	// 原生调用
        return restTemplate.getForObject("http://192.168.230.1:8081/testUser",String.class);
        // Ribbon调用
//        return restTemplate.getForObject("http://user-server/testUser",String.class);
    }
}

代码中,store服务通过url(ip地址加端口/方法)去调用user服务的接口,这是原生的调用方式。可以成功调用。Ribbon调用通过服务名称也可成功.

2、Ribbon的实现原理。

解释:两个服务启动时,会将自己的服务名称,以及IP地址和端口注册到注册中心,当有服务调用时,就会拉取注册中心的注册表,里面包含了所有的注册信息。当我们使用Ribbon方式去调用其他服务时,ribbon的拦截器会拦截请求,然后根据拉取到的注册信息,去找到服务名称对应的ip地址和端口,然后重组请求地址,最后以原生的RestTemplate方式去实现服务的调用。

3、源码跟踪

配置类中,我们在设置Bean RestTemplate时,加了一个注解@LoadBalanced。这个注解尤为重要,它就是Ribbon实现原理最为重要的一环。

@LoadBalanced 源码:

复制代码
/*
 * Copyright 2013-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.client.loadbalancer;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation to mark a RestTemplate 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 {
}

这里的解释翻译是:标记一个RestTemplate bean来配置使用LoadBalancerClient。那么,这个LoadBalancerClient是什么呢?

复制代码
/*
 * Copyright 2013-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.client.loadbalancer;

import org.springframework.cloud.client.ServiceInstance;

import java.io.IOException;
import java.net.URI;

/**
 * Represents a client-side load balancer.
 * @author Spencer Gibb
 */
public interface LoadBalancerClient extends ServiceInstanceChooser {

	/**
	 * Executes 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> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * Executes 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> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

	/**
	 * Creates a proper URI with a real host and port for systems to utilize.
	 * Some systems use a URI with the logical service 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);
}

翻译一下注释:Represents a client-side load balancer.这是一个客户端负载平衡器。而@LoadBalanced注解标记的RestTemplate就是来配置这个平衡器的,这里我们就需要找到这个配置类,同文件夹下有一个LoadBalancerAutoConfiguration,给它的解释是功能区的自动配置(客户端负载平衡)

复制代码
	@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 restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}

而在这个配置类里就有我们的ribbon的拦截器设置了。然后进入LoadBalancerInterceptor拦截器,通过拦截器的执行代码,然后到RibbonLoadBalancerClient的execute方法,这个方法就是通过我们的key来找到对应的服务器去执行请求,后面方法太多,就不一 一叙述,最后发现,最底层调用的还是原生的请求去实现调用的。

相关推荐
龙山云仓26 分钟前
MES系统超融合架构
大数据·数据库·人工智能·sql·机器学习·架构·全文检索
未来龙皇小蓝37 分钟前
RBAC前端架构-02:集成Vue Router、Vuex和Axios实现基本认证实现
前端·vue.js·架构
Tadas-Gao41 分钟前
深度学习与机器学习的知识路径:从必要基石到独立范式
人工智能·深度学习·机器学习·架构·大模型·llm
啊森要自信1 小时前
CANN ops-cv:揭秘视觉算子的硬件感知优化与内存高效利用设计精髓
人工智能·深度学习·架构·transformer·cann
国强_dev1 小时前
轻量级实时数仓架构选型指南
架构
roman_日积跬步-终至千里1 小时前
【系统架构设计-综合题】计算机系统基础(1)
架构
瑶山2 小时前
Spring Cloud微服务搭建五、集成负载均衡,远程调用,熔断降级
spring cloud·微服务·负载均衡·远程调用·熔断降级
C澒2 小时前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
代码游侠2 小时前
复习——Linux设备驱动开发笔记
linux·arm开发·驱动开发·笔记·嵌入式硬件·架构