Skip to content

event_dispatcher 数量设置咨询 #3191

Open

Description

@MJY-HUST

在我们的场景中,通过 brpc 框架传输数据量较大。单个客户端可能会和多个服务端建立连接。在流量较小时(大量 brpc_worker 都阻塞在 parking log上),如果单次发送大量请求后返回。极端情况下分为两种可能,会影响系统长尾时延

  • 通过一个 fd 接收所有的 repsonse,此时虽然能产生 n 个 bthread,但是加了nosignal的flag。最后的 bthread_flush 只能唤醒最多 2 个 brpc_worker 消费。其实大量的 response 都是在串行消费。 这里是 signal_task 为了权衡性能和​​调度及时性,所以设置成了2。目前来看好像已经是最优设置?
  • 如果 response 通过多个 fd 接收。fd 的 消费在一个 event_dispatcher 中是串行的,此时也会存在大量response 串行消费的问题。但是这种场景下,可以通过增加 event_dispatcher 的数量,来提升唤醒并消费任务的 brpc_worker 数量, 实测可以提升一些性能。

这里有几个问题咨询一下

  • event_dispatcher 数量设置有什么建议参考吗?event_dispatcher 较多对性能有副影响么?
  • 当前 event_dispatcher_num 设置是不区分 tag 的,即每个 tagged group 都会初始化 event_dispatcher_num 个 bthread。但是基于实际需求,每个 tagged group 中的 brpc_worker 数实际上是不一样的。之前 当event_dispatcher_num 大于 num_threads 时,ctrl c失效 #1120 中提到过,event_dispatcher_num 的大小不能大于等于 brpc_worker 的数量,否则会存在问题。导致在多 tag 场景下设置 event_dispatcher_num 不太灵活,是不是应该有类似于分别设置或者每 n 个 brpc_worker 初始化一个 event_dispatcher 的参数。

Activity

  1. yanglimingcn commented on Jan 12, 2026

    @yanglimingcn
    Contributor

    其实每个tag下的event_dispatch数量是可以不同的,只是现在都是通过FLAGS_event_dispatcher_num这个flags来设置的,为了保持兼容性,所以就不太好修改,目前也没想到提供怎么样API用着更合理,你有什么好的建议吗?

  2. MJY-HUST commented on Jan 14, 2026

    @MJY-HUST
    ContributorAuthor

    其实每个tag下的event_dispatch数量是可以不同的,只是现在都是通过FLAGS_event_dispatcher_num这个flags来设置的,为了保持兼容性,所以就不太好修改,目前也没想到提供怎么样API用着更合理,你有什么好的建议吗?

    如果仍然还是需要通过 gflags 初始化,不支持动态修改,目前只想到下面两种方式

    1. gflag: 字符串 + 解析逻辑
    2. gflag:改为每 n 个 worker 初始化一个 dispatch
  3. yanglimingcn commented on Mar 11, 2026

    @yanglimingcn
    Contributor

    现在组worker里面的epoll数量应该会很大吗?都设置成你希望的比较大的,把每组的worker调整的稍多一些,是不是也满足需求了。

  4. Lfengjie commented on May 15, 2026

    @Lfengjie

    请问一下你用的那个版本的brpc,我也遇到类似的问题,但是调大 event dispatcher 的个数,client端会报连接错误的错,server端过了一会儿直接就core了

  5. chenBright commented on Oct 30, 2026

    @chenBright
    Contributor

    @MJY-HUST #3683 已经解决了 #1120 的问题了。

  6. wwbmmm commented on Oct 31, 2026

    @wwbmmm
    Contributor

    [replied by brpc-oncall robot] 结合当前 master 的实现补充几点(引用的都是仓库里的实际代码):

    1. ED 数量目前只支持全局配置
    FLAGS_event_dispatcher_num 是全局 flag(src/bthread/task_control.cpp:60)。每个 tag 会有自己独立的 num 个 ED 实例,但数量对所有 tag 相同:g_edisp 直接按 FLAGS_task_group_ntags * FLAGS_event_dispatcher_num 一次性分配(src/brpc/event_dispatcher.cpp:78),TaskControl 里的 ED 优先队列也按 tag * _ed_priority_queue_num_of_each_tag + index 布局(src/bthread/task_control.h:173-176、src/bthread/task_control.cpp:216-218)。所以“每组独立”指的是每组有自己的实例,不是可以分别配数量,确实没有 per-tag 接口。

    2. #1120 的约束已由 #3683 解除
    ED 和 bthread 内部 epoll 线程是永不退出的循环,会长期占住承载它的 worker pthread(docs/cn/server.md:598)。#3683 之后它们走独立的“预留并发数”额度:初始化 ED 时按 tag 调用 bthread_add_reserved_concurrency_by_tag(FLAGS_event_dispatcher_num, tag)(src/brpc/event_dispatcher.cpp:60-75),预留 worker 不再从 bthread_concurrency/num_threads 里扣(docs/cn/server.md:600-604、src/bthread/bthread.h:157-188)。因此 event_dispatcher_num >= worker 数 导致所有 worker 卡在 epoll_wait、业务不响应甚至进程退不掉的问题已不存在,对应回归用例见 test/brpc_event_dispatcher_unittest.cpp:621 起。现在的硬约束变成每个 tag 的预留累计不超过 BTHREAD_MAX_RESERVED_CONCURRENCY(512,src/bthread/types.h:159)。注意该修复在 1.17.0 之后,1.17.0 及更早版本没有。

    3. ED 数量如何取值,以及副作用
    fd 到 ED 的映射是 butil::fmix32(fd) % FLAGS_event_dispatcher_num(src/brpc/event_dispatcher.cpp:98),同一个 ED 上的 fd 串行处理事件,所以调大主要是把热点 fd 打散、提高事件处理/唤醒的并行度,和你实测的结论一致。副作用:

    • 每个 ED 占一个常驻 worker pthread(现在计入预留额度),总线程数变为 普通并发数 + ntags * num (+ 内部 epoll),可用 bvar bthread_worker_count 观察;
    • 每个 ED 有自己的 epoll fd,开启 -enable_bthread_priority_queue 后每个 ED 还有一个 BTHREAD_MAX_CONCURRENCY(1024)容量的 steal queue(src/bthread/task_control.cpp:225-241);
    • 线程数远超物理核后,调度抖动、cache 争抢会上升,长尾可能反而变差。

    建议从 1 开始按 IO 密集度往上加,数量级控制在不超过可用核数,用 bvar event_dispatcher_read / event_dispatcher_write(src/brpc/event_dispatcher.cpp:76-77 注册)配合长尾指标来定,没有通用的推荐值。

    4. signal_task 里的 2
    这是硬编码上限,src/bthread/task_control.cpp:718-720,注释说明是“唤醒足够 worker”和“避免大量无用 signal”之间的折中,目前没有 flag 可调。调用时它会在该 tag 的 parking lot 之间轮转(src/bthread/task_control.cpp:722-731),parking lot 数量由 -bthread_parking_lot_of_each_tag 控制(默认 4,src/bthread/bthread.cpp:65)。所以一次 flush 最多唤醒 2 个 worker 确实是当前实现的上限。想绕开这条路径,可以考虑 usercode_in_coroutine(docs/cn/coroutine.md:112,此时实际处理能力由 ED 数量决定,不再依赖 worker 唤醒),或让事件处理尽量在 ED 上就地完成,减少串行入队再唤醒的环节。

    5. per-tag 配置的落地方式
    实现上可行(ED 本身是按 tag 独立启动的),但要同时把 TaskControl 的 _ed_priority_queues 改为按 tag 变长布局(现在假定每组数量相同),且必须在 InitializeGlobalDispatchers() 的 pthread_once 之前生效(src/brpc/event_dispatcher.cpp:49、GetGlobalEventDispatcher),运行时改 gflag 是无效的。接口形态上倾向新增 API 按 tag 设置,而不是把 flag 改成字符串再解析。如果你确认这个需求,我可以整理成开发任务推进。

    关于 @Lfengjie 的报错:需要 brpc 版本(tag/commit)、启动参数(event_dispatcher_num、task_group_ntags、bthread_concurrency/num_threads)、core 栈,以及 client 端“连接错误”的具体错误码和日志。1.17.0 及更早版本没有 #3683 的预留逻辑,event_dispatcher_num 逼近或超过 worker 数时正好会触发上面说的所有 worker 卡在 epoll_wait 的问题,建议先换到包含 #3683 的代码后复测。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions