1 背景
假如有一个服务的各项 JVM 的配置都比较合理的情况下,它的 GC 情况还是不容乐观。
然后 dump 了一把内存,一顿分析之后发现有 2 个对象特别巨大,占了总存活堆内存的 76.8%。
其中第 1 大对象是本地缓存, 使用 caffine 缓存组件,缓存自动刷新周期设定 1 小时,目的是尽量减少 IO 查询次数;
由于占用空间很大,会导致频繁GC,影响系统性能。
如何能尽量缓存较多的数据,同时避免过大的 GC 压力呢?
可以把缓存对象移到堆外,这样可以不受堆内内存大小的限制;并且堆外内存,并不受 JVM GC 的管控,避免了缓存过大对 GC 的影响。
堆外内存不受堆内内存大小的限制,只受服务器物理内存的大小限制。这三者之间的关系是这样的:物理内存=堆外内存+堆内内存。
2 堆外缓存
为了缓解在高并发,高写入操作下,堆内缓存组件造成的频繁GC问题,堆外缓存应运而生。堆内缓存是受JVM管控的,所以我们不必担心垃圾回收的问题。但是堆外缓存是不受JVM管控的,所以也不受GC的影响导致的应用暂停问题。但是由于堆外缓存的使用,是以byte数组来进行的,所以需要自己进行序列化反序列化操作。目前已知的知名开源项目中,netty4的buffer pool采用了堆外缓存实现,具体的比对信息截图如下:
img带有Direct字眼的即为offheap堆外Buffer,x轴为分配的内存大小,Y轴为耗时。从上面可以看出,小块内存分配,JVM要稍微优秀一点;但是大块内存分配,明显的堆外缓存要优秀一些。由于堆外Buffer操作不受GC影响,实际上性能更好一些。但是需要的垃圾回收管控也需要自己去做,要麻烦很多。
2 开源堆外缓存组件 OHC
2.1 OHC 介绍
OHC 全称为 off-heap-cache,即堆外缓存,是 2015 年针对 Apache Cassandra 开发的缓存框架,后来从 Cassandra 项目中独立出来,成为单独的类库,其项目地址为:https://github.com/snazy/ohc
其特性如下:
-
数据存储在堆外,只有少量元数据存储堆内,不影响 GC
-
支持为每个缓存项设置过期时间
-
支持配置 LRU、W_TinyLFU 驱逐策略
-
能够维护大量的缓存条目
-
支持异步加载缓存
-
读写速度在微秒级别
OHC具有低延迟、容量大、不影响GC的特性,并且支持使用方根据自身业务需求进行灵活配置。
2.2 OHC 用法
快速开始:
OHCache ohCache = OHCacheBuilder.newBuilder().
keySerializer(yourKeySerializer)
.valueSerializer(yourValueSerializer)
.build();
可选配置项:
图片在我们的服务中,设置 capacity 容量 12G,segmentCount 分段数 1024,序列化协议使用 kryo。
2.3 优化效果
切换到堆外缓存后,服务 YGC 降低到了 800ms / 每分钟,端到端的整体吞吐量上涨了约 20%。
2.4 缺点
缺点主要是序列化、反序列化,如果数据查询特别高频,那反序列化的成本就不可忽略了。
3 应用场景
OHC适合将离线数据进行本地缓存,从而节省访问远程数据库的时间。
网上看到的具体应用场景,主要是在推荐服务中用到,比如 Java堆外缓存OHC在马蜂窝推荐引擎的应用 , vivo的推荐服务计算中也有用到。
网友评论