【深入理解SpringCloud微服务】深入理解微服务中的远程调用,并手写一个微服务RPC框架
远程过程调用
远程过程调用简称RPC,也就是本地调用远程服务器上的方法。比如本地调用UserService的getUser(String userId)方法,经过网络通信,调用到远程服务器上的UserServiceImpl的getUser(String userId)方法。
一般的RPC框架,会屏蔽掉网络通信的细节,通过动态代理返回一个代理对象,我们调用这个代理对象,RPC框架就会帮我们向服务器发送RPC请求。
然后服务器那一端接收到RPC请求后,还要解析出接口类型、方法、参数等信息,然后调用对象的实现类进行处理。
如果一个接口有多个提供者提供服务,那么客户端的RPC框架还要处理负载均衡的逻辑。
上面说到的只是RPC中的服务调用流程,在此之前,还有服务暴露(或者叫服务注册)和服务引入(或者叫服务发现)两个过程。比如服务暴露就是把服务提供方的ip地址端口号,以及相关的接口信息发布到注册中心。而服务引入则是从注册中心把服务提供者发布上去的信息拉取到本地,根据该信息生成对应接口的代理对象。
以上这些,都是作为一个RPC框架需要实现的,可以看出来实现一个完整RPC框架还是比较复杂的。而我们作为使用者只要做相应的配置,既可以像调用本地方法一样调用远程服务。
微服务中的RPC框架
比起实现一个完整的RPC框架(比如Duboo),实现一个微服务中的RPC框架则要简单的多。由于微服务本身自带了注册中心和注册中心客户端等组件,服务暴露和服务引入就不需要RPC框架的实现者去考虑了。并且微服务还自带了负载均衡(比如Ribbon),RPC框架的实现者也可以复用,无需重复造轮子。
因此作为微服务中的RPC框架,只需要扫描接口生成代理对象,然后代理对象中复用微服务的负载均衡器即可。
如何实现一个微服务中的RPC框架
接口扫描
接口扫描这一步要做的事情就是扫描出所有需要生成代理对象的接口,这些接口在我们本地没有实现类,都是通过代理对象调用底层的远程调用逻辑请求到远程服务器上的接口实现类。
我们可以定义一个注解(比如OpenFeign的@FeignClient注解),注解需要指定服务名(表示这个接口是请求哪个微服务的),然后通过Spring的ClassPathBeanDefinitionScanner扫描我们自定义的注解修饰的接口,然后生成BeanDefinition,注册到Spring容器中。
生成代理对象
由于要根据扫描到的接口生成代理对象,因此扫描接口时生成的BeanDefinition的beanClass属性需要设置为Spring提供的FactoryBean类型。但是FactoryBean是一个接口,因此我们要实现自己的FactoryBean实现类,重写FactoryBean的getObject()方法,getObject()方法生成接口的代理对象,我们可以使用JDK动态代理。
代理对象处理逻辑
如果我们使用的是JDK动态代理,那么就会进入我们实现的InvocationHandler的handle()方法。
首先要解析修饰接口的自定义注解,获取注解上的服务名。
除服务名外,我们还要有请求的url,因此一般接口方法上也要有注解,我们们可以复用SpringMVC的注解(@RequestMapping、@GetMapping等),也可以自己重写定义一套。
获取到服务名后,然后要读取接口方法上的注解,解析注解上的url,从注解解析出来的url前面拼接上修饰接口的自定义注解的服务名,就形成了完成的请求地址。
得到请求地址后,我们复用负载均衡微服组件进行客户端负载均衡,然后通过RestTemplate或者OkHttp等其他的http工具发出http请求即可。
手写一个微服务RPC框架
@RPCClient
@RPCClient是自定义的用于修饰接口的注解,功能相当于OpenFegin的@FeignClient,用于被扫描并生成代理对象。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE})
public @interface RPCClient {
String serviceName() default "";
}
@EnableRPCClient
@EnableRPCClient注解也是自定义注解,功能与OpenFeign的@EnableFeignClients注解类型。用于修饰启动类,表示启动微服务RPC功能。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE})
@Import({
MicroServiceRPCClientRegistrar.class})
public @interface EnableRPCClient {
String[] scanBasePackages() default {
};
}
scanBasePackages属性指定了包扫描路径,@Import引入了一个MicroServiceRPCClientRegistrar,由MicroServiceRPCClientRegistrar进行包扫描并生成BeanDefinition。
MicroServiceRPCClientRegistrar
MicroServiceRPCClientRegistrar 实现了Spring的ImportBeanDefinitionRegistrar接口,并重写了registerBeanDefinitions方法。在registerBeanDefinitions方法中进行包扫描。
public class MicroServiceRPCClientRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware,
ResourceLoaderAware {
private Environment environment;
private ResourceLoader resourceLoader;
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 创建包扫描使用的scanner
ClassPathBeanDefinitionScanner scanner = new RPCClientBeanDefinitionScanner(registry, false, environment, resourceLoader);
// 设置扫描@RPCClient注解
scanner.addIncludeFilter(new AnnotationTypeFilter(RPCClient.class));
// 解析@EnableRPCClient注解