基于共享内存的HashMap的思考

基于mmap共享内存在文件中实现既可用于多进程又可用于多线程的无需持久化的并发HashMap,支持多进程并发rehash,我们就叫它SharedHashMap吧!

使用mmap把文件内容映射到进程的虚拟地址空间,在这块虚拟地址空间中实现一个HashMap

  1. 每个进程都会使用mmap把Nodes文件做为内存池映射到自己的虚拟地址空间,内存池由一个userspace spinLocker进行保护,任何一个进程检测到内存池已使用完时,都会发起一次内存池的扩容操作,扩容首先会调用ftruncate对Nodes文件扩展到当前大小2倍,然后使用mremap进行重映射,完成一次内存池的扩容,只有第一个发起扩容的进程才能初始化内存池,后来的进程直接扩容后就可以使用内存池,扩容期间可以对map,进行get,update操作,进行set,delete操作将会自旋,因为当set,delete时要访问内存池,获取内存池的锁,而此时内存池正在扩容,锁被发起扩容的进程lock着。当一个进程进行扩容后,其它进程要及时,感知到这种变化,自己也发起一次扩容,否则在一个未及时扩容的进程的HashMap中访问已扩容后的进程插入的从扩容后的虚拟地址空间中分配出去的节点,未扩容的进程将崩溃
  2. 当进程重启后所有的数据将变的无法访问,因为所有的数据的操作,都是基于之前进程的虚拟地址空间的,对于map中的每一个node,在不同的进程中有不同的虚拟地址,当内存池进行扩容后,已经从内存池中分配出去的node都将失效,扩容后再通过扩容前的地址去访问扩容前的node,进程将收到一个段错误,然后崩掉,因为扩容前的虚拟地址空间有可能已不存在于进程可访问的虚拟地址段中,因此在内存池扩容后,每一个进程都要修正自已的map中每一个的node的虚拟地址,为了解决这种问题,在从内存池分配node时只能分配相对地址,每次获取对象时使用当前的Nodes文件被映射的首地址+相对地址去访问node
  3. 由于扩容导致虚拟地址的改变,在上一行代码中可以正常访问的节点,在下一行代码中可能已经失效,因此代码调试不是像多线程环境中那样顺利,要详细考虑每一行代码中的数据是否已经失效,否则进程将崩溃,一种解决办法就是扩容时强制从上一次映射的虚拟地址开始映射
  4. 多进程并发rehash问题,多进程并发rehash是一种协作关系,由于多进程通信及SharedHashMap设计实现原因,实现并发rehash不是那么容易,所以在此会有一进程专门负责rehash,rehash进程会把Slots文件当做哈希槽mmap到自己的虚拟地址空间,redis使用渐进式rehash,ConcurrentHashMap使用多线程并发rehash, SharedHashMap使用全量rehash,一次性把原map中的数据移动到扩容后的map中,在数据量大的情况下可能会产生一小段时间的不可用,但rehash在整个map的生命周期中并不常见,如果我们能预估数据集大小rehash可能永远不会发生,rehash进程与其它进程使用同一把UserSpace读写自旋锁,通过获取写锁来取得rehash的机会,因此在进行rehash过程中其它进程都将因获取不到读锁而自旋,在rehash后其它进程要及时感知到已经rehash了要进行一次哈希槽的切换,在进行操作map前其它进程会获取到共享的读锁让rehash进程无法进行下一次rehash。因为进程1,2,3可能率先完成哈希槽的切换,此时插入了大量数据,而rehash进程可能会又发起多次rehash,而进程4,5,6可能还正在进行第一次的哈希槽的切换
  5. 锁的粒度控制,jdk1.8的ConcurrentHashMap,已经不再使用分段锁,
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值