高频金融系统如何防止突然断电导致的数据丢失?

144 天前
 latifrons

我知道 MySQL ,RocksDB 等数据库其实都有 WAL ,但同样地都依赖操作系统定期将内存缓存中的数据通过 fsync()刷回磁盘。如果此时断电(或者操作系统崩溃),1 秒内的数据可能会丢失。

MySQL:innodb_flush_log_at_trx_commit

https://dba.stackexchange.com/questions/12611/is-it-safe-to-use-innodb-flush-log-at-trx-commit-2

The default value of 1 is required for full ACID compliance. You can achieve better performance by setting the value different from 1, but then you can lose up to one second worth of transactions in a crash. With a value of 0, any mysqld process crash can erase the last second of transactions. With a value of 2, only an operating system crash or a power outage can erase the last second of transactions. InnoDB's crash recovery works regardless of the value.

所以似乎为了数据完备性,innodb_flush_log_at_trx_commit=1 是不可避免的,从而会导致比较严重的性能劣势。

例如 LevelDB ,开启 WriteOptions(Sync=true)后,tps 从 190000/s 骤降为 1200/s.

Qwen3 的一个回答: https://chat.qwen.ai/s/0e7d9977-4400-41da-883f-2972893104cf?fev=0.0.85

想请教一下大家,在对数据完备性要求极高的场景下,大家是怎么优化性能的?

11041 次点击
所在节点    程序员
133 条回复
liqingyou2093
143 天前
异地冗余呗,这边服务器被炸了换另一个
缺点就是成本翻倍了
acorngyl
143 天前
@wxf666 #68 就说数据库设计原理,不具体说什么数据库,因为每种数据库的具体实现方式不一样。
数据块的修改和回写和日志是平行的两条线。所有的 DML 操作都会记日志,为了避免频繁 IO ,日志先在日志缓存区里,等日志缓存区片满了,会统一写一次硬盘。日志里有所有的操作、操作流水号、还有 commit 。

数据块也会在内存里做同样的操作,并且有和日志一样的流水块。内存里也有内存缓存区,缓存区满了,会写回磁盘。数据块写回磁盘的时候是不管脏不脏的。判断事物结束唯一标识是 commit 。

这样看日志和数据块操作重复了。日志的作用(一般)就是用于做实例恢复,万一突然断电,下次启动做实例恢复,先加载数据文件,从数据文件最后一个流水号开始,去日志文件找相应流水号,把该流水号之后的操作都 redo 一遍(所以日志一般叫 redo 日志)。这样做原因,日志是顺序写,不考虑业务逻辑和数据分布,所以 io 可以非常快,基本可以同步磁盘顺序写的速度。之后,数据块的回写就可以变得非常灵活,因为数据块不仅仅是存储数据,还有查询和计算作用。

即便日志写频繁,SSD 速度快,一般也不能每次 commit 都回写,这样就是散列 IO 了,不管 SSD 和磁盘,4K 速度和连续写都差几十倍了。还有,一个事物可能不足一个块,要不就补空块回写,一堆碎片块,占 IO 和损硬盘;或者每次都写一个空块,再把它读回来,接着写,IO 更慢了。
lianglu
143 天前
我的理解:如果是分布式的数据库,fsync 可以是 1s 钟刷盘,毕竟提交成功后至少其他节点已经有数据了。如果是单机的话,为了保证绝对的不丢失,确实每次 commit 时候刷盘。以上为了保证高可用以及数据不丢失,性能肯定是打折的
rainbowhu
143 天前
大嘴巴水一水,不用太当真:既然要在数据完备性要求这么高的情况下提升性能。那就考虑下怎么提升性能。
1. 一台机器慢就多整点节点,做好负载划分。
2. 性能也分吞吐和延迟,如果要求高吞吐,可以增大持久化的粒度。当然这样延迟会更高。如果是要求低延迟,那粒度只能维持在一个正常水平就行了,也不用太小。
3. 剩下就是软件栈上的各种优化了,简化软件栈的关键路径,略微提升性能。。。
rainbowhu
143 天前
对了,还有就是用性能更好的盘,或者用非易失内存或者保电内存之类的。反正硬件的极限就是系统的极限。
ladeo
143 天前
供电的问题就应该供电去解决啊( UPS,ATS,多路市电,自备柴油发电机)。不要越俎代庖。
suxb201
143 天前
和 UPS 、fsync() 没关系,想要数据安全一定得分布式,单机不管怎么搞总会有单点的安全问题(火灾、陨石)。分布式场景下性能可以做到很好的,RDMA 网络,写到同 AZ 的多份主机上,整个链路延迟很低( us 级别)。

但这一套不是自建玩的来的,要不要考虑上云呢?各厂商数据库都有类似能力,丢数据直接甩工单索赔多爽。
Ch3n4y
143 天前
双路市电+UPS+柴油发电机,从机房建好后基本没断过电。
latifrons
143 天前
听了大家说了这么多,收获颇丰,感谢大家。

其实这个问题在我这里产生的根源就是在分布式系统中。单体状态自己说了算,灾难恢复回来是什么就是什么。而分布式系统就需要考虑一致性了。

TCC 分布式事务环境下,正如 @weirdte 所说,“返回失败不一定是真的失败,但返回成功是一定要成功的”。 如果被调用方返回成功,但结果却因未落盘而回滚,调用方根本不会知道有这么个回滚节点。回滚节点也不知道有什么应该做而没做的事。

事后对账当然是一个办法,但很多时候对账并不能挽回损失。例如扣减余额这种事如果回滚,肯定要有人背锅了。

似乎解决方法就是双写/强制落盘,不知道例如银行、券商这样的系统,对于涉及钱的跨系统的一致性,除了对账,在技术上是如何实践的呢?又如果性能要求极其苛刻,有什么更好的优化方案?

又从业务角度而言,问题归结为:防止数据回滚,是不是应该成为每一个程序员在开发分布式系统时必须考虑的标准设计规范,还是,程序员不用管,交给 DBA ?
natsu94
143 天前
强一致性有 raft 这样的协议
https://github.com/HuyuYasumi/raft-kv
sc2yml
143 天前
目前接触到的高频交易系统没有分布式部署,都是高频交易服务器+策略服务器,遇到极端情况交易所托管机房整体故障( AB2 路市电断电,柴发接管我司定义已经是极端情况),断开高频交易服务器与席位的连接,策略服务器执行回档落库脚本,基本都是 DBA 优化,与程序员无关
seansong
142 天前
op 估计没有接触过真正的高频交易系统,闭门造车 ing ? 高频交易系统应该极少选择 java 那一套所谓的分布式部署,不敢说所有,但我接触到的,都没有分布式部署的,会拆分成不同的独立服务,但拆分的原则之一就是没有 op 说的这种依赖问题。

普通系统里面遇到 op 说的这个问题,除了楼上大家说的物理层防御以外,最好的办法就是堆砌硬件,尽量降低强制落盘的时延,fsync 已经是很上层的概念了,fsync 完成并不代表数据一定落盘成功了。纠结这个性能,可能还不如优化一下系统中的其他地方的性能,把这个延时从其他地方抢救回来
ipwx
142 天前
我就不谈金融了。

有没有可能,任何交易指令,你可以在主体处理流程之外,复制一份指令,然后旁路发给 N 个独立的服务器。这样哪怕一台没写成功,其他也写成功了。
iceecream
142 天前
这么多人老说 ups ,ups 只是防止断电,但是如果服务器挂了呢,操作系统 crash ,等造成系统宕机,楼主说的那内存中的脏页数据不也丢了。
PhpBB
142 天前
板鸭这次大断电真的是深刻体会到电视剧里的灾难随时都会来,现代科技其实可以很脆弱
julyclyde
141 天前
@scegg UPS 也有坏的时候啊
想当年天津塘沽联通机房的发电机进楼的线没接上的事故……
UPS 、发电机、油库……每个 unit 单独测试都是 OK 的
scegg
141 天前
@julyclyde 嗯,机房也有塌的时候。对小概率事件的预备程度有多高,只取决于你的客户想出多少钱。
wxf666
140 天前
@scegg #80 楼主的意思不是说,就算提交事务后,数据库脏页还在内存里,每秒才刷新一次,保证落盘到 SSD / 存储卡 / HBA ,在此之前若数据库/系统崩溃,会丢失这一秒数据吗?

感觉 54 楼说的,数据库若能先攒够一堆事务数据(高频系统很容易短期内满足此条件),再批量顺序将脏页写入 SSD / 存储卡 / HBA ,最后才让事务返回结果,可以解决楼主的问题?

即使是随机写入事务,WAL 落盘时也是顺序写入的,有空再慢慢回写到数据库本体就好。

说不定还能合并多条相邻的事务数据,减少随机写入次数,事实上更快回写呢( 1MB 随机写,总体速度比 4KB 随机写快)

比如同一用户(或同一数据页内 ID 相近的多个用户)几秒钟内几百笔交易,若每秒落盘一次,可能需要 10 次 4K 随机写。若有空慢慢回写,可以攒成一次 40K 随机写。。
wxf666
140 天前
@acorngyl #82 嗯。。感觉你说的都是,commit 后,数据库定时将内存里的页缓存(数据/日志等),落盘到 SSD 上?

这样就会有楼主的《 commit 后,落盘前,数据库/系统崩溃,数据丢失》问题?

能否 commit 时,就落盘日志/数据,再结束 commit / 事务呢?

当然,每次 commit 都落盘一次不划算,需要数据库积攒多些事务平摊成本( 4K 顺序写,和 1M 顺序写,速度也是不一样的)


我同意你后面说的,提交事务,先顺序写数据/日志页,有空/崩溃重启后,再慢慢回写到数据库本体上。
scegg
140 天前
@wxf666 理论上,只要事务提交成功,数据库有义务完成事务的持久性( ACID 特性的 D )。至于实际上,为了“提升性能”,可做出妥协。但妥协多少是可以选择的。如果选择不妥协的策略,那么就一定会在操作系统认可的磁盘写入操作完成后,才会返回提交成功。
另,操作系统认可的磁盘写入操作完成并不意味着实际磁盘的写入完成,因为这是存储系统的事,数据库和操作系统管不着了。但只要这存储系统设计靠谱,也意味着它在很大程度上是会完成持久保存的。至于程度有多大,又是一个为了提升性能的妥协程度问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1128883

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX