全文目录
github项目地址:https://github.com/peptos/traffic-shm
Trafic-SHM特点:
1. Traffic-SHM使用sun.misc.Unsafe和FileChannel提供共享内存机制的纯Java实现,需要JDK 1.6+。共享内存是进程间通信的有效机制。 内存映射文件提供动态内存管理功能,允许应用程序以与将虚拟地址空间的物理内存共享段相同的方式访问磁盘上的文件。
2. Traffic-SHM使用非阻塞算法,实现多生产者/单消费者并发队列,可用于构建具有高吞吐量和低延迟的实时系统。
3. Traffic-SHM数据对齐方式为4字节对齐,字节顺序为大端模式。
4. Traffic-SHM提供一个OAOO(ONCE-AND-ONLY-ONCE)保证的FIFO队列。光标只能向前传送,一旦消息成功传递,消息就是AUTOMATIC ACKNOWLEDGMENT,这意味着一旦接收者接收到消息,就会确认消息。
一、内存映射
操作系统使用虚拟内存来进行内存管理。虚拟内存是一个抽象的概念,它为每个为应用进程提供了使用虚拟地址取代物理内存地址进行内存访问的功能。
操作系统通过将一个虚拟内存区域与一个磁盘上的普通文件关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射。Java语言中,可通过FileChannel类的map()方法进行操作,实现过程由操作系统的mmap()实现。
内存映射使用文件系统建立从用户空间直到可用文件系统页的虚拟内存映射。这样做有几个好处:
1. 用户进程把文件数据当作内存,所以无需发布read()或write()系统调用。
2. 当用户进程碰触到映射内存空间,缺页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到磁盘,文件得到更新。
3. 操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理。
4. 数据总是按页对齐的,无需执行缓冲区拷贝。
5. 大型文件使用映射,无需耗费大量内存,即可进行数据拷贝。
二、内存对齐
1. 页对齐 操作系统对内存采用页式管理机制,对于用mmap()映射的普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定。进程能够访问的有效地址大小取决于文件被映射部分的大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。示例如下:
因此,考虑到不同操作系统及处理器架构,内存映射文件的大小及mmap()映射的内存空间大小均需要按页大小进行对齐。
2. 数据类型对齐 简单来讲,数据类型对齐需要满足以下条件:数据所在的内存虚拟地址要能被这个数据的长度所整除。数据域单个数据块以4字节进行对齐。
三、字节顺序
不同的处理器架构使用不同的字节顺序存储数据,目前常见有大端小端两种存储方式。一个多位的整数将按照其存储地址的最低或最高字节排列,如果低位字节存放在内存的低地址端,高位字节存放在内存的高地址端,称为小端模式,反之为大端模式。
以0x0A0B0C0D为例:
小端模式:
1 |
2 |
3 |
4 |
0x0D |
0x0C |
0x0B |
0x0A |
大端模式:
1 |
2 |
3 |
4 |
0x0A |
0x0B |
0x0C |
0x0D |
为了保证在不同操作系统及处理器架构下的可移植性,Trafic-SHM约定按照大端模式存储数据。
四、数据结构
如下图所示,队列由元数据域及数据域两部分组成,公共信息及读写位置指针在元数据域维护,数据块为数据域的最小单位(图有误,Block的顺序为ACK-Length-Payload)。
同时为了节约内存空间,避免频繁映射造成的内存浪费,针对数据域采用了环形队列RingBuffer数据结构。
五、并发控制
可见性 为保证可见性,使用Unsafe的volatile方法对读写位置指针及ACK域进行操作。
原子性 为了保证在多生产者模式下的原子性,对ACK域的一个int类型先后进行了两次无符号short型填充。
六、关键代码
内存映射类MappedFile
关键成员:
类型 |
变量名 |
备注 |
RandomAccessFile |
raf |
随机读写文件 |