文章收录地址:Java-Bang
专一于零碎架构、高可用、高性能、高并发类技术分享
Kafka 依赖于文件系统(更底层地来说就是磁盘)来存储和缓存音讯。在咱们的印象中,对于各个存储介质的速度认知大体同下图所示的雷同,层级越高代表速度越快。很显然,磁盘处于一个比拟难堪的地位,这不禁让咱们狐疑 Kafka 采纳这种长久化模式是否提供有竞争力的性能。在传统的消息中间件 RabbitMQ 中,就应用内存作为默认的存储介质,而磁盘作为备选介质,以此实现高吞吐和低提早的个性。然而,事实上磁盘能够比咱们料想的要快,也可能比咱们料想的要慢,这齐全取决于咱们如何应用它。
无关测试结果表明,一个由 6 块 7200r/min 的 RAID-5 阵列组成的磁盘簇的线性(程序)写入速度能够达到 600MB/s,而随机写入速度只有 100KB/s,两者性能相差 6000 倍。操作系统能够针对线性读写做深层次的优化,比方预读(read-ahead,提前将一个比拟大的磁盘块读入内存)和后写(write-behind,将很多小的逻辑写操作合并起来组成一个大的物理写操作)技术。程序写盘的速度不仅比随机写盘的速度快,而且也比随机写内存的速度快,如下图所示。
页缓存的魅力
Kafka 在设计时采纳了文件追加的形式来写入音讯 ,即只能在日志文件的尾部追加新的音讯,并且也不容许批改已写入的音讯,这种形式属于典型的 程序写盘 的操作,所以就算 Kafka 应用磁盘作为存储介质,它所能承载的吞吐量也不容小觑。但这并不是让 Kafka 在性能上具备足够竞争力的惟一因素,咱们无妨持续剖析。
页缓存是操作系统实现的一种次要的磁盘缓存,以此用来缩小对磁盘 I/O 的操作。具体来说,就是把磁盘中的数据缓存到内存中,把对磁盘的拜访变为对内存的拜访。为了补救性能上的差别,古代操作系统越来越“激进地”将内存作为磁盘缓存,甚至会十分乐意将所有可用的内存用作磁盘缓存,这样当内存回收时也简直没有性能损失,所有对于磁盘的读写也将经由对立的缓存。
当一个过程筹备读取磁盘上的文件内容时,操作系统会先查看待读取的数据所在的页(page)是否在页缓存(pagecache)中,如果存在(命中)则间接返回数据,从而防止了对物理磁盘的 I/O 操作;如果没有命中,则操作系统会向磁盘发动读取申请并将读取的数据页存入页缓存,之后再将数据返回给过程。
同样,如果一个过程须要将数据写入磁盘,那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在,则会先在页缓存中增加相应的页,最初将数据写入对应的页。被批改过后的页也就变成了脏页,操作系统会在适合的工夫把脏页中的数据写入磁盘,以保持数据的一致性。
Linux 操作系统中的 vm.dirty_background_ratio 参数用来指定当脏页数量达到零碎内存的百分之多少之后就会触发 pdflush/flush/kdmflush 等后盾回写过程的运行来解决脏页,个别设置为小于 10 的值即可,但不倡议设置为 0。与这个参数对应的还有一个 vm.dirty_ratio 参数,它用来指定当脏页数量达到零碎内存的百分之多少之后就不得不开始对脏页进行解决,在此过程中,新的 I/O 申请会被阻挡直至所有脏页被冲刷到磁盘中。对脏页有趣味的读者还能够自行查阅 vm.dirty_expire_centisecs、vm.dirty_writeback.centisecs 等参数的应用阐明。
对一个过程而言,它会在过程外部缓存解决所需的数据,然而这些数据有可能还缓存在操作系统的页缓存中,因而同一份数据有可能被缓存了两次。并且,除非应用 Direct I/O 的形式,否则页缓存很难被禁止。此外,用过 Java 的人个别都晓得两点事实:对象的内存开销十分大,通常会是实在数据大小的几倍甚至更多,空间使用率低下;Java 的垃圾回收会随着堆内数据的增多而变得越来越慢。基于这些因素,应用文件系统并依赖于页缓存的做法显著要优于保护一个过程内缓存或其余构造,至多咱们能够省去了一份过程外部的缓存耗费,同时还能够通过结构紧凑的字节码来代替应用对象的形式以节俭更多的空间。如此,咱们能够在 32GB 的机器上应用 28GB 至 30GB 的内存而不必放心 GC 所带来的性能问题。
此外,即便 Kafka 服务重启,页缓存还是会放弃无效,然而过程内的缓存却须要重建。这样也极大地简化了代码逻辑,因为保护页缓存和文件之间的一致性交由操作系统来负责,这样会比过程内保护更加平安无效。
Kafka 中大量应用了页缓存,这是 Kafka 实现高吞吐的重要因素之一。尽管音讯都是先被写入页缓存,而后由操作系统负责具体的刷盘工作的,但在 Kafka 中同样提供了同步刷盘及间断性强制刷盘(fsync)的性能,这些性能能够通过 log.flush.interval.messages、log.flush.interval.ms 等参数来管制。
同步刷盘能够进步音讯的可靠性,避免因为机器掉电等异样造成处于页缓存而没有及时写入磁盘的音讯失落。不过笔者并不倡议这么做,刷盘工作就应交由操作系统去调配,音讯的可靠性应该由多正本机制来保障,而不是由同步刷盘这种重大影响性能的行为来保障。
Linux 零碎会应用磁盘的一部分作为 swap 分区,这样能够进行过程的调度:把以后非沉闷的过程调入 swap 分区,以此把内存空进去让给沉闷的过程。对大量应用零碎页缓存的 Kafka 而言,该当尽量避免这种内存的替换,否则会对它各方面的性能产生很大的负面影响。
咱们能够通过批改 vm.swappiness 参数(Linux 零碎参数)来进行调节。vm.swappiness 参数的下限为 100,它示意踊跃地应用 swap 分区,并把内存上的数据及时地搬运到 swap 分区中;vm.swappiness 参数的上限为 0,示意在任何状况下都不要产生替换(vm.swappiness = 0 的含意在不同版本的 Linux 内核中不太雷同,这里采纳的是变更后的最新解释),这样一来,当内存耗尽时会依据肯定的规定忽然停止某些过程。笔者倡议将这个参数的值设置为 1,这样保留了 swap 的机制而又最大限度地限度了它对 Kafka 性能的影响。