0%

异步编程小记

当前网关的代码整体是异步阻塞的模式,阻塞主要体现在epoll的EventLoop那里,现在要在网关内部加一些逻辑,如果按照当前其他代码的方式,添加的是阻塞的代码,会导致EventLoop被阻塞,当所有EventLoop都被阻塞的时候(当前线程数是CPU两倍),网关不能处理新进来的请求的速度会减慢,整体的吞吐会降低。

一、如何解决

解决的办法,其实就是参考当前Vert.x的实现方式,一旦碰到耗时的操作,都放到EventLoop中去,例如需要从Socket读数据的时候,是将Socket的fd加到epoll中,并监听read事件,在read事件到达时候调用相应的处理函数,进行解包处理,并在包被完整解开之后调用相应的处理函数进行处理。
对应到实际代码中,以Redis的Letuce Client为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EventExecutorGroup eventLoopGroup =  new DefaultEventExecutorGroup(8, new DefaultThreadFactory("wudan-group", true));

ClientResources clientResources = DefaultClientResources
.builder()
.eventExecutorGroup(eventLoopGroup)
.build();
RedisClient redisClient = RedisClient.create(clientResources, "redis://localhost:6379");
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisReactiveCommands<String, String> reactiveCommand = connection.reactive();

reactiveCommand.get("wudan").subscribe(s -> {
System.out.println(Thread.currentThread().getName() + " " + s);
});

Thread.sleep(1000L);

执行当前代码的线程和执行subscribe中打印代码的线程不是一个线程,因为subscribe是EventLoop在read事件之后触发的,并在EventLoop线程中执行了。
因此,对于网关的情况,我们只需要使用这种Reactive的方式来执行即可,对于Redis的操作,通过lettuce Client来执行,对于gRPC的调用,我们使用新的基于Reactor Core的Client。

二、问题

上述解决方案,存在一个小问题,由于不同的组建的EventLoop不是同一个,当我们从一个切换到另一个时,相当于把之后代码的运行权都交给了后面的EventLoop,存在一定的不可控性,因为后续的EventLoop可能没有fine tuned,以及当前的一些监控都没有加到后续的EventLoop上。
一个比较统一的方式是让所有程序块都用相同一个EventLoop,例如上面的代码中lettuce是支持设置EventLoop的。

三、延伸

关于同步、异步、阻塞、非阻塞这个古老的问题,认识又进一步加深了。个人的理解是

  • 异步和同步的差异在于,是否在同一个线程里处理耗时操作,同步是在当前线程,异步是在另一个线程,异步的好处是,如果有多个耗时操作,我可以并行的去执行,这样需要等的时间就是单个操作的Max时间,而同步的等待时间是所有操作的总时间
  • 阻塞和非阻塞的差异在于,是否直接调用耗时的系统调用(例如read),如果是直接调用read,那就是阻塞直到可读,如果是调用select、poll、epoll之类的系统调用,可以去执行别的代码(目前没找到例子),然后再回来不断的去调用epoll去查看,直到返回可读状态的时候,再去调用read系统调用,读取数据并处理

四、总结

第一部分中的判断是基于对于线程模型的理解,需要实际的压测结果来支持。
个人认为Reactor的方式已经基本接近异步非阻塞了,只是需要一个阻塞的EventLoop线程来对一堆fd做epoll操作,可以将上下文代码通过subscribe的方式来连接起来,核心点是需要将需要的数据和状态存储到一个context里。
由于Reactor依赖所有的代码都采用Reactor的方式,而很多互联网公司通常都会自己写一些组件,因此在整个公司层面存在这样的意识,否则无法让所有组件都支持Reactor,从而不能达到预期的目标。

五、Reference

  1. asynchronous and non-blocking calls? also between blocking and synchronous
  2. Asynchronous I/O
如果您觉得这些内容对您有帮助,你可以赞助我以提高站点的文章质量