分布式思考:我们需要fsync吗?
历史上看,是先有单机系统,然后再向分布式演变。我们很多分布式的设计,都源自单机的很多经验。这是没错的,因为分布式本来就是众多单机参与的一个集群系统。我们当然要借鉴单机的经验和单机的基础。
但是,分布式源自单机,但不能思想受限于单机。
我的观点:分布式要学习和复用单机的知识,但在一些领域,要勇敢跳出单机的思维限制。
先看个思考:我们需要fsync吗?
## 单机为什么需要fsync
对于一个单机数据库,收到客户端的Write请求,也就是客户端希望数据库服务器存下某个数据,需要给客户端确认消息(committed response)。一旦给了客户committed response,客户端就像古时交易的银货两讫,认为数据被数据库完整地保存下来了。
但这不是我们理想中的那样有保证。
如果数据库服务器不用fsync及时落盘,风调雨顺的日子里,服务器将数据放在内存,可以继续向客户保证这个数据在它那里完好无损地保存,但一旦遇到灾祸,比如掉电,数据库服务器重启,这个数据就会丢失。
所以,单机下,数据库需要及时地进行fsync操作。如果不想丢一点数据,数据库必须在每次给committed response前,先用fsync落盘,然后发送committed response,这样才能保证对客户的承诺,无论是丰年还是灾年,都永恒不变。
但问题是:fsync是个非常慢的动作
站在Latency角度看,如果向内存写入数据,Latency是几个ns,多100个ns。而如果向磁盘写入,对于HDD,Latency是几个ms级别,SSD是几十或几百个us级别。这是千倍,甚至百万倍的时间差别。
而且,磁盘是单条路径,不能并发。也许你看SSD的内部是并发的(SSD因此而快,可参考:Kafka is Database),操作系统提交给SSD也可以多线程多队列(而且对于NVMe SSD这样做是提高其Throughput的一个好的解决方案),但是,对于OS内核的SSD driver和SSD硬件实体之间,还是单通道的。
而对于内存和CPU不一样。主内存Main Memory的操作虽然是单通道的,但可以通过多条内存条、NUMA下的多个Memory Bus、重要的是,让CPU实现多个CPU core以及对应的每个CPU corer一个L1 Cache,让并发得以提升(其实从某种程度而言,现代CPU多core的架构就是一种分布式)。
网络端,由于网卡的Bandwidth足够大,可以让多个客户端并发发送数据包,实现更好的并发性。
所以,磁盘操作受限于磁盘自己的慢速,以及不好并发,导致Latency很大,从而降低Throughput。如果我们服务器每次操作,都必须fsync的话,必然让整个系统的Throughput,受这个磁盘Throughput的约束,从而成为瓶颈bottleneck。
对于单机系统,这是无解的。如果你要向客户端承诺不丢数据,你必须接受这个性能的惩罚。
## 分布式下必须fsync吗?
我们看上面的例子,核心点是如果服务器如果给客户端以committed response,那么发生意外(如断电),这个数据不应该丢失。
对于分布式系统,如果数据能在多台机器上的内存里都存在,即使不存盘,我们还是遵从了这个承诺。
因为即使集群里的一台机器发生了掉电,那么另外一台机器上,还有完全一样的一份数据,作为单机,这个承诺不成立,但作为集群,承诺还在,只要两台不同时发生灾难。
你可能会反问,一个机架(Rack)下,掉电可是一起发生的。
那么我们可以让集群的这两台机器,分布在不同的机架上(即不共用电源和网络集线器)。
你可能再追问,如果一个机房停电呢?
那我们可以将集群部署在两个机房上,只要两个机房的网络通信速度几乎和局域网相当(或者延时足够小,如果不能和局域网的速度完全匹配的话)。
你可能再问,如果两个机房一起完蛋呢?
这是无解的,而且无意义。
即使对于单机,它还有磁盘坏的可能,同样无法保证fsync就一定保证数据不丢失。对于任何系统,灾难到一定程度(比如:地球毁灭),数据的保证都不能实现。
我们只能满足一定程度的可靠性即可,对于集群,通过多机,甚至多机分布多机房,来保证这个对客户端的承诺。从概率学角度,只有极其微小概率的损坏可能,如果概率足够小,我们可以认为它趋近于零。
但是,让数据只通过网络,只保存在内存,但用多机保存,我们避免了fsync的每时每刻惩罚。这个trade-off是值得的。因为一年都很少发生一次灾难,但每秒的数据操作,如果qps是百万/秒的话,我们每秒要做1百万次操作。
那么发生网络故障怎么办?
方法很简单:对于集群系统,如果发生网络故障,我们就不做承诺(发送committed response给客户)。更先进的解决方案是:对于网络也做冗余,即网络通道不只一个,即希望双网络故障的概率也趋近于零(一个类似的案例是UUID,理论上有重复的可能,但实践中我们认为就是值,因为概率够低)。
还有一个针对网络分割Network Partition的特殊解决方案:就是网络故障,只导致一个集群里部分主机之间不能通信,但并不是全部不能通信。那么,我们还是可以通过一些特殊的算法(如Raft),来实现这个承诺。
## 结论
在分布式系统下,我们要考虑放弃实时fsync(后还是要存盘,但可以后台,不忙时,以批的方式进行)。尽可能用高速的内存和网络去替代慢速的磁盘,同时维持对客户的承诺。
因为我们已经用了多机和网络,用两份内存去保存同样一份数据,我们已经pay something,我们当然要get something。
只要你放弃数据安全,必须fsync这个思想的束缚。
两个我自己实践的案例:
- 1. 优化etcd,通过取消fsync,让etcd提高Throughput。
2. BunnyRedis,BunnyRedis里没有fsync,不管是层bunny-redis的RocksDB,还是后面一层的Kafka。
相关文章