JetCache 缓存开源组件设计精要

作者:张隆 阿里电影演出技术中心团队
本文将为大家介绍 JetCache 缓存开源组件的前世今生,并剖析了 JetCache 的工作原理及设计优势。
一、JetCache 的前世今生

1.1 诞生-阿里彩票 JetCache 的伊甸园

2013 年,JetCache 诞生于 [ 阿里彩票 ],作者是 [ huangli ] 凭借得天独厚的 Tair 支持和丰富的 Spring 生态注解支持,赢得了大家的喜爱。
2015 年,随着 SpringBoot 的大热和集团内 PandoraBoot 的彻底铺开,JetCache 以 Starter 的形式实现了扩展,优化了配置项,在架构设计和性能上更上一层楼。
2015 年同年,JetCache 开源至 Github,作为 alibaba 的开源缓存框架,其易用性和设计先进性吸引了大批国内外用户,截止当前在 github 上累计 3.7k star,870 fork。
2018 年 JetCache 最大版本更新,对整体的设计进行了调整,修改了默认的序列化方式,集成支持了 SpringData,RedisLettuce,Redisson 等更加高效以及功能更加灵活且高级的三方 SDK。
1.2 整合-开源界大放异彩

JetCache 原生支持的远程缓存是 Tair,但是 Tair 在集团外并不可用。JetCache 为了拥抱开源,实现了时下主流的 GuavaCache, CaffeineCache, Redis,MemCache 基本覆盖了国内的主流缓存中间件。
在功能性方面,JetCache 满足了用户一行注解解决 Method 缓存的刚需,同时也能通过叠加注解的方式非常高效的处理缓存穿透,缓存击穿,缓存雪崩,缓存失效等经典分布式缓存的问题,这让用户充分体验到了缓存框架的效率优势和设计先进性。
在扩展性方面,JetCache 满足了用户一行注解解决 Method 缓存的刚需,也提供了优秀的扩展能力。想要实现一个新的 Cache 类型,只需要实现 AbstractEmbeddedCache 或者 AbstractExternalCache 就可以以非常低廉的成本实现一个新的缓存框架。
1.3 挑战-SpringCache 江湖地位
在 2015 年最火的框架是 SpringBoot,SpringBoot 提供了非常丰富的组件支持以及模块化的组件管理,其中就包括基于 JSR-107--JCacheAPI 实现的 SpringCache 框架。
SpringCache 框架很好的实现了 JCacheAPI,在当时占据了非常有力的位置,几乎所有的 SpringBoot 初创项目,都选择了使用 SpringCache 来作为他们的第一个缓存框架。但随着软件工程的规模越来越大,分布式场景的经典问题也接踵而至,显然 SpringCache 在应对分布式环境的经典问题时显得太过于稚嫩。
对于分布式场景,缓存穿透,缓存击穿,缓存雪崩 等经典问题,缺少足够成熟的方案。
高级特性上,如 分布式锁,多级缓存滑动窗口,缓存序列化,异步 API 支持等实际工作场景经常会需要用到的核心能力,要么没有,要么不够用。
对于扩展性上,设计的不够开放和正交,很难低成本的完成一些高级功能的扩展。
JetCache 在这方面做的就不错,并且在迁移缓存方面基本上可以做到换注解平替,所以一旦工程规模达到一定量级,很多架构师会选择从 SpringCache 的方式切换到 JetCache 上。
二、JetCache 是如何工作的
完整的组件串联文档:
https://app.heptabase.com/w/db02907915c401c6e33ddcc47e4d67a589047a846be16f30de1644501d939787

2.1 JSR-107--缓存 JCache 标准抽象实现
Java 在 2012 的 JSR-107 协议中新增了关于缓存的抽象设计标准--JCache。

2.2 丰富注解-无侵入抽象设计

2.3 启动器和配置-Bean 方式

2.4 注解模式-AOP-缓存
基于 AOP 的方法级缓存,最常用最直观的 CacheAside 模式。
2.5 注解模式-Cache-API 缓存
基于 CacheAPI 的缓存形式,复杂场景下最灵活的模式。
2.6 高级 API 模式-手动创建 CacheAPI
2.7 Cache 基础缓存操作
2.8 分布式-缓存穿透

分布式场景下的热点数据通常都保存在缓存当中,以减少数据库的压力,提升服务的性能。
缓存击穿是指,攻击者利用随机访问的方式短时间大量的访问不存在的数据,由于数据不存在,所以缓存中查不到,请求越过缓存层直达数据库,造成数据库的压力激增。
通常的解法有:[空值缓存] 及 [布隆过滤器]
JetCache 使用了较为轻量级的 [空值缓存] 方式,来解决这个问题。
@Cached(cacheNullValue=true)
、@CreateCache(cacheNullValue=true)
2.9 分布式-缓存击穿

CacheAside 模式的缓存由于本身有淘汰策略,在数据失效后,缓存组件会直接访问数据库尝试重建缓存。
在大规模分布式热点的情况下,一旦热点数据失效,会有大量的请求同时尝试重建缓存,这不但会导致资源浪费,更加危险的是会造成数据库瞬时极大的压力。
JetCache 通过注解
@CachePenetrationProtect
实现了 JVM 内存锁级的击穿保护,使并发重建的请求限制到可控范围。( 如果数据利用率高还可以使用@CacheRefresh
的方式来实现基于分布式锁的缓存重建能力 )
2.10 分布式-缓存雪崩

缓存雪崩与缓存击穿类似,但是情况更为危机后果更为严重,有可能导致整个集群服务瘫痪。
当大量热点缓存同时失效的时候,大量的缓存重建请求会直达数据库,造成服务节点瘫痪形成服务雪崩。
缓存雪崩的处理方式较为复杂,但简单来说:
可以建立多级缓存,通过设置不同的过期时间,形成重叠数据滑动窗口。
通过服务主动维护异步任务的形式,维护一块永固缓存,防止热点失效。
JetCache 可以通过
多级缓存
来避免这种情况。JetCache 还提供了
@CacheRefresh
和CacheLoader
的方式,使服务有能力创建内建的时间块任务,来达到维护分布式环境下永固缓存的目的。
2.11 分布式-缓存失效/更新
缓存数据也需要维护,尤其是缓存和实际数据不一致的情况下。
例如用户数据,就非常需要缓存失效和缓存更新的能力,及时的在用户做了数据操作之后更新公共缓存的数据。
JetCache 通过
@CacheInvalid
和@CacheUpdate
提供了这种能力,极大程度的避免了缓存数据不一致的情况,同时也增强了缓存操作的灵活性。
三、JetCache 框架设计剖析优势有哪些?
3.1 支持多种 KV 序列化方式

CacheKey Convertor :用来进行缓存 Key 的加工处理
环境隔离: CacheKey 在影演使用最广泛方式,抽象实现环境前缀 Convertor 就可以当前环境进行缓存前缀的拼接,从而达到数据隔离的目的。
长短缓存: 长短缓存通常使用对象缓存作为 Key,为了容灾短缓存和长缓存通常使用了不同的缓存 Key。通过实现长短缓存 Convertor 可以实现相同对象,可以控制长、短缓存的 Key 使用对象中的不同属性构造,从而达到短缓存提升性能,长缓存降级的目的。
ValueEncode、ValueDecode:用来提升缓存性能的绝佳方式
高性能序列化:选择 JavaSerialize、kyro、Kyro5 的序列化方式可以极大程度的提升我们系统对性能的要求,很适合应对高并发环境的大流量压力。
兼容性序列化:选择 JSON(FastJson、FastJson2、Jackson)的方式,可以为缓存提供良好的兼容性。在架构设计的初期,完全可以采用这种方式来实现平稳迭代。
加密序列化:当我们使用外部数据库的时候,我们可以自己实现 ValueEncode 和 ValueDecode 来保障我们数据的安全。
3.2 支持多种本地,远程缓存

3.3 多级缓存-乐高积木

长短缓存:通过多级缓存加上 KeyConvertor 可以快速构建成本最低效率最高的长短缓存组件。
用户缓存:互动用户数据很多,配合用户路由,可以结合 LocalCache + LDB 的方式既保证数据的可靠性,又能将性能从 10ms -> 1ms 级。
自定义多级“缓存”:由于 JetCache 缓存的实现相当方便,我们甚至可以实现 Mysql,Opensearch 的 Cache 实现,并且把它组转到多级缓存之中,形成一种结构稳固的数据读写组件。
3.4 高级特性-加载器

3.5 高级特性-监听器

官方实现-数据报告
效果:

四、影演之路:影演如何发展了 JetCache
Jetcache 在开源界如此火,离不开它遵循了 JSR107 标准,遵从于原则的设计和对原则的扩充使得它在学习效率上非常高效,代码结构上也非常优秀,并且它也在开放性和扩展性下足了功夫,真正实现了架构上的 ”正交“。
在电影演出 BU 内部,由于要应对业务的复杂性,所以需要针对 Jetcache 做一些比较定制化的扩展,其中有关于核心底层 tair 的支持,也有关于分布式场景管理的诉求,更有对业务瓶颈挑战的通用设计。
通过这些新的场景设计,我们极大的丰富了 Jetcache 的应用场景以及让它重新再集团中间件的环境之下,长出了新的分支,非常好的支撑的业务发展。
4.1 通用高并发三级缓存熔断组件

4.2 缓存后置写(Cache Write-Back)

缓存后置写是一种 Cache Write-Back 模式的实现:
1)缓存后置写由 JetCache 的 Monitor 来实现活跃事件的监控以及记录,每当有事件产生,后置写监控器就会被触发。
2)将需要缓存后置写的 Cache 实例通过 Config.Monitor 的方式添加好默认后置写监控器。
3)活跃 Event 将会被不同的 缓存后置写实现捕获,并会将 CacheKey 缓存在一个唯一分布式队列中,等待调度。
4)我们通过了 ScheduleX 实现了分布式调度器,每分钟都会进行触发(当然每个后置写实现可能会有不同的触发频率)
目前影演使用缓存后置写实现了非常多的实用应用,包括:
影演评分数据准实时合并入库,同步至淘票票,大麦三方业务库。( 准实时并发写方案,数据同步方案)
线上、预发缓存准实时同步。 (环境数据一致性)
数据变更对比,趋势数据记录。 ( 数据对账,数据趋势图 )
本地缓存广播器。( 本地缓存一致性,避免数据波动)
4.3 本地缓存广播器(LocalCache Distribute)

4.4 稀疏列表缓存实现(MultiListCache)


五、面向未来:JetCache 还有哪些不足


版权声明: 本文为 InfoQ 作者【阿里技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/cac316dcf2db8cc3d029c1065】。文章转载请联系作者。
评论