【微服务】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来找到对应的服务器去执行请求,后面方法太多,就不一 一叙述,最后发现,最底层调用的还是原生的请求去实现调用的。

相关推荐
已读不回1432 小时前
告别痛苦的主题切换!用一个插件解决 Tailwind CSS 多主题开发的所有烦恼
前端·架构
架构师沉默2 小时前
架构师的秘密武器:Java SPI 插件机制解密
java·后端·架构
云布道师3 小时前
AI时代下阿里云基础设施的稳定性架构揭秘
人工智能·阿里云·架构
Wgllss19 小时前
Kotlin 享元设计模式详解 和对象池及在内存优化中的几种案例和应用场景
android·架构·android jetpack
程序员不迷路1 天前
微服务学习
微服务·架构
fanly111 天前
使用surging 常见的几个问题
微服务·surging
Sadsvit1 天前
源码编译安装LAMP架构并部署WordPress(CentOS 7)
linux·运维·服务器·架构·centos
得物技术1 天前
营销会场预览直通车实践|得物技术
后端·架构·测试