bthread_id

学习bRPC bthread_id。

bthread_id是一个特殊的同步结构,它可以互斥RPC过程中的不同环节,也可以O(1)时间内找到RPC上下文(即Controller)。注意,这里我们谈论的是bthread_id_t,不是bthread_t(bthread的tid),这个名字起的确实不太好,容易混淆。

具体来说,bthread_id解决的问题有:

  • 在发送RPC过程中response回来了,处理response的代码和发送代码产生竞争。
  • 设置timer后很快触发了,超时处理代码和发送代码产生竞争。
  • 重试产生的多个response同时回来产生的竞争。
  • 通过correlation_id在O(1)时间内找到对应的RPC上下文,而无需建立从correlation_id到RPC上下文的全局哈希表。
  • 取消RPC。

上文提到的bug在其他rpc框架中广泛存在,下面我们来看下brpc是如何通过bthread_id解决这些问题的。

bthread_id包括两部分,一个是用户可见的64位id,另一个是对应的不可见的bthread::Id结构体。用户接口都是操作id的。从id映射到结构体的方式和brpc中的其他结构类似:32位是内存池的位移,32位是version。前者O(1)时间定位,后者防止ABA问题。

bthread_id的接口不太简洁,有不少API:

  • create
  • lock
  • unlock
  • unlock_and_destroy
  • join
  • error

这么多接口是为了满足不同的使用流程。

  • 发送request的流程:bthread_id_create -> bthread_id_lock -> … register timer and send RPC … -> bthread_id_unlock
  • 接收response的流程:bthread_id_lock -> ..process response -> bthread_id_unlock_and_destroy
  • 异常处理流程:timeout/socket fail -> bthread_id_error -> 执行on_error回调(这里会加锁),分两种情况
    • 请求重试/backup request: 重新register timer and send RPC -> bthread_id_unlock
    • 无法重试,最终失败:bthread_id_unlock_and_destroy
  • 同步等待RPC结束:bthread_id_join

为了减少等待,bthread_id做了一些优化的机制:

  • error发生的时候,如果bthread_id已经被锁住,会把error信息放到一个pending queue中,bthread_id_error函数立即返回。当bthread_id_unlock的时候,如果pending queue里面有任务就取出来执行。
  • RPC结束的时候,如果存在用户回调,先执行一个bthread_id_about_to_destroy,让正在等待的bthread_id_lock操作立即失败,再执行用户回调(这个可能耗时较长,不可控),最后再执行bthread_id_unlock_and_destroy

修改于 2024年10月7日: Oncall report (1b7065e)