rpbear88

Results 3 issues of rpbear88

Hi dragonboat, We are building a ultra-low latency storage system which aims to make the whole IO path latency under 50us for 4KB workloads(the network is RDMA and disk is...

各位好, 我们正在使用braft构建我们自建的存储系统,我们做了一个原型系统来验证braft的性能,原型系统的一些要点如下: - 网络通信层使用brpc的RDMA支持,开启polling+inplace并且绑核,但是没有使用isolcpu来隔离独立的核给RDMA使用 - braft在WAL采用了`MemoryLogStorage`,想忽略这块在磁盘IO上的消耗,因为目前基于文件系统实现的LogStorage的延迟较高,针对一个8K的日志提交延迟会超过300us,远差于基于SPDK实现的IO性能 - 服务端程序自行起了一个pthread用于运行我们的存储引擎,存储引擎主要负责在该线程内使用SPDK进行IO的提交 因此,整个服务端处理写IO的流程是这样的: 1. 服务端收到写请求后从RDMA,其polling线程原地将数据得到后创建一个background的bthread(我们称之为task bthread)来运行任务。(由于运行创建该bthread的worker线程用于RDMA的pooling,没有机会来处理本地的_rq任务,所以最终该任务会被其他worker偷过去运行?) 2. task bthread进入到我们业务状态机后提交给braft 3. braft执行完成后调用`on_apply`,此时会将raft同步完成的请求提交给存储引擎(通过SPDK的ring队列),此处会产生worker thread和引擎thread间的数据交换 4. 存储引擎从任务队列中获取任务后进行SPDK的提交和polling 5. SPDK提交完成后,在存储引擎所在的pthread直接原地(没有起新的background bthread)调用done->Run进行数据回复 目前在单队列的4K写场景下,braft从得到任务到调用on_apply耗时约90us(这个结果显然不能满足实现低于100us的IO延迟的存储系统的要求),我们还在调查这个延迟具体消耗在什么位置。但是考虑到raft本身的一些weakness,我们想从原理上看看是否有更好的方案,下面是我们提出的一个方案(感谢@PFZheng提供的思路)。 ### 元数据和数据使用不同的复制方式 普通的主从同步方式,从并发的角度来说性能是优于raft的,因为它没有顺序提交的问题,但是主从方式做replication的缺点在于: 1. 数据不是强一致,如果leader切换的话,可能会出现数据倒转等问题,这在某些应用场景是不可接受的 2....

按我的理解,bthread是brpc设计的核心,而正如[bthread or not](https://github.com/apache/incubator-brpc/blob/master/docs/cn/bthread_or_not.md)中提到的,由于bthread本身的开销大概在10us左右,如果计算任务只要不足10us那么使用bthread就是一件不太值得的事情。 目前存储IO在闪存介质上已经进入到了10-100us的性能水平,这还是底层IO的耗时,如果使用SPDK等异步IO框架,实际上在io submit这一阶段的耗时已经远远低于10us了,所以业界普遍采用了在一个线程内使用run-to-completion的模型来处理如下某个用户请求所涉及的各个步骤: 1. 查询RDMA接收/发送队列是否有报文要处理,如果有则在当前线程直接处理 2. 查询SPDK的completion queue是否有任务处理, 如果有则在当前线程直接处理 3. 任务如果有IO需求,则提交给SPDK队列后返回 4. 任务结束后提交回应给RDMA队列后返回 整个的操作是在一个线程上运行的,虽然不能利用多核的处理能力,但单核的处理能力可以跑满,在保证上述多个步骤都以极低延迟进行时,整个请求的处理时间将非常短。 我们在使用brpc的过程中确实遇到了一些问题,在将某个任务从接入层传递给存储层时至少消耗10-20us的bthread开销,由于我们追求极低的存储延迟,这是不可接受的。而这一情况在我们使用`-usercode_in_pthread`后也没有好转。 所以如果我们希望在brpc框架上使用run-to-completion模型的话,官方是否有建议的方式呢?我们计划的方式是在接入层收到请求后(此时还是运行在bthread中),将创建的状态机扔进一个高性能的消息队列TaskQueue,我们会为每一个core创建一个pthread来looping这个TaskQueue,至少除了数据接收/发送(RDMA处理)部分以外的其他部分都可以运行在一个thread内采用run-to-completion的方式完成。 还有一种方式,我们想基于brpc实现一种poller的注册机制,比如braft中,各个apply的task可以注册到我们的poller thread上,当事件触发后在poller thread上进行原地调用,但是目前来看对源码的改动会比较大。 很想倾听下百度的朋友在这个问题上的解决方案,谢谢。

discussion