V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
DinoStray
V2EX  ›  程序员

忍不住想吐槽下 grpc 的 C++ async API

  •  
  •   DinoStray · 2020-03-11 09:35:01 +08:00 · 5631 次点击
    这是一个创建于 1718 天前的主题,其中的信息可能已经有所发展或是发生改变。

    grpc 当前版本的 C++ async API, 是基于 completion queue 实现的.

    1. 首先在 api 设计上, 对于单次 rpc 调用, 这个 completion queue 只能有两个, 分别是 call 和 notification(grpc 起的名字), 读写事件只能在 call 这一个 completion queue 回调
    2. completion queue 事件返回时会带有一个标识. 在官方的 demo 中, 这个标识用来标记是哪个对象产生了事件. 你想通过这个标识同时区分到底是哪种事件产生, 是不可能了.
    3. 简单来说, completion queue 返回的值, 只有一个, 这个值在有些 demo(官方) 里被用来标记对象, 有些 demo(网上的一些例子) 里被用来标记事件, 可是我写基于 stream(也就是 N 个请求 N 个返回) 的长连接, 需要同时获得这两个信息啊
    4. 如果参照 libevent 之类的办法, 最起码应该分别有"读"/"写"以及"连接事件"的 completion queue
    5. 可能官方也不知道怎么处理最好, 唯一的 async api demo 是不包含 stream 的, 简单的单次 rpc 调用

    为了解决这个问题, 我只好用一个"全局唯一的 uint64 id"标识对象, 并在分配 id 的时候做了区间划分, 比如 * id + 0 用来标识 "连接创建事件"

    • id + 1 用来标识 "读完成事件"
    • id + 2 用来标识 "写完成事件"
    • id + 3 用来标识 "连接关闭事件"

    然后下一个新的对象 id , 在上一个 id 的基础上 + 4
    这是我的 demo:
    https://github.com/DinoStray/grpc_example_async_cpp_api

    关于这个问题, 几年前就有人开始吐槽(描述的问题和我一模一样)
    https://groups.google.com/forum/#!topic/grpc-io/7P8QvBBNq_E
    grpc 官方也给了回应, 并表示将优化 api
    https://github.com/grpc/grpc/issues/7352
    可是 2016 年的回应, 都过去 4 年了, 还没完成....吐血

    28 条回复    2020-03-11 12:33:29 +08:00
    xkeyideal
        1
    xkeyideal  
       2020-03-11 09:42:11 +08:00
    用亲儿子啊
    codehz
        2
    codehz  
       2020-03-11 09:50:42 +08:00 via Android   ❤️ 1
    (这设计确实挺恶心的)
    不过作为 workaround,我觉得你可以用位运算来简化问题(
    其中 id 就表示对象 id,按 1 递增
    然后弄个事件类型的 enum (type)
    (id << 2) | type 来合成最终的 id(设为 x)
    提取就可以通过 id = x >> 2 和 type = x & 0b11 来解决了
    类似的想法在 windows 的 handle 上也有应用( 你仔细看 win 的进程 id,会发现它永远是 4 的倍数,同样也是尾部的 2 个 bit 用于区分类型)
    DinoStray
        3
    DinoStray  
    OP
       2020-03-11 09:56:08 +08:00
    @codehz good idea.
    可我刚刚发现自己转牛角尖了. 也许作者在设计这套 api 的时候, 就想让使用者每个 rpc 请求都有单独的 completion queue, 也就是最早的 "每个 tcp 连接 一个线程" 模型. 我 epoll 用惯了, 思维方式固化了, 老是想着限制线程数量,
    DinoStray
        4
    DinoStray  
    OP
       2020-03-11 10:05:06 +08:00
    @xkeyideal 我看了 java 和 golang 的 api, 实现都很简单易用, 唉
    icylogic
        5
    icylogic  
       2020-03-11 10:33:16 +08:00
    这个 tag 是个 void*,不一定非得是 integer 啊?你放个 CallData* ,pair<id, event>或者 callback 指针不可以吗,CallData 里边啥都可以放啊。
    jonah
        7
    jonah  
       2020-03-11 10:38:43 +08:00
    我一般是这么做的:传进去一个类或者结构体,里面保存请求时的上下文,比如你这里面的事件类型。
    目前这个 grpc c++封装是基于 c 的,搞成 Async 异步接口意味着库里面要有线程读 cq,不一定灵活。
    DinoStray
        8
    DinoStray  
    OP
       2020-03-11 11:10:57 +08:00
    @icylogic 官方 demo 就是个指针, 我的问题是, 如果用返回值标识指针, 就没办法区分事件类型了, 比如异步读和异步写
    DinoStray
        9
    DinoStray  
    OP
       2020-03-11 11:12:18 +08:00
    @icylogic 这个 demo 我研究过了, 问题在于他没用 stream, 我的核心需求是实现 pubsub 模型, 所以必须用 stream, 基于一些设计, 还得是 bi-di stream
    janxin
        10
    janxin  
       2020-03-11 11:19:56 +08:00
    C++的 gRPC 确实很值得吐槽,要么考虑一下 brpc ?
    tyrantZhao
        11
    tyrantZhao  
       2020-03-11 11:23:17 +08:00
    cpp 的 grpc 槽点太多了
    DinoStray
        12
    DinoStray  
    OP
       2020-03-11 11:25:31 +08:00
    @icylogic 这个官方 demo , 如果同时做异步读写, 那这个 demo 的模型就跑不通了, 因为无法区分读完成和写完成
    jonah
        13
    jonah  
       2020-03-11 11:30:47 +08:00
    @DinoStray 你还是没有理解,指针可以指向一个对象,对象里的成员可以放东西的,放请求时的事件类型。
    DinoStray
        14
    DinoStray  
    OP
       2020-03-11 11:35:36 +08:00
    @jonah 读和写都是同步的, 不存在先后, 如果读写同步, 只能通过一个 completion queue 返回, 那你在对象成员里也没办法做区分了
    DinoStray
        15
    DinoStray  
    OP
       2020-03-11 11:38:25 +08:00
    @jonah 如果先读再写, 或者先写再读, 所有的事件基于线性产生, 那这个 demo 的模型的确可以跑通, 虽然实现有点繁琐. 可我的目标是 bi-di stream, 这时候就不适用了
    DinoStray
        16
    DinoStray  
    OP
       2020-03-11 11:51:56 +08:00
    @jonah 再详细点说, 异步读和异步写都开启了, 这时候 completion queue 返回, 你可以根据返回的 tag 知道是哪个对象产生了事件, 可没有办法区分是读完成了, 还是写完成了
    jonah
        17
    jonah  
       2020-03-11 11:57:40 +08:00
    @DinoStray 可以区分,completion queue Next()返回的指针就是你请求时传递的那个对象指针,请求时你是知道是读还是写的。 demo: https://gist.github.com/byteink/2b998cf1e641ce8ef8965d081f0814e1
    DinoStray
        18
    DinoStray  
    OP
       2020-03-11 12:00:27 +08:00
    @jonah bi-di stream, 是指同时开启异步的读写, 你这个 demo 依然还是把读写线性化了
    DinoStray
        19
    DinoStray  
    OP
       2020-03-11 12:01:59 +08:00
    @jonah 这个 demo 在同一时间, 只能读, 或者只能写, 要么写完了再读, 要么读完了再写. 在 bi-di stream 模型中, 我的需求是随机性有 N 个 request, 随机性有 M 个 reply, request 和 reply 没有任何关联
    jonah
        20
    jonah  
       2020-03-11 12:05:31 +08:00
    @DinoStray 没理解为啥线性化,你试试吧,completion queue Next 返回的顺序本来就是任意的,哪个先完成就先返回,不然也不需要有个指针来对应了。这个跟你现在的原理是等价的,只是传的结构优雅些。
    DinoStray
        21
    DinoStray  
    OP
       2020-03-11 12:06:20 +08:00
    @jonah Sorry, 我看懂你的意思了, 你是做了第二层封装, 把 session 对象和状态封装到一起了
    DinoStray
        22
    DinoStray  
    OP
       2020-03-11 12:07:50 +08:00
    @jonah 你的这个封装, 和我把 session id + event 事件 的解决方式, 是类似的, 不过有一点, 我的代码习惯用 C++11 智能指针管理, 不太想直接传递裸指针
    DinoStray
        23
    DinoStray  
    OP
       2020-03-11 12:11:28 +08:00
    @DinoStray 你的意思, 是每次调用异步 api, 都 new 一个新的对象, 新的对象封装了 session 和事件, 异步 api 回调的时候, 就可以通过这个对象知道 session 和产生的 event, 然后直接销毁这个临时的对象, 我的理解没错吧
    jonah
        24
    jonah  
       2020-03-11 12:17:43 +08:00
    @DinoStray unique_ptr 更安全,传的时候 release 一下,Next 回来就用 unique_ptr 包起来。但是传递给 completion queue 避免不了裸指针的,它调的底层 c 的接口。
    对, 是这个意思,我看你主题里说主要问题是需要获取同时多个信息,这种是个通用的做法,可以保存任何必要的信息。
    DinoStray
        25
    DinoStray  
    OP
       2020-03-11 12:22:34 +08:00
    @jonah 我一直觉得用了智能指针就不能暴露裸指针了, 这两个东西混太容易导致程序崩溃. 我看 grpc 源码, 到处都是 unique_ptr 和裸指针的混用, 看的蛋疼
    jonah
        26
    jonah  
       2020-03-11 12:27:55 +08:00
    @DinoStray 只能说尽量这样做。grpc 的代码一言难尽。。。
    DinoStray
        27
    DinoStray  
    OP
       2020-03-11 12:30:31 +08:00
    @jonah 我还一个问题暂时不知道怎么办, 不管服务端还是客户端, 我好像只能调用 TryCancel() 函数去主动关闭 一个处于 读写状态的 session, 更合适的主动关闭方式是调用哪个函数呢
    jonah
        28
    jonah  
       2020-03-11 12:33:29 +08:00
    @DinoStray 这个我也不清楚,已经弃用 grpc 有些年头了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2687 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 11:56 · PVG 19:56 · LAX 03:56 · JFK 06:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.