apache / brpc

brpc is an Industrial-grade RPC framework using C++ Language, which is often used in high performance system such as Search, Storage, Machine learning, Advertisement, Recommendation etc. "brpc" means "better RPC".
https://brpc.apache.org
Apache License 2.0
16.56k stars 3.98k forks source link

Request handling order on Server side #2769

Closed cheungsuifai closed 1 month ago

cheungsuifai commented 1 month ago

当使用异步通信时,可以看到服务端的处理顺序是乱序,这应该是bthread多线程处理的逻辑引起的。 如果我的业务上需要保证处理顺序,有什么最佳实践的处理方式?

I0924 15:22:48.872973 212203 25769805576 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1868] from 192.168.1.100:58076 I0924 15:22:48.877137 212200 25769804556 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1872] from 192.168.1.100:58076 I0924 15:22:48.877269 212200 25769805584 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1876] from 192.168.1.100:58076 I0924 15:22:48.877297 212204 12884903722 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1884] from 192.168.1.100:58076 I0924 15:22:48.877304 212200 17179869216 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1878] from 192.168.1.100:58076 I0924 15:22:48.877338 212200 17179869223 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1880] from 192.168.1.100:58076 I0924 15:22:48.877360 212204 12884903728 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1882] from 192.168.1.100:58076 I0924 15:22:48.877464 212201 12884903527 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1874] from 192.168.1.100:58076 I0924 15:22:48.877504 212206 25769805349 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1870] from 192.168.1.100:58076 I0924 15:22:48.882074 212200 8589936233 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1888] from 192.168.1.100:58076 I0924 15:22:48.882702 212206 42949674583 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1886] from 192.168.1.100:58076 I0924 15:22:48.885630 212200 34359739963 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1892] from 192.168.1.100:58076 I0924 15:22:48.886040 212206 17179871021 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1890] from 192.168.1.100:58076 I0924 15:22:48.887117 212200 17179871007 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1896] from 192.168.1.100:58076 I0924 15:22:48.887426 212204 17179870796 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1902] from 192.168.1.100:58076 I0924 15:22:48.887444 212202 12884903504 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1900] from 192.168.1.100:58076 I0924 15:22:48.887453 212200 21474837825 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1906] from 192.168.1.100:58076 I0924 15:22:48.887451 212201 21474837853 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1904] from 192.168.1.100:58076 I0924 15:22:48.887464 212204 25769803781 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1908] from 192.168.1.100:58076 I0924 15:22:48.887597 212203 12884903719 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1894] from 192.168.1.100:58076 I0924 15:22:48.887600 212198 17179870801 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1898] from 192.168.1.100:58076 I0924 15:22:48.890736 212206 17179870804 /home/derek/experiments/8.gpt-hook/server/brpc/cuda_runtime_cublas_brpc_server.cpp:399] Received request[log_id=1912] from 192.168.1.100:58076 I0924 15:22:48.891359 21219

谢谢~

yanglimingcn commented 1 month ago

那只能自己收到请求做一个排序,再一个一个处理了。

chenBright commented 1 month ago

Streaming RPC可以保证顺序。

cheungsuifai commented 1 month ago

Streaming RPC可以保证顺序。

谢谢你的回复。

但streaming的机制似乎是发起一个RPC调用,RPC后续数据使用stream传输,它能保证stream中传输的数据是有序的。但它并不适用于多个RPC(如果有多个RPC,会有多个相互独立的stream)

我现在的场景是client会发起多个不同的rpc的请求,希望server能发送顺序来处理。这样似乎stream并不合适?

chenBright commented 1 month ago

顺序要求是针对不同client、不同server接口还是两者都有?

cheungsuifai commented 1 month ago

顺序要求是针对不同client、不同server接口还是两者都有?

针对同一个client的不同的server接口

例如现在有两个client(Client A,Client B)发送一系列的RPC请求。 Client A发送顺序是 A1,A2,A3 Client B发送顺序是 B1,B2,B3

Server多个线程收到的请求的时间顺序是A1,B1,B2,A3,B3,A2。

其中Client B的请求顺序是OK,按序执行即可; 而Client A的请求是乱序的(A1,A3,A2),我希望可以Server在执行的时候能按(A1,A2,A3)的顺序执行。

chenBright commented 1 month ago

同一个client用一个stream发 A1,A2,A3,server顺序处理,根据请求的特点分发到不同接口,就可以满足需求了吧?

wasphin commented 1 month ago

如果 A2 依赖 A1 的结果是不是 client A 在收到 A1 回复后再调用 A2?如果不依赖结果,只要求顺序执行,也可以合并 A1、A2 为新的 RPC 来处理吧。

cheungsuifai commented 1 month ago

如果 A2 依赖 A1 的结果是不是 client A 在收到 A1 回复后再调用 A2?如果不依赖结果,只要求顺序执行,也可以合并 A1、A2 为新的 RPC 来处理吧。

十分感谢您的回复,您的建议十分的有启发性! 我已经测试过完全同步的方式(即Client在收到前一个RPC的响应再发送后一个),业务逻辑的确是可以正确执行的。

只是我的场景有一些额外约束:

  1. 对时延的要求十分的高,如果每一个RPC都需要等待前者响应,那么时延就长到不可接受,所以我采用了异步的模式,也引入了这个问题。
  2. 我的业务逻辑中RPC之间的依赖关系的数量和次序可能并不固定(举例说明,即有一些场景是A1->A2, 有一些场景是A1->A2->A3->A4,另外一个场景是A1->A2->A4)。所以如果要合并多个RPC,就会面临组合数爆炸的问题。理论上我可以再剪枝掉一些不可能的组合,但估计剪枝的效益并不明显。
chenzhangyi commented 1 month ago

如果 A2 依赖 A1 的结果是不是 client A 在收到 A1 回复后再调用 A2?如果不依赖结果,只要求顺序执行,也可以合并 A1、A2 为新的 RPC 来处理吧。

十分感谢您的回复,您的建议十分的有启发性! 我已经测试过完全同步的方式(即Client在收到前一个RPC的响应再发送后一个),业务逻辑的确是可以正确执行的。

只是我的场景有一些额外约束:

  1. 对时延的要求十分的高,如果每一个RPC都需要等待前者响应,那么时延就长到不可接受,所以我采用了异步的模式,也引入了这个问题。
  2. 我的业务逻辑中RPC之间的依赖关系的数量和次序可能并不固定(举例说明,即有一些场景是A1->A2, 有一些场景是A1->A2->A3->A4,另外一个场景是A1->A2->A4)。所以如果要合并多个RPC,就会面临组合数爆炸的问题。理论上我可以再剪枝掉一些不可能的组合,但估计剪枝的效益并不明显。

听你的描述是需要一个动态的DAG执行引擎,这个引擎来决定没有数据依赖的RPC能并发,有依赖的等前者,这个可能需要包装一下。

cheungsuifai commented 1 month ago

Streaming RPC可以保证顺序。

Streaming RPC可以保证顺序。

再思考了一下,的确可以用到streaming的方式。 我将原来的多个RPC方法接口,统一到一个接口中。然后在随后的streaming通信中传递request和返回response。 感觉似乎不太优雅和奇怪,但技术上的确可行。

另外streaming是如何保序,是因为单线程处理吗?

chenBright commented 1 month ago

ExecutionQueue来实现保序的。

cheungsuifai commented 1 month ago

更新一下当前的进展。 我尝试了使用streaming的方式来保序,技术上的确可行。但是考虑再三(参考了ChatGPT的意见),最后还是没有采用此方式。 原因包括:

  1. 将 RPC 请求和响应通过流传输的设计不合理: RPC 与 Streaming 的区别: 在 brpc 中,RPC 通常用于一次请求对应一次响应的通信模式,而 Streaming 更适合于传输大量数据或需要持续通信的场景。 混淆了 RPC 和 Streaming 的使用场景: 您的需求是在 Streaming 模式下传输 RPC 的请求和响应,这可能导致设计上的混乱。

  2. 潜在的线程安全问题: 服务端的 StreamId 管理: 在服务端代码中,StreamingEchoService 使用了成员变量 _sd 来存储 StreamId。如果有多个客户端连接,可能会导致不同客户端的 StreamId 覆盖,产生线程安全问题。

  3. 性能和效率问题: 重复的序列化和反序列化: 每次发送和接收消息时,都需要手动进行序列化和反序列化,增加了 CPU 开销。 缺乏流控和背压处理: 在高并发或大数据量的情况下,可能会出现消息堆积或网络拥塞的问题。

  4. 未充分利用 brpc 的特性: brpc 已支持异步 RPC 调用: 您希望实现低时延通信,但 brpc 本身已经支持异步的 RPC 调用,完全可以满足低时延的需求。 Streaming 适用场景: brpc 的 Streaming 更适合于大数据量的传输,而非频繁的小数据包传输。

最后我是采用在服务端根据log_id进行重新排序的方式实现保序的。 谢谢各位的支持。