美文网首页
听说JavaGuide写了一个不错的rpc,特来围观!

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

作者: 程序员ken | 来源:发表于2021-04-22 18:14 被阅读0次

    前言: 之前看到了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,程序之路,让我们一起探索,共同进步。

    相关文章

      网友评论

          本文标题:听说JavaGuide写了一个不错的rpc,特来围观!

          本文链接:https://www.haomeiwen.com/subject/xuyfrltx.html