咸鱼回响

望之天回,即之云昏

0%

一次使用grpc的记录

最近在设计一个P2P分布式存储系统,网络路由方面采用的是Kalelima协议,在这个协议的实现中主要有四个RPCs:

  • Ping:节点发起ping请求,主要用来检测与其他节点网络连接是否通顺
  • Store:存储请求,参数为<key, value>键值对,key是value的SHA1值,value是要保存的数据。
  • FindNode:寻找节点,参数是一个节点SHA1值,返回的是一个三元组<ip, 端口, 节点id>集合
  • FindValue:寻找值,参数是一个文件的SHA1值,如果不存在该文件,则返回距离这个文件最近的一个三元组<ip, 端口, 节点id>集合,如果存在文件,则直接返回文件的数据内容 我对.proto的设计如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    syntax = "proto3";

    option java_multiple_files = true;
    option java_package = "com.dust.grpc.kalelima";
    option java_outer_classname = "KalelimaServiceProto";

    // 定义的一个服务
    service KalelimaService {
    // Ping函数
    rpc Ping(PingPackage) returns (PingPackage) {}

    // 存储
    rpc Store(StoreRequest) returns (StoreResponse) {}

    // 寻找节点
    rpc FindNode(FindRequest) returns (stream FindNodeResponse) {}

    // 寻找值
    rpc findValue(FindRequest) returns (stream FindValueResponse) {}
    }

    //ping函数发起的请求,携带发起者的id以及发起时间,收到ping之后的节点将id修改成自身id,时间不变原样返回
    message PingPackage {
    string nodeId = 1;
    int32 timestamp = 2;
    }

    //存储请求,携带有一个sha1编码的key和数据对象。其中key还是该对象的checksum
    message StoreRequest {
    string key = 1;
    bytes data = 2;
    }

    //存储响应,code = 1表示成功;code = 0表示失败。当code = 0的时候errmsg才会有值
    message StoreResponse {
    int32 code = 1;
    string errmsg = 2;
    }

    //寻找请求,key既可以表示节点id,又可以表示文件id。具体根据请求的接口来判断
    message FindRequest {
    string key = 1;
    }

    //寻找节点返回,返回的是一个三元组<IP地址、端口、节点id>列表
    message FindNodeResponse {
    string host = 1;
    int32 port = 2;
    string nodeId = 3;
    }

    //寻找文件呢返回,如果文件不存在则返回离这个文件最近的三元组<IP地址、端口、节点id>列表,否则直接返回值
    message FindValueResponse {
    string host = 1;
    int32 port = 2;
    string nodeId = 3;

    bytes data = 4;
    }

然后在生成代码的时候问题来了,这里官方会给出两种生成方式:1、使用protoc编译器生成;2、使用maven插件生成

一开始我觉得改xml麻烦就直接通过protoc生成,生成的结果让我有点疑惑:

我的Service呢?我这么大的一个Service呢?其中的KalelimaServiceProto并不是一个服务文件的接口,而是类似于描述文件的东西。

百度之后发现是缺少了一行命令option java_generic_services = true;加上后终于有Service了。

看着也没问题,是一个抽象类,其中定义了.proto文件中定义的抽象方法。好的那就去实现它吧!

实现之后问题来了,我该如何将这个服务注册在服务端中呢?查询了一些文档发现许多教程都是通过maven生成的代码,那我用maven生成代码中创建服务端的部分将这个Service注册进去可以吗?

不可以!虽然grpc有addService方法,但是他们两个传入的Service和protoc生成的Service完全不是一个接口:

1
2
3
4
5
6
7
8
9
public interface Service {
ServiceDescriptor getDescriptorForType();

void callMethod(MethodDescriptor var1, RpcController var2, Message var3, RpcCallback<Message> var4);

Message getRequestPrototype(MethodDescriptor var1);

Message getResponsePrototype(MethodDescriptor var1);
}

1
2
3
public interface BindableService {
ServerServiceDefinition bindService();
}

甚至连jar包都不是同一个!

查询了protobuf的官方文档后发现,proto和grpc应该是这样的一种关系:

protobuf只是grpc的一小部分,负责传输协议,而不是RPCs!

因此如果只要grpc的话需要自己手动实现自己的RPC。

为了这个我浪费了快3小时。

最后还是老老实实用grpc,至于自己实现RPCs,就要等到后面一个版本了,将Vertx与protobuf结合感觉不要太香。