在使用RPC过程中,经常会调用多个RPC服务来获取数据。

比如获取用户个人主页的时候,就要去用户服务获取用户的头像、昵称等基本信息,还要去访问服务获取用户最近访问的信息,还有去关系服务获取用户新增的粉丝或关注的主播的消息。

值得注意的是,这些服务之间并没有依赖,即并不需要先获取用户的头像才能获取用户的访问信息。

如果一个一个地调用这些RPC服务,那么消耗的总时间就是这些RPC调用耗时之和。

因为这些服务之间并没有依赖,所以可以并行地调用,那么消耗的总时间就是耗时最长的那个RPC调用的时间。特别是当调用的RPC服务众多时,并且调用可以大幅度减少时间,提高效率。

如何并行调用呢?

采用线程池和Future就可以,样例如下。
进行两个计算任务,每个需要耗时3秒,如果按照往常调用,那么总共需要6秒时间。但如果并行调用,只需3秒。

import java.time.LocalDateTime;
import java.util.concurrent.*;

public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {


        ExecutorService executorService =
                Executors.newFixedThreadPool(4);

        Callable<Integer> calculateA = new SumCalculateTask(3, 4);
        Callable<Integer> calculateB = new SumCalculateTask(4, 6);

        System.out.println("START AT " + LocalDateTime.now());
        Future<Integer> futureA = executorService.submit(calculateA);
        Future<Integer> futureB = executorService.submit(calculateB);

        Integer resultA = futureA.get(4, TimeUnit.SECONDS);
        System.out.println(resultA);
        Integer resultB = futureB.get(4, TimeUnit.SECONDS);
        System.out.println(resultB);

        System.out.println("END AT " + LocalDateTime.now());
        executorService.shutdown();
    }


    static class SumCalculateTask implements Callable<Integer> {

        private Integer a;
        private Integer b;

        public SumCalculateTask(Integer a, Integer b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public Integer call() throws Exception {
            Thread.sleep(3000);
            return a + b;
        }
    }
}

Github源码示例:https://github.com/chengjf/java-snippet/blob/master/src/main/java/com/chengjf/future/ConcurrentCalculatorTest.java

其实我自己一直想搭建这么一个工具,可以放一些常用的小工具,比如Json字符串美化,亦或者是查询今日天气,更或者是Base64之类的编解码。

最初我的想法是整一个客户端出来,那时候我看了AWT、Swing,后面有了JavaFX,但这个东西写起来实在麻烦就放弃了。

接下来想用Android来整,有了这个项目SharpPM25SharpWeather,但后来实在觉得丑,也放弃了。

也用Python写过一个,database-interface-doc-management,用的是flask和emberjs。

也用Go写过一个,mymoney

但是都好景不长,基本上是写了一个能跑起来的demo后就没管了。

最近工作时,又发现了经常使用这些东西,特别是查看Json。所以这个想法又涌现出来了。
我基本上是非常不愿意写前端代码的,那些样式、兼容性之类的东西使我很烦。所以我找了一个框架vaadin来写第一版的helper,先用的是vaadin10。但是这个vaadin10太新了,以至于safari和ie的支持都不算太好,我跑起来后就放弃了。

接下来有了这个helper-vue,使用spring-boot和vue。这个spring-boot就是现在工作使用的,vue以前就写过一点。这样项目就搭建起来了,虽然页面样式什么的丑了一点,但是不在乎么,只希望以后不要荒废这个项目。

所以我开了个子域名https://helper.chengjf.com给这个项目。

Java8中推出了Optional这个class,用来做null check,我在实际工作中也经常使用,确实方便很多,不过也遇到了不少坑,现在写出来供大家参考。

0x01 这些情况不要使用Optional

  1. 集合类不要包装为Optional。集合类永远必须返回集合,不允许返回null值,即使如果没有数据,也要返回空集合。返回空集合避免了NPE,特别是使用stream接口处理起来非常方便。
  2. POJO的属性不要使用Optional。Optional不要作为class的属性,如果要对空属性进行处理,总是应该在对应属性的getter方法上做。同时由于Optional没有实现Serializable接口,所以这些类在序列化的时候也非常麻烦。
  3. RPC服务的方法返回值和参数不要使用Optional。正如第二点所说,Optional不能正常序列化,所以使用RPC服务时,如果Optional作为参数或者返回值进行调用的时候,序列化失败就会报错。(但是如果方法返回值不能返回Optional对象,而返回null的话又失去了Optional存在的意义。SO上也有Optional为啥不实现Serializable接口的讨论,地址在此stackoverflow

- 阅读剩余部分 -

序列化和反序列化是RPC中非常重要的部分了,也是RPC中必不可少的一部分。

  • 序列化-即将调用的对象转换成可传输的字节序列
  • 反序列化-即将接收到的字节序列转换成对象

当我们使用RPC调用一个方法时,先将我们调用的方法和参数进行序列化,将序列化后的字节序列传输到RPC服务提供者。
当RPC服务提供者收到请求后,进行反序列化将收到的字节序列转换成具体的方法和参数,然后再去执行对应的方法。接着将执行后的结果进行序列化,再传输给调用方。
调用方收到响应的字节序列后,将数据进行反序列化,就得到了调用结果对象。

一次普通的RPC调用,至少要经过上面的两次序列后和两次反序列化。

序列化和反序列化需要一中序列化规范,即序列化前的对象和序列化后的字节之间的一种对应关系。常见的序列化,如json,hessian2,protobuf等。Java中,最基本的就是java自带的Serializable接口了。

由于json是一种可读的文本协议,所以常用于HTTP中。而hessian2和protobuf都是一个二进制协议,序列化后字节序列更小,所以传输速度快,因此这两种协议常用于RPC中序列化和反序列化。



- 阅读剩余部分 -