SEDA能够将请求进行异步流水线化处理。现代服务器的底层基本是这三篇文章奠定的,Kqueue Reactor SEDA,这次补全最后一块拼图https://dl.acm.org/doi/pdf/10.1145/502034.502057
传统架构
每个请求一个线程,加个线程池作为优化,憨憨做法
EDA,scheduler监听所有事件, 负载太重
设计目标
- 处理高并发
- 简化服务设计 - 模块化,便于调试
- 允许自省 - 根据负载调节,例如请求优先级、服务降级(不处理或者简易处理)
- 自调节的资源管理 - 动态调节资源满足性能需求而非硬编码
Stage
Event Handler会对于Event进行批处理,但是它本身不能控制队列与线程。Handler本身可能创建新的event,注意这里不能和Reactor的acceptor混淆,acceptor创建的是新的event handler,而不是event。
SEDA
这里为了简化,每个Stage有独立的线程池,实际上可能是共享的。
在传递事件时,event handler会先获取其他队列的句柄(through a system-provided lookup routine),然后enqueue。
队列的长度有限,入队失败可以实现为阻塞,也可以实现为丢弃,或者执行一些自定义的操作,例如报错或者服务降级。
stage之间隔离,仅仅通过event传递数据,引入这样的queue保证了隔离、模块化、独立负载管理,便于代理中间过程提供debug信息,但是也增加了时延。
动态资源管理
Batching和线程池大小均可动态调节。还可以创建新的控制器,例如整体线程数目限制、线程调度参数。(Section 5.1的时延控制器,依据排队论通过调节队伍长度阈值控制时延,而阈值本身其实又是联动着线程池控制器)
线程池控制器会定期检查队列长度,如果长度超过阈值则创建线程,如何idle时间超过阈值则销毁线程。
Batch越大,throughput越大,同时response time越大,这是个trade-off。
批处理控制器会观察一段时间内平均的事件输出速度,如果输出慢慢越来越少,就略微增加,如果骤降就迅速提高到最大。
总体来说两者都是负反馈机制,并不关注OS细节,只看应用性能。作者也提到某些时候可能人们会关注QOS这种更底层的东西,不过他认为这些简单的控制器足够用了。
AIO
利用SEDA实现异步通信 pipeline。
应用发出read异步请求,利用Demultiplexer监听Read Ready事件,event handler根据事件产生新的事件(Packet)传给应用。应用的线程池被唤醒,处理这个事件并发出write异步请求,后续过程类似。
这样有什么好处呢?学过计算机体系结构的都知道,流水线的stage越平均,最终的性能越好,因此需要通过调节线程池大小控制stage时间,否则最慢的那个stage会成为瓶颈。
假设读需要1s,写需要2s,那么我们用1个线程处理读,4s内能处理4个读;用2个线程处理写,4s内能处理4个写。也就是说最终3个线程4s处理了四个请求。
如果我们用EDA呢?3个线程用3s处理完成3个请求,剩下一个请求又要再等3s才能完成。