重新认识 Java 中的内存映射(mmap)

mmap 基础概念

mmap 是一种内存映射文件的方法,即将一个文件映射到进程的地址空间,实现文件磁盘地址和一段进程虚拟地址的映射。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页到对应的文件磁盘上,即完成了对文件的操作而不必再调用 read,write 等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

mmap工作原理

操作系统提供了这么一系列 mmap 的配套函数

1
2
3
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap( void * addr, size_t len);
int msync( void *addr, size_t len, int flags);

【参赛总结】第二届云原生编程挑战赛-冷热读写场景的RocketMQ存储系统设计

前言

人总是这样,年少时,怨恨自己年少,年迈时,怨恨自己年迈,就连参加一场比赛,都会纠结,工作太忙怎么办,周末休息怎么办,成年人的任性往往就在那一瞬间,我只是单纯地想经历一场酣畅的性能挑战赛。所以,云原生挑战赛,我来了,Kirito 带着他的公众号来了。

读完寥寥数百多字的赛题描述,四分之一炷香之后一个灵感出现在脑海中,本以为这个灵感是开篇,没想到却是终章。临近结束,测试出了缓存命中率更高的方案,但评测已经没有了日志,在茫茫的方案之中,我错过了最大的那一颗麦穗,但在一个月不长不短的竞赛中,我挑选到了一颗不错的麦穗,从此只有眼前路,没有身后身,最终侥幸跑出了内部赛第一的成绩。

传统存储引擎类型的比赛,主要是围绕着两种存储介质:SSD 和 DRAM,不知道这俩有没有熬过七年之痒,Intel 就已经引入了第三类存储介质:AEP(PMem 的一种实现)。AEP 的出现,让原本各司其职的 SSD 和 DRAM 关系变得若即若离起来,它既可以当做 DRAM 用,也可以当做 SSD 用。蕴含在赛题中的”冷热存储“这一关键词,为后续风起云涌的赛程埋下了伏笔,同时给了 AEP 一个名分。

AEP 这种存储介质不是第一次出现在我眼前,在 ADB 比赛中就遇到过它,此次比赛开始时,脑子里面对它仅存的印象便是”快”。这个快是以 SSD 为参照物,无论是读还是写,都高出传统 SSD 1~n 个数量级。但更多的认知,只能用 SSD 来类比,AEP 特性的理解和使用方法,无疑是这次的决胜点之一。

曾经的我喜欢问,现在的我喜欢试。一副键盘,一个深夜,我窥探到了 AEP 的奥秘,多线程读写必不可少,读取速度和写入速度近似 DRAM,但细究之下写比读慢,从整体吞吐来看,DRAM 的读写性能略优于 AEP,但 DRAM 和 AEP 的读写都比 SSD 快得多的多。我的麦穗也有了初步的模样:第一优先级是降低 SSD 命中率,在此基础上,提高 DRAM 命中率,AEP 起到平衡的效果,初期不用特别顾忌 AEP 和 DRAM 的命中比例。


聊聊Unsafe的一些使用技巧

前言

记得初学 Java 那会,刚学完语法基础,就接触到了反射这个 Java 提供的特性,尽管在现在看来,这是非常基础的知识点,但那时候无疑是兴奋的,瞬间觉得自己脱离了“Java 初学者”的队伍。随着工作经验的积累,我也逐渐学习到了很多类似的让我为之而兴奋的知识点,Unsafe 的使用技巧无疑便是其中一个。

sun.misc.Unsafe 是 JDK 原生提供的一个工具类,包含了很多在 Java 语言看来很 cool 的操作,例如内存分配与回收、CAS 操作、类实例化、内存屏障等。正如其命名一样,由于其可以直接操作内存,执行底层系统调用,其提供的操作也是比较危险的。Unsafe 在扩展 Java 语言表达能力、便于在更高层(Java层)代码里实现原本要在更低层(C层)实现的核心库功能上起到了很大的作用。

从 JDK9 开始,Java 模块化设计的限制,使得非标准库的模块都无法访问到 sun.misc.Unsafe。但在 JDK8 中,我们仍然可以直接操作 Unsafe,再不学习,后面可能就没机会了。


使用堆内内存HeapByteBuffer的注意事项

前言

国庆假期一眨眼就过去了,本来在家躺平的很舒服,没怎么肝云原生编程挑战赛,传送门:https://tianchi.aliyun.com/s/8bf1fe4ae2aea736e692c31c6952042d ,偏偏对手们假期开始卷起来了,眼看就要被人反超了,吓得我赶紧继续优化了。比赛大概还有一个月才结束,Kirito 的详细方案也会在比赛结束后分享,这期间我会分享一些比赛中的一些通用优化或者细节知识点,例如本文就是这么一个例子。

趁着假期最后一天,分享一个很多人容易踩得一个坑:HeapByteBuffer 的使用问题。我们都知道 NIO 分装了 ByteBuffer 接口,使得 filechannel 的文件 IO API 变得非常的简单。ByteBuffer 主要有两个实现类

  • HeapByteBuffer 堆内内存
  • DirectByteBuffer 堆外内存

按我的个人经验,大多数情况,无论是读操作还是写操作,我都倾向于使用 DirectByteBuffer,主要是因为 HeapByteBuffer 在和 FileChannel 交互时,可能会有一些出乎大家意料的内部操作,也就是这篇文章的标题中提到的注意事项,这里先卖个关子。

先来看看这次比赛为什么要用到 HeapByteBuffer 呢?

原因一:赛题需要设计分级存储,并且提供了 6G 堆内内存 + 2G 堆外内存,一个最直接的思路便是使用内存来存储热点数据,而内存存储数据最方便的数据结构便是 ByteBuffer 了。

原因二:由于堆内 6G 远大于堆外 2G,且 JVM 参数不能调整,所以要想利用好堆内富余的内存去做缓存,非 HeapByteBuffer 莫属了。

可能有一些读者并没有关注赛题,我这里简化一下前言,可以直接理解为:有一块 2G 的 HeapByteBuffer 用于文件 IO,我们该如何利用。


文件 IO 中如何保证掉电不丢失数据

前言

好久没有分享文件 IO 的小技巧了,依稀记得上次分享还是在上次。

第二届云原生编程挑战赛正在火热进行中,Kirito 也在做《针对冷热读写场景的RocketMQ存储系统设计》这个题目,不过参与的是内部赛道,没法跟外部的小伙伴们一起排名了。

众所周知,存储设计离不开文件 IO,将数据存储到文件中进行持久化,是大多数消息队列、数据库系统的常规操作。在比赛中,为了更贴近实际的生产场景,往往也会引入正确性检测阶段,以避免让选手设计一些仅仅支持内存行为的代码逻辑。试想一下,RocketMQ 或者 Mysql 在宕机之后因为索引丢失,而导致数据无法查询,这该是多么可怕的一件事!

正确性检测要求我们写入的数据能够被查询出来,没有丢失,按照我个人的参赛经验,通常分为三种级别

  • 进程正常退出或者进程被 kill -15 中断
  • 进程被 kill -9 中断
  • 系统掉电

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×