dubbo 中层次的学习—简易rpc实现

​ 很多人和我一样,在平时的工作中对于dubbo只是停留于使用阶段。甚至很多时候,必须要新开放一个rpc接口,或者新引入一个rpc接口都是要抄之前的配置(所有参数照抄,只是把接口名改一下,然后把id改一下)。其实冷静下来想想,我们正在慢慢朝着刚毕业时最鄙视的人(浑浑噩噩的码农)转变了。

​ 我最近跟公司的另外一个小伙伴一起在吃dubbo。他吃完了之后都做了笔记,而我可能看的源码比较少,吃起来很累,而且可能也没有那么深刻。不过毕竟这是我第一个吃的比较透的rpc框架,也希望能够留下一些文字,写一下我吃的整个过程。没准能给跟我一样的小菜鸡们带来一点思路,哈哈哈哈。加油!

rpc的最基础实现

​ 大家都知道dubbo是个rpc框架,那么rpc框架最基础的实现是什么呢?在这里大家可以先思考一下,如何搭建一个最最简单的rpc框架?

​ 我们使用过dubbo的同学应该知道,如果A要调用B的方法,那么必然是依赖了B的包(至少是api包,否则我怎么知道要调用B的哪个方法?其实也可以不依赖,这个在后面讲)。所以最最简单的rpc框架应该是A告诉B,我要执行你的哪个哪个方法,然后你执行完之后把结果再告诉我。那么这个方法执行的基础信息是什么呢?

方法执行的基础信息

​ 如果从正向调用方法的逻辑去思考,可能说不清楚到底执行一个方法需要哪些东西。于是我们可以通过反射调用的方式来思考这个问题。

  @Test
    public void test() {
        try {
            Class<?> greetingService = Class.forName("org.apache.dubbo.common.extension.ExtensionTest$GreetingService");
            Class<?>[] parameterTypes = new Class[]{String.class, String.class};
            Method sayHello = greetingService.getMethod("sayHello", parameterTypes);
            Object[] parameterObjects = new Object[]{"tom", "18"};
            Object result = sayHello.invoke(greetingService.newInstance(), parameterObjects);
            System.out.println(result);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    static class GreetingService {

        public String sayHello(String name) {
            return "hello " + name;
        }

        public String sayHello(String name, String age) {
            return "hello " + name + ":" + age;
        }
    }
}

​ 通过类名方法名参数类型数组(区别方法重载),我们可以确定唯一的一个方法。而当我们需要调用这个方法的时候,就需要把具体的参数传进去。

​ 我上面写的代码显示了如何用一个类(消费者)反射调用另一个类(提供者)的方法。虽然用的是单元测试的代码,相信大家也能够理解。不理解的,我觉得。。。这篇文章不适合你。。哈哈哈哈哈

​ 我们回到rpc的环节。这时候我们再继续思考,最简单的rpc框架就应该是对于上面代码的扩展。而上面是单机版,我们只要把它做成分布式的,岂不是就成了?没错!我们顺着这个思路,很自然而然的就能得到我们下面一步要做的事情——把消费者和提供者分离开,两边通过网络传输的方式进行交流。

socket加入

​ 说实话我这里不知道应不应该大段贴代码,贴太多代码的话确实会影响观感。。。因为socket这一块并不是本文要重点分析的,所以我考虑以后决定只贴一些关键代码。本文最后会把我的git地址贴上,大家有兴趣的话可以直接把代码clone下来看。

消费者
	@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Socket socket = new Socket(host, port);
        OutputStream outputStream = socket.getOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

        Class<?>[] paramTypes = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            paramTypes[i] = args[i].getClass();
        }
        //注意proxy已经是代理类了 所以第一个参数要用method.getDeclaringClass().getName()
        ServiceMetadata serviceMetadata = new ServiceMetadata(method.getDeclaringClass().getName(), method.getName(), paramTypes, args);
        objectOutputStream.writeObject(serviceMetadata);
        return getResultFromRemote(socket);
    }


    /**
     * 从远程服务获取方法调用结果
     *
     * @param socket 通信socket
     * @return 调用结果
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private Object getResultFromRemote(Socket socket) throws IOException, ClassNotFoundException {
        InputStream inputStream = socket.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        return objectInputStream.readObject();
    }
服务提供者
  /**
     * 开始监听
     */
    public void doListen() throws IOException, ClassNotFoundException {
        Integer port = applicationRpcPort == null ? Integer.valueOf(20880) : applicationRpcPort;
        ServerSocket serverSocket = new ServerSocket(port);
        keepListening = new AtomicBoolean(true);
        while (keepListening.get()) {
            Socket accept = serverSocket.accept();
            System.out.println("接收到消息");
            InputStream inputStream = accept.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            Object readObject = objectInputStream.readObject();
            if (readObject instanceof ServiceMetadata) {
                //当监听到一个消费者请求 放到线程池里面继续操作
                threadPoolExecutor.submit(() -> {
                    Object result = RpcServiceInvoker.doInvoke((ServiceMetadata) readObject);
                    try {
                        OutputStream outputStream = accept.getOutputStream();
                        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
                        objectOutputStream.writeObject(result);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
        threadPoolExecutor.shutdown();
    }

​ 讲到这里,我这边会建议大家开始动手了! 真的很重要,如果不动手光看的话,感觉是不一样的。大家应该能写出一个最简单的rpc框架了。

框架的启动

​ 我们在上一节写好的框架,只能通过main()或者单元测试调用。我们的下一步就是需要把我们的框架嵌入到web应用中,做到服务启动,我们的项目就随之启动(跟dubbo一样)。

引入spring

​ 考虑到我们绝大多数的后端服务都会集成spring这个老大哥,而且引入spring之后也会方便我们直接通过web请求验证我们的结果。所以我们可以通过引入spring-boot-starter-web把consumer做成一个controller,然后把provider就藏在另一个tomcat项目里面。

​ 接下去我们要做的事情,就是把从provider端引入的接口通过代理的方式,走远程调用而不走接口直接调用(直接调用会报错的,因为在consumer端只能拿到接口,拿不到实现)。

consumer端步骤如下:

  1. 通过自定义注解的方式找到哪些是需要rpc调用的请求,通过注解的好处是,你可以通过反射得到被注解的所有信息(也就是我们上面提到的服务调用基础信息);
  2. 把引入的接口做代理(java的原生代理,InvocationHandler),然后把代理对象设置到原对象中;
  3. 在自己实现的代理类里面,写上上面的socket调用逻辑。

provider端步骤如下:

  1. 在项目启动的时候,把我们的监听打开就行了。

什么?你不知道项目启动的时候调用自己的程序怎么写? spring的扩展千万种,总有一种适合你~~

总结

​ 写到这里,我们的rpc框架雏形已经出来了。而且这个也是真实可以使用的框架。虽然在本文里面没有讲关于dubbo的东西,但是相信如果大家能完整看下来然后写下来的话,一定是会有启发的。(如果你是大神,当我没说过哈~)

​ 最后附上我这个项目的地址https://gitee.com/hanochMa/rpc-study.git 。大家记得把分支切到v1.0。

在v1.0里面的代码和本文所分析的代码,不同点在于v1.0里面的代码做了很多对象的封装,所以里面的类会比想象中的多那么一点点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值