Ari Zilka 谈 Ehcache 的进程内堆外缓存 BigMemory

  • Srini Penchikala
  • 王丽娟

2010 年 11 月 29 日

话题:Java架构DevOps语言 & 开发文化 & 方法

Ehcache 的BigMemory提供了一个进程内的堆外缓存,用来存储应用相关的大批量数据。Terracotta上周发布了 BigMemory 模块的 GA 版本,该模块支持 Ehcache 企业版。BigMemory 是 Ehcache 标准 API 的一部分,为 cache 定义两个新属性(overflowToOffHeap 和 maxMemoryOffHeap)就可以使用了,代码片段如下所示:

<cache name="sample-offheap-cache"
    maxElementsInMemory="10000"
    eternal="true"
    memoryStoreEvictionPolicy="LRU"
    overflowToOffHeap="true"
    maxMemoryOffHeap="1G"/>

BigMemory 在内存存储策略上有别于传统的缓存解决方案。它不把数据存储在 Java 堆里,从而避免了 JVM 的 GC 问题。BigMemory 这种特别的存储被称为堆外(Off-Heap)存储。传统的缓存解决方案为了规避这些问题,都将数据分布在缓存节点组成的集群上。BigMemory 提供了一种新的架构选择方案,允许应用运行在堆小于 1G 的 JVM 上,利用堆外的内存来加快数据访问的速度。

InfoQ 有幸采访了 Terracotta 的 CTO Ari Zilka,请他谈了 Ehcache 框架里的 BigMemory 新特性、BigMemory 有助于应用性能提升的用例场景,以及 BigMemory 的局限性。

InfoQ:为 Ehcache 框架添加 BigMemory 特性的主要动机是什么?

主要动机是为了解决我们在 Terracotta 服务器里遇到的 GC 问题。服务器里的 GC 会引起响应时间和大规模 GC 事件出现变化,可能会致使(一级缓存 L1 的)缓存客户端故障转移到备份的 Terracotta 服务器上去。我们意识到这个解决方案的好处后就立即扩大了它的应用范围,包括为独立 Ehcache 追加一个内存存储,这个内存存储后来就发展成了 BigMemory——Ehcache 企业版的一个插件。

InfoQ:BigMemory 堆外存储提供的方式能避免 Java GC 的复杂性,你能谈一谈实现的具体细节么?

BigMemory 把缓存对象存储在 Java 堆之外,但仍然在操作系统的 Java 进程里。所以它仍然是个进程内的缓存,具备所有与此相关的高性能,但它不使用堆,这样就可以给应用配置很小的堆空间,从而避免 GC 问题。BigMemory 使用了 JDK 1.4 引入的DirectByteBuffers。所有的 Java 实现都可以运行 BigMemory,所以任何人都可以使用 BigMemory,而不用更换 JDK。

操作系统内存管理器的功能差不多就要完成了。届时我们会在放入数据时分配内存、移除数据时释放内存,我们能做这些事情是因为 BigMemory 是个缓存,而不是一般用途的 Java 程序。DirectByteBuffers 分配内存很慢,但用起来非常快。所以我们会在启动的时候就从操作系统获取所有需要的内存。

BigMemory 的关键之处在于,怎样判断某个对象不再被使用、关联的内存被释放了,这也是很多人一开始最难理解的地方。对缓存来说,这其实非常简单。Map 主要涉及 put、get 和 remove 操作。我们在放入数据的时候分配内存(malloc),移除数据的时候释放内存(free)。我们实现了一个内存管理器,它使用的是很好理解的计算机科学算法,还有我们专门为此实现的增强。

在被问及 BigMemory 什么情况下能提升应用性能时(从只读、经常读、读写操作来说),Ari 回答说,在“90% 读 /10% 写”的常见情况和“50% 读 /50% 写”的写操作过多的情况下,他们都见过比较好的性能结果。这是因为缓存是进程内的。只读操作会受到分布式缓存的影响。读取活跃数据集要比读取其它数据快很多,那些不活跃的数据必须通过网络获取。

InfoQ:BigMemory 解决方案有什么局限性?

鉴于 BigMemory 是纯 Java 的、进程内的,而且和常见的 JVM、容器兼容,所以它没有明显的局限性。我们找到的最大的内存盒有 384GB 内存,我们在上面测试的结果显示,在 BigMemory 始终有 350GB 内存的空闲情况下,性能都是线性的,没有明显的增长。

我们要向用户强调的限制只有一个,那就是使用堆外存储的话,放置在 BigMemory 中的对象必须进行序列化。对通常就存储在缓存里的那些数据来说,这并不是什么问题。

一旦对象被序列化,在返回 Java 堆的时候必需反序列化才可以使用。这确实是一笔性能开销。因此在没有 GC 的时候,BigMemory 会比堆内存储慢。但 BigMemory 还是要比底层可用的存储快很多,不论是本地磁盘、网络存储,还是 RDBMS 等原本记录数据的系统。

还应指出的是,序列化 / 反序列化的性能开销远没有很多用户想象的那么大。BigMemory 已经针对字节缓冲区做了优化,本身也包含一些优化机制,可以对使用标准 Java 序列化的对象进行优化。举例来说,测试版发布之后添加的那些优化机制能使复杂 Java 对象的性能提升两倍,使 byte 数组的性能提升四倍。Terracotta Server Array 正是用 byte 数组存储数据的。用自定义的序列化则能进一步减少性能开销。

InfoQ 问 Ari,架构师和开发人员在应用中使用 BigMemory 时应该注意哪些最佳实践和问题。Ari 回答说,任何成功的商业应用都要处理伸缩性问题。缓存是最稳妥、最容易实现的解决方案之一。现在比较新颖的是,不用非得引入缓存集群了。

最好的做法就是用新眼光来审视一下你的性能架构,看你能否从大型的进程内缓存获益。BigMemory 可以让架构师对服务器和进程密度进行优化,以满足特定的需求,而不用受制于 Java 的局限性。

最大的问题则是大多数人已经针对 Java 本身的局限性做了优化。比如说,大多数 Ehcache 用户会运行 32 位的 JVM。根据 OS 的不同,32 位 Java 的地址空间会是 2 到 4GB 不等。所以这些用户在用 Java 时就放弃了使用很多内存。应用目前可能都运行在 RAM 很小的硬件上。所以用户要是想使用 BigMemory 来运行 100GB 的进程内缓存,可能就意味着要更换新的硬件,即便现在硬件很便宜。

InfoQ:Ehcache 框架以后的路线图是怎样的?BigMemory 有什么特殊的么?

我们正在开发 Ehcache 和 Terracotta 的下一个版本(代码以澳大利亚弗里曼特尔的别称 Freo 命名),计划本月发布测试版。我们计划在这个版本中添加一系列功能特性和性能增强。比如Ehcache Search,它能让 Ehcache 用户在缓存里进行搜索,就像使用数据库一样。Ehcache Search 已经发布了测试版,文档也已经全部可用了。

至于 BigMemory,我们还在继续提升性能,同时会增加一系列比较实用的增强,例如提供更多的工具,帮助人们更好地理解最适合他们用例的设置。

查看英文原文:Ari Zilka on Ehcache BigMemory

Java架构DevOps语言 & 开发文化 & 方法