v:* { }
o:* { }
w:* { }
.shape { }p.MsoNormal,li.MsoNormal,div.MsoNormal { margin: 0cm; margin-bottom: .0001pt; text-align: justify; font-size: 10.5pt; font-family: "Calibri", "sans-serif" }
h2 { margin-top: 13.0pt; margin-right: 0cm; margin-bottom: 13.0pt; margin-left: 0cm; text-align: justify; line-height: 173%; page-break-after: avoid; font-size: 16.0pt; font-family: "Calibri Light", "sans-serif" }
h3 { margin-top: 13.0pt; margin-right: 0cm; margin-bottom: 13.0pt; margin-left: 0cm; text-align: justify; line-height: 173%; page-break-after: avoid; font-size: 16.0pt; font-family: "Calibri", "sans-serif" }
span.tag { }
span.1
{ }
span.attribute { }
span.value { }
span.2Char
{ font-family: "Calibri Light", "sans-serif"; font-weight: bold }
span.3Char
{ font-weight: bold }
.MsoChpDefault { font-family: "Calibri", "sans-serif" }
div.WordSection1 { }table.MsoNormalTable { font-size: 10.5pt; font-family: "Calibri", "sans-serif" }

1 奇怪的现象

在使用Hazelcast的Eviction时,发现观察到的现象与想象的不同。按照官方文档介绍,Eviction有这样几个配置选项:

<hazelcast>

<map name="default">

...

<time-to-live-seconds>0</time-to-live-seconds>

<max-idle-seconds>0</max-idle-seconds>

<eviction-policy>LRU</eviction-policy>

<max-size policy="PER_NODE">5000</max-size>

<eviction-percentage>25</eviction-percentage>

...

</map>

</hazelcast>

看后三项,按照参数的描述应为:当每个结点的entry数达到5000时,使用LRU策略,剔除25%的entry,即1250个。可是观察到的现象一般是entry数达到4200左右就止步不前,继续大量高并发的insert测试,既不会增长也不会减少。这是怎么回事?而且相比Redis,Hazelcast在达到eviction临界条件后继续并发插入和读写时,性能表现依旧良好,就像没有发生eviction一样。是使用了多线程还是什么神奇的算法?源码之前,了无秘密,还是从代码中寻找答案吧。

2 代码剖析

我们客户端使用的,也是最顶层的API,就是IMap了,这里以比put()更为高效的set()作为分析的起点。后面其实能够看到,因为使用了命令模式(command),两者都是继承一个父类,evict都是在一个地方触发的。

2.1 MapProxyImpl包装器

IMap的直接实现类是MapProxyImpl,但它只是个Wrapper,负责转换key和value。

2.2 MapProxySupport封装命令对象

真正的实现都是在它继承的MapProxySupport类中,例如set()调用的setInternal()就能在Support类中找到。各种internal方法将操作包装成Operation类,这方便了远程调用的实现,例如set()要执行的结点不在本地。

2.3 SetOperation命令模式

invokeOperation()中会确定操作应该在哪个分区执行,这个分区位于哪个结点上。这里Hazelcast维护了一个线程池,每个分区都有对应的线程去执行本分区的操作。因为过程比较复杂,所以这里直接略过,继续关注我们重点想知道的eviction过程的实现。那么直接看一下SetOperation中的逻辑。SetOperation很简单,直接调用RecordStore保存键值对,但afterRun()中有一些隐含的后处理。

2.4 BasePutOperation触发eviction

果然,在afterRun()中除了广播事件、使Near缓存失效外,还有触发eviction过程。Evict()调用的就是RecordStore的evictEntries()方法。

2.5 AbstractEvictableRecordStore控制eviction

真正的evict控制逻辑就在这里。首先,shouldEvict()会判断是否满足了我们之前配置的eviction的触发条件,如PER_NODE=5000。如果满足则调用removeEvictableRecords()开始剔除数据。

2.6 EvictionOperator事有蹊跷

最终removeEvictableRecords调用的是EvictionOperator,具体的实现都在这里。但仔细看这段代码却看不出有什么高明之处,只是简单地迭代RecordStore的记录,将满足条件的entry剔除掉。既没用多线程,也没什么特殊的算法,这到底是怎么回事?

2.7 答案揭晓

谜底其实就在Operation类对应的RecordStore初始化上。我们知道,默认情况下,Hazelcast将map分为271个partition。其实RecordStore也是按这些partition划分的,而不是使用一个大的RecordStore。所以从BasePutOperation的evict()到后续处理的都只是当前key对应分区的RecordStore。也就是说:当key要被处理时,Eviction发生在对应的partition里,而不会evict所有数据的25%(Redis就是处理database中的所有数据,所以延迟会有所增加)。所以,当我们继续压力测试时,不断有key继续插入,这些分区就会不断发生eviction,导致整体的内存使用会保持不变。

3 类图全貌

梳理了上面的执行流程后,我们最后整理一下这些类之间的关系。

Hazelcast源码剖析之Eviction的更多相关文章

  1. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  2. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

  3. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  4. 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析

    项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...

  5. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  6. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

  7. 自己实现多线程的socket,socketserver源码剖析

    1,IO多路复用 三种多路复用的机制:select.poll.epoll 用的多的两个:select和epoll 简单的说就是:1,select和poll所有平台都支持,epoll只有linux支持2 ...

  8. Java多线程9:ThreadLocal源码剖析

    ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...

  9. JS魔法堂:mmDeferred源码剖析

    一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...

随机推荐

  1. win10+ ubuntu12.04双系统安装教程与遇到的问题

    1. 准备ISO. 参考:网站http://mirrors.ustc.edu.cn/ubuntu-releases/precise/ 下载 ubuntu-12.04.5-desktop-amd64.i ...

  2. Python模块 - re

    Python 的 re 模块(Regular Expression 正则表达式)提供各种正则表达式的匹配操作,在文本解析.复杂字符串分析和信息提取时是一个非常有用的工具,下面我主要总结了re的常用方法 ...

  3. Spring Cloud Eureka 自我保护机制

    Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果 ...

  4. 浅谈轮播图(原生JavaScript实现)

    现在各种轮播图插件,玲琅满目,用起来也非常方便,通常只需要选择元素然后传入参数就可以了.但是,和授人以鱼不如授人以渔一样的道理,不管怎样最基本的轮播图原理还是应当掌握的.这样不仅有利于我们自己写出来满 ...

  5. js在光标处插入内容

    //场景一 简易的页面可以这样写var range = window.getSelection().getRangeAt(0);range.insertNode(document.createText ...

  6. bzoj 3191: [JLOI2013]卡牌游戏

    Description N个人坐成一圈玩游戏.一开始我们把所有玩家按顺时针从1到N编号.首先第一回合是玩家1作为庄家.每个回合庄家都会随机(即按相等的概率)从卡牌堆里选择一张卡片,假设卡片上的数字为X ...

  7. 【USACO】 录制唱片

    题目描述 贝西想从奶牛摇滚乐队的 N 首歌里挑出一些录制一套专辑.专辑分 K 张唱片,每张唱片可容纳总长为 C 分钟的歌曲.第 i 首歌的长度为 T i 分钟,录制唱片时,唱片之间的歌曲需要保持原本的 ...

  8. [BZOJ]3926 诸神眷顾的幻想乡(ZJOI2015)

    听说大佬们都会后缀自动机. 小C看完SAM,想找个裸题练习一下模板.听说这题还是陈老师出的?(羊毛出在羊身上) Description  幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生 ...

  9. Spring学习笔记1——入门

    Spring是一个基于IOC和AOP的结构J2EE系统的框架 IOC 反转控制 是Spring的基础,Inversion Of Control 简单说就是创建对象由以前的程序员自己new 构造方法来调 ...

  10. 如何在Google上下载高清原图

    在我们学习和生活中常常一些高清图片作为相关的素材,比如制作PPT.写博文.制作视频都需要大量图片.我们常常会在百度上下载一些图片,但是百度上提供的图片存在很多问题:存在水印.清晰度不够等.而Googl ...