听说JavaGuide写了一个不错的rpc,特来围观!

376 阅读5分钟

前言: 之前看到了JavaGuide 写的一个rpc项目,拜读了一下文档,有点小缺陷就是没有项目运行的文档,在项目下面有部分小伙伴留言说,“项目运行不起来”。自己试了一下 运行其实做过微服务的人多多少少都知道怎么弄,但部分没怎么接触微服务架构的人就不知道怎么弄,此文章一方面是作为JavaGuide的运行说明,主要的还是从底层研究下 这个rpc功能 具体是怎么实现的,为什么服务能够被注册 被发现并调用呢,当然同时你还巩固了一下spring基础的知识。

1. 关于项目

技术: 反射(如注解式注入)、jdk动态代理 、序列化(Kryo 序列化) 、 中间件(Zookeeper) Map缓存 、 Netty(通信技术)、消息编码器/解码器 、

机制: SPI机制

其他: Curator操作zookeeper

2. 项目运行

2.1. 使用 Edit Configurations 新建两个Application

分别命名为Server_Netty_Application 和 Client_Netty_Application。主类分别为example-server文件夹下的NettyServerMain类,以及example-client文件夹下的NettyClientMain(这样看起来比较直观而已)

file file file

2.2. 其他说明:

先开启zookeeper ,再一次启动Server_Netty_Application 和 Client_Netty_Application。 建议zookeeper 版本在3.5以上,貌似Curator对3.5以下版本的兼容性不是很好,之前我用的是3.4版本 直接报错。

3项目代码

3.1 服务端启动类代码:
@RpcScan(basePackage = {"github.javaguide"})
public class NettyServerMain {
    public static void main(String[] args) {
         // Register service via annotation
        // 通过注释 注册服务
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(NettyServerMain.class);
        NettyRpcServer nettyRpcServer = (NettyRpcServer) applicationContext.getBean("nettyRpcServer");
        // 可以不用手动注册服务
        // Register service manually
        //手动注册服务
/* HelloService helloService2 = new HelloServiceImpl2();
        //了解@lomlock注解的用法
        RpcServiceProperties rpcServiceProperties = RpcServiceProperties.builder()
                .group("test2").version("version2").build();
        nettyRpcServer.registerService(helloService2, rpcServiceProperties);*/
        nettyRpcServer.start();
    }
}
3.2 客户端启动代码:
package github.javaguide;
import github.javaguide.annotation.RpcScan;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author shuang.kou
 * @createTime 2020年05月10日 07:25:00
 */
@RpcScan(basePackage = {"github.javaguide"})
public class NettyClientMain {
    public static void main(String[] args) throws InterruptedException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(NettyClientMain.class);
        HelloController helloController = (HelloController) applicationContext.getBean("helloController");
        helloController.test();
    }
}
3.3 代码分析:
3.3.1 分析相关的“SPi类”等 注解式 注入是怎么实现的 ,服务是如何注册和发现的

观察两端代码有两处公共的部分,一是类上面都有个@RpcScan的注解,二是都用到了 Spring的注解式 注入 的方式new AnnotationConfigApplicationContext(Class);

下面跟随AnnotationConfigApplicationContext的源码,找到服务是怎么被注册的?

这就在AnnotationConfigApplicationContext类里面了,如下图

file

调用invokeBeanFactoryPostProcessors方法前的 beanFactory

file

调用 invokeBeanFactoryPostProcessors方法,主要作用实例化并调用所有已注册的 BeanFactoryPostProcessor. 这方法里面会调用CustomScannerRegistrar的相关方法,进行注解式注入并生成BeanDefinition,并将其追加到beanFactory容器中,如果你不信,可以试着把registerBeanDefinitions方法的内容置空,看看调用完invokeBeanFactoryPostProcessors方法的中beanFactory中的BeanDefinition内容,下面是注解式生成了相关的BeanDefinition的截图:

file

在刚开始的beanDefinition里面 只有一个 nettyServerMain 主类

ConfigurationClassParser类 doProcessConfigurationClass方法

file

processImports 里面有一行代码

ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);

他需要实现的接口为 ImportBeanDefinitionRegistrar

file

然后执行了 invokeAwareMethods方法:(意义是调用“Aware接口”的方法)

说明该类需要实现 父接口是Aware的接口

file

ConfigurationClassBeanDefinitionReader类里面的方法:loadBeanDefinitionsForConfigurationClass

调用 registerBeanDefinitions方法

file

从上面可以看到 首先会CustomScannerRegistrar 会依次调用setResourceLoader方法和 registerBeanDefinitions方法,setResourceLoader方法是获取到ResourceLoader,registerBeanDefinitions方法是注入了 使用了RpcService和Component的接口/类 统统被自动注入了

SpringBeanPostProcessor 实现BeanPostProcessor接口

SpringBeanPostProcessor方法里面 手动注入了这两个成员:

ServiceProvider serviceProvider; RpcRequestTransport rpcClient;

postProcessBeforeInitialization方法:

获取了version和group,服务提供者上线==》serviceProvider.publishService(bean, rpcServiceProperties),并把服务链 进行存入缓存(Map,可以是this.getServiceName() + this.getGroup() + this.getVersion())

如上图 this.registerBeanPostProcessors(beanFactory);调用这个方法是生成所有的BeanPostProcessor,如项目中实现BeanPostProcessor的类SpringBeanPostProcessor里面两个方法 postProcessBeforeInitialization 和 postProcessAfterInitialization 就会被先后调用。

SpringBeanPostProcessor的构造器后被调用,因为被注入了 这个方法 主要是创建单例的 服务提供者 和创建 RpcRequestTransport(作用是发送请求)

postProcessBeforeInitialization方法

在 ExtensionLoader的类方法里面 作者使用SPI的方式(不太理解这块的同学 可以网上搜一下),位置唯一resources 的META-INF下面 文件名为接口的全路径 里面为key和对应的接口实现,【key的不同 代表接口的实现不同而已】 file

使用了 SPI的接口分别为 RpcRequestTransport 、ServiceRegistry、ServiceProvider、LoadBalance 。

所有的“SPI类”经此都实例化了。

//SPI类实例化顺序 ZkServiceRegistry 》 NettyRpcClient 》ZkServiceDiscovery 》 ConsistentHashLoadBalance // 分别是 zookeeper注册器 NettyRpcClient zk服务发现 负载均衡器

file

file

例如 RpcRequestTransport serviceDiscovery

postProcessBeforeInitialization 方法的作用 其实 就是生产者提供服务接口,满足带有注解RpcService的”生产者”,这里面就是保存了所有注册服务【服务链】,并把其注册到zookeeper上去(实际操作是在zookeeper上添加节点,父节点为my-rpc代表是rpc服务,二级节点是服务地址包含group和version ,三级节点是服务主机和端口号)。

postProcessAfterInitialization方法

postProcessAfterInitialization 这个方法在这里就是 取获取引用RpcReference的字段 ,并把通过JDK动态代理生成该对象,并把代理对象赋予给该字段。后面调用时,直接走项目中的RpcClientProxy类,因为该实现了InvocationHandler接口,最终也是 根据上面提到的postProcessBeforeInitialization方法 的serviceProvider 取出对应的接口的实现类, 这个根据需求 整个项目也只有充当“消费者”的 example-client中才使用了,这也更明确了这方是调用方/消费者了。

file

最后文章若有不足,请指正。

欢迎关注我的公众号:程序员ken,程序之路,让我们一起探索,共同进步。