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@LoadBalancedpublic 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 {@Autowiredprivate 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 {@Beanpublic LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient,LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);}@Bean@ConditionalOnMissingBeanpublic 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来找到对应的服务器去执行请求,后面方法太多,就不一 一叙述,最后发现,最底层调用的还是原生的请求去实现调用的。