为何突然间就“不火”了?
B+ 树中有两种操作可以持久化其中的数据,也就是检查点(Checkpoint)和驱逐(Eviction);其中,前者是按照固定频率触发的,当日志的大小达到了特定的阈值后才会触发,这样可以保证提交日志的大小在固定范围内,而驱逐会从缓存向树中写入脏数据,它也会在缓存达到特定阈值时触发写入。 这种设计更容易受到同步(Synchronization)额外开销的影响,论文在测试中发现只有 18% 的时间用于处理客户端的请求,而其他时间都用于不同的等待,内核中 75% 的时间都在等待 futex 和 yield 等函数调用。 当内存中数据的驱逐不能快速完成时,B 树的性能也会受到影响,论文中的数据表示 WiredTiger 的吞吐量会在延迟期间从 120 Kops/s 降低到 8.5Kops/s,这种巨大的影响持续几秒钟的时间才会恢复。 设计原则 为了利用新存储设备的特性并减少键值存储的 CPU 开销,我们在现代 SSD 上开发的 KVell 会遵循如下所示的设计原则提高键值存储的性能:
不共享数据
在多线程的软件系统中,稍微有常识的人都知道不同线程之间同步数据会对性能带来比较大的影响,让多个线程之间不共享数据就可以避免上述的同步开销,减少线程等待带来的性能损失。 磁盘中的数据结构包含多个层级,每个层级都会包含多个不可变的、排序后的文件,同一个层级中文件的键范围也不会有重叠。为了保证上述特性,LSM 引入了 CPU 和 I/O 密集的操作 — 压缩,如上图所示,压缩会将多个低层级的文件合并成更高层级的文件,保证键值对的顺序并删掉其中重复的键。这也使得 CPU 在新的存储设备上已经成为 LSM 树的主要瓶颈,这种设计让我们在旧设备上花费 CPU 时间保证数据的顺序并降低扫描操作顺序访问磁盘时的延迟。 除了 CPU 成为瓶颈之外,使用 LSM 树的键值存储的负载在数据压缩时会受到显著的影响,论文中的数据表示 RocksDB 在压缩期间的性能可能会降低一个数量级,虽然有一些技术可以缓解数据压缩的影响,但是这些方法在高端的 SSD 上却并不适用。 B 树
B+ 树只在叶节点存储键值对数据,内部的节点只包含用于路由的键,每个叶节点都包含一组排序后的键值对,所有的叶节点会组成方便扫描的链表。最先进的 B+ 树为了实现优异的性能都会依赖缓存,大多数的写操作也都会先写入提交日志再写入缓存,当缓存中的数据被驱逐时,B+ 树中的信息才会被更新。 当查询到版本号信息自后,会把版本号作为消息体的一部分投递到MQ,那在并发的情况下会发生什么情况呢?假设当前的版本号为1:
线程A查询版本号为1,然后投递了版本号为1,消息id为x的消息,于此同时线程B也查询了当前用户版本,数值也为1,然后投递了消息id为Y的消息,这个时候消费端无论是先消费消息X还是消息Y,数据库的版本号都会增加,则导致了另外一个消息由于版本号的不符而消费失败。 对于同一条消息的重复投递来说,这样做确实可以做到幂等性消费,毕竟程序利用数据库的锁机制来保证了一致性。那有什么问题呢? 消息的版本号问题
所有的分布式系统都面临着同样的问题,就是数据的一致性问题,MQ的消费场景也不例外。以上边用户加积分为案例,因为消息的生产者在投递消息的时候需要查询当前的版本号,类似于以下sql 当消费新消息的时候,执行以下类似以下的sql语句,拿到消息是否已经消费过的结果来判断当前消息是否需要重复消费 select count(0) from table where MsgId='消息id' 当然,这里还会有问题,如果只有一个消费者进行消费,不会有任何问题,如果有多个消费者在并行的进行消费,在判断重复消息的时候你会需要锁来保证同样数据的顺序化,这个时候你可能需要分布式锁。 郑重提示 除了生成消息id这种方式之外,网上有很多文章指出可以利用版本号来解决幂等性问题,试问:这种方案又有多少人亲自实践过?今天我们就以给用户添加积分这个案例来庖丁解牛一下这个方案的做法:
(编辑:淮南站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |