
面试考场上我见过太多人在JCache这道题上翻车了。前阵子帮团队面一个高级Java候选人简历上写着“精通分布式缓存”我问他“JCacheJSR-107定义了哪两种主要的缓存存储模式”他很流畅地说“LOCAL本地缓存和PARTITION分区缓存一个存在本机一个分散到多个节点。”听起来没毛病但我接着问了一句“那分区模式下一个key请求进来客户端怎么知道该找哪个节点”他卡住了支支吾吾半天说不出一致性哈希。这就是典型的分层背过概念没理解机制。这道题表面上考的是两个英文名词实际上考的是你对“缓存数据在分布式环境下怎么存、怎么找、怎么容错”这一整套逻辑的掌握程度。这篇文章我打算把LOCAL和PARTITIONED从规范定义到实现原理、从选型逻辑到面试回答框架完整拆一遍不管你是在准备面试还是正在设计缓存方案都能直接拿去用。1. 从面试翻车现场说起Topology到底在考什么1.1 JCacheJSR-107解决的是Java缓存生态的碎片化问题在JCache出现之前Java里的缓存方案是各占山头的。Ehcache、Guava Cache、Caffeine、Hazelcast、Redis的客户端库每个都有自己的API用法千差万别。今天你基于Ehcache写了一堆缓存存取代码明天项目组说要换Caffeine你会发现缓存工具类的接口全得改业务代码跟着遭殃。这种感觉就像换了数据库DAO层全部重写一样痛苦。JSR-107就是日本JCP组织在2014年发布的一套统一缓存规范小名叫JCache。它的核心目标是把缓存API标准化CachingProvider负责提供缓存实现CacheManager管理缓存的生命周期Cache就是你业务里最常用的那个键值对存储接口再加上过期策略ExpiryPolicy、缓存加载器CacheLoader、缓存写入器CacheWriter和事件监听器CacheEntryListener构成了一套完整的能力矩阵。这套规范落地之后你写业务代码时只需要依赖javax.cache的标准接口至于底层是Ehcache 3还是Hazelcast还是Infinispan对代码来说是透明的。Spring Cache的注解抽象之所以能和JCache互通也正是因为底层接了这套标准API。理解了这层背景你才能明白Topology这个话题不是孤立存在的它是Cache配置体系中描述“数据如何分布”的一个关键维度。1.2 Topology是Cache配置体系中的一个维度JCache里的Cache不是一个裸的Map它要回答的问题很多数据存哪里、存多久、满了怎么办、要不要回写外部存储、条目变化要不要通知业务方。这一堆问题汇总起来就是Cache的Configuration主要包含CacheLoader、CacheWriter、ExpiryPolicy、CacheEntryListener等配置项其中还有一个容易被忽略的议题——Topology拓扑。Topology在规范里描述的是一个核心问题当一个应用部署成多节点集群时你创建的缓存数据到底在节点之间怎么摆是每个节点各存各的还是大家把数据切开来分着存这两种摆法就是规范里定义的LOCAL和PARTITIONED两种拓扑模式。这里有个细节我建议你记住规范原文里写的第二种模式叫PARTITIONED但很多面试题、二手资料里会简写成PARTITION甚至你在面试时口误说成PARTITIONED面试官也不会纠正你。但如果你能主动说一句“题目里的PARTITION对应规范里完整写法应该是PARTITIONED”这在面试官那里的加分效果非常明显说明你读的是原始规范不是考前背的八股。1.3 两种模式的规范定义和第一印象规范里对两种拓扑的原文描述翻译过来很好懂LOCAL本地模式缓存条目只存储在当前JVM节点内部不做任何跨节点的数据分发和共享。节点A写入的数据节点B是看不到的因为B根本没有这份数据。PARTITIONED分区模式缓存条目按照key的哈希结果分散存储到集群里的多个节点上。每个节点只持有全量数据的一个子集没有任何一个节点能独立拥有全部数据。这俩定义看着就两句话但往深了挖它们背后的数据访问路径、一致性模型、故障恢复机制是完全不同的。面试官考这道题真正想听的就是这一层。接下来我分别把两种模式拆开讲清楚它们到底是怎么运作的。2. LOCAL模式拆解单机缓存为什么也能撑起生产环境2.1 LOCAL模式的本质数据边界就在当前JVMLOCAL模式从实现上看就是在使用缓存的JVM内部维护一个键值对存储区数据不会发送到网络也不会被其他节点感知。你在这个节点执行cache.put(order:1001, orderData)这条数据就安安稳稳地躺在这个节点的内存里其他节点的cache.get(order:1001)永远返回null除非它们自己也执行了一次put。正是因为这个特性LOCAL模式的Cache和数据源、业务进程是强绑定的。JVM一重启缓存数据全部清空然后通过CacheLoader从数据库等外部存储里重新加载。这种“简单粗暴”的模式恰恰是很多生产环境的主力缓存形态。很多初学者会低估LOCAL模式觉得它就是个高级点的HashMap。这么理解会吃大亏。LOCAL模式的价值在于访问路径极短一个get请求从代码发起到JVM内部定位到缓存条目全程是纯内存操作没有序列化没有网络IO没有跨进程通信。对响应时间极度敏感的场景比如商品详情页热点数据、用户会话信息LOCAL模式能给你稳定到纳秒到微秒级的访问延迟。2.2 LOCAL模式的数据一致性模型LOCAL模式的一致性只能在“单节点内部”保证。同一个JVM内的缓存put之后立即get肯定是你刚写入的数据。但放在集群视角看LOCAL模式天然是“各节点各说各话”的。节点A更新了一条数据节点B的缓存里还是旧值直到B的过期策略触发或者B自己重新加载。这个特性决定了它的适用边界适合一致性要求不高的读多写少场景或者数据本身就是节点内私有、不需要共享的场景。比如你一个后台管理系统每个服务实例只处理自己分到的那部分任务任务状态放LOCAL缓存里完全合理。再比如一个计算型服务中间结果本来就是节点内临时用的没必要同步到集群。反过来如果你用LOCAL模式缓存了一份全局配置而且大部分节点都依赖这份配置做实时判断那就要小心了一个节点更新了配置其他节点短时间拿不到最新值在配置切换的窗口期里可能出现行为不一致。这种情况我见过不止一次解决方案倒也不复杂要么缩短本地缓存的过期时间要么引入一个分布式缓存作为配置的统一来源。2.3 LOCAL模式的生产级配置要点LOCAL模式虽然简单但真要在生产环境里用好细节还挺多。过期策略别乱拍脑袋ExpiryPolicy有三种典型选项Created从创建时间算过期Modified从最后修改时间算Accessed从最后访问时间算。如果你缓存的是热点商品数据Accessed策略能让热数据一直在缓存里活着冷数据慢慢被淘汰命中率会高很多。容量上限要提前定JCache标准API里有CacheConfiguration的容量设置项具体到实现Ehcache、Hazelcast都有自己的eviction策略配置。最简单常用的淘汰算法是LRU最近最少使用优先淘汰最久没被访问的条目。容量设太小缓存会频繁驱逐设太大内存风险就上来了要做压测。加载器别做慢操作CacheLoader是在缓存miss时被调用的。如果在load方法里做慢SQL查询一次缓存穿透就可能拖垮接口。生产经验是CacheLoader里只放毫秒级的快查询慢查询放到异步任务里预加载或者等业务层做多级兜底。可以对比一下市面上常见的本地缓存组件在LOCAL语义上其实都类似维度标准JCacheLOCALCaffeine/Guava Cache对比参考数据分布当前JVM节点内部当前JVM节点内部访问延迟纯内存极快纯内存极快集群感知无无淘汰机制可通过实现配置自带LRU/LFU等持久化通常不支持通常不支持从使用角度讲LOCAL模式的价值不在于“跟别人比谁强”而在于它是一种确定性的快速通道。它不需要考虑“数据在哪个节点”“网络超时怎么办”这些复杂度把缓存当成进程内的私有空间来用就好。2.4 面试官追问LOCAL时的高频陷阱面试官考LOCAL模式最爱往下追的坑基本集中在两个点上。第一个是“那各节点的数据不一致怎么办”这是分布式架构里绕不开的问题。你要能给出对应的答案逻辑先从场景角度判断是否需要强一致大多数本地缓存场景允许秒级延迟比如商品信息、配置信息这些都是最终一致就行。如果业务真的要求强一致那压根不应该用LOCAL模式该用分布式缓存或者加版本号校验。第二个是“缓存穿透和缓存击穿”面试官问你LOCAL模式会不会遇到缓存穿透你要能说清楚缓存穿透是指查询一个不存在的key每次都要打到数据库缓存击穿是指某热点key在过期瞬间被大量请求同时打到DB。解决方案也成体系穿透可以用空值缓存击穿可以用互斥锁防止并发重建缓存热点数据可以设置永不淘汰加定时刷新。能说到这里面试官对你在LOCAL模式上的理解就已经很满意了。3. PARTITIONED模式拆解缓存数据是怎么被拆到多个节点的3.1 PARTITIONED模式的本质全量数据被切片存放PARTITIONED模式和LOCAL模式最大的不同在于数据的所有权分配。在PARTITIONED模式下集群中没有任何一个节点保存全量缓存数据每份数据归属于某个特定节点。你可以类比银行的分行制度总行数据分散存储在各分行你办业务不需要跑遍所有分行只要去管你那个账户的分行就行。这样做的好处非常直观总容量可以水平扩展。单节点内存撑不住大数据量那就加节点每个节点分担一部分数据。100GB的缓存数据10个节点每个节点只需要10GB存储压力被摊开了。这就是为什么大数据量缓存场景通常不会用LOCAL模式的原因——你的单机内存再大也有限而且性价比很低。3.2 分区路由算法key是怎么找到它的归属节点的PARTITIONED模式最核心的机制就是key到节点的路由算法。这个算法决定了get、put操作到底该发往哪个节点是分布式缓存的灵魂。绝大多数的实现包括Hazelcast的JCache分区机制都采用了一致性哈希Consistent Hashing的变种。一致性哈希的基本思路是把key计算hash值映射到一个固定范围的哈希环上同时每个节点物理节点在环上占据一个或多个位置虚拟节点。查找key归属时从key的哈希位置出发在环上顺时针找到第一个节点数据就归那个节点管。为什么用一致性哈希而不是简单取模简单取模hash(key) % N也能分数据但节点数量从N变成N1时绝大多数key的归属都会变化意味着几乎全量数据要迁移生产上这是灾难。一致性哈希则不同新增或下线一个节点只有环上邻近的一小部分key需要迁移到新节点大部分数据原地不动。这种最小化rehash的特性让集群扩缩容变成了一个轻量操作。你可以在回答里补充一句虚拟节点的意义物理节点少的时候哈希分布可能不均匀某些节点数据特别多给每个物理节点配置多个虚拟节点让它们在哈希环上分散开可以让数据分布更均衡。这一点一说出来面试官就知道你真的懂分区。3.3 一次get请求在PARTITIONED模式下的完整旅程理解PARTITIONED模式最好的方法是走一遍读写链路。假设一个3节点的集群客户端发起cache.get(order:1001)客户端对key计算哈希比如hash(order:1001) 19528437。客户端通过一致性哈希环定位到这个哈希值归属于节点B。如果客户端本身就在节点B上直接本地内存读取如果不在客户端通过网络向节点B发送get请求。节点B收到请求后在其本地存储中查找对应条目返回给客户端。如果节点B上没有该条目则缓存miss接下来按照配置的CacheLoader去数据源加载或者返回null给业务方。这整个过程里有两个关键点值得在面试中主动说出来。第一读写都需要网络跳转除非key恰好归属本地节点因此PARTITIONED模式的访问延迟比LOCAL模式高一个数量级这是分布式存储的固有代价。第二客户端是感知数据分布的也就是常说的“智能客户端”。它知道每个key该找哪个节点所以不需要中间代理层转发。如果面试官继续追问你还可以补充这种机制和Redis Cluster的客户端路由如出一辙底层都是“客户端算位置—直接连接目标节点—返回结果”的路径只不过Redis Cluster拿出slot槽位的概念每个槽位映射到具体节点逻辑上更工程化。3.4 节点故障和数据安全PARTITIONED模式带来的一个绕不开的问题节点挂了它管的那部分数据就没了。怎么解决答案是冗余副本。JCache规范本身没有强制规定PARTITIONED必须配副本但主流实现如Hazelcast的JCache、Infinispan都支持配置备份数量backup-count。默认情况下一份主数据可以有1个或多个备份副本分散存放在其他节点上。当主节点宕机集群会从备份中提升一个新的主节点出来客户端的路由表也随之更新整个服务不受影响。你在面试时如果能把这个容错机制讲出来就比单纯说“PARTITIONED是分片”的人高一个段位。要记住的要点是副本数越多容错能力越强但写放大和存储成本也越高。一个备份副本意味着每份数据写两次、空间占用翻倍。生产上通常设置1个备份副本就够除非你的缓存数据像交易流水一样完全不能丢。3.5 PARTITIONED模式和Redis Cluster的关系很多候选人以为PARTITIONED是JCache独创的概念其实它和Redis Cluster的分片思想高度相似。你完全可以在面试中做个类比Redis Cluster把整个键空间划分成16384个哈希槽每个节点负责一部分槽位key通过CRC16计算落到某个槽再由槽定位到节点JCache的PARTITIONED模式也是把key集合切分成多个分区每个节点负责一部分分区。差别在于Redis Cluster是一个具体产品JCache是一个标准抽象标准本身不规定实现细节所以不同Provider的PARTITIONED性能差异会很大。这个类比的好处在于它能把一个JCache的小概念连接到你已经熟悉的Redis知识体系里让你回答问题时有更多延伸空间。面试官听你提到Redis Cluster大概率会顺着问你对Redis Cluster分片的理解这是一个很好的引导性话题前提是你真的能把两边讲清楚。4. LOCAL和PARTITIONED怎么选从架构视角看取舍4.1 先看数据量与单机容量最直接的判断标准就是数据量。你的缓存数据总规模如果只有几百MB到1GB级别单机内存完全放得下LOCAL模式是最优解。它没有网络开销没有分布式的复杂度出现问题的概率最小。一旦数据量到了几十GB甚至上百GB单机内存放不下或者放得下但成本太高PARTITIONED模式就变成刚需了。很多项目的问题在于明明数据量只有几百MB却强行上了分布式缓存。结果缓存服务集群加了一堆网络开销和运维成本全上去了收益却微乎其微。这就是典型的架构过度设计。4.2 再看一致性与性能要求的矛盾LOCAL模式访问快但集群内数据的一致性差PARTITIONED模式能享受大容量和水平扩展但每次访问多一次网络跳转。这是个绕不开的权衡。如果业务允许缓存数据在不同节点上短暂不一致比如商品详情、用户昵称、资讯列表LOCAL很合适。如果是分布式锁、库存计数、全局唯一数据这类强一致场景LOCAL模式压根不能用PARTITIONED模式配上写后失效或同步更新机制才有机会满足。这里我要多说一句即使是PARTITIONED模式也很难做到跨节点强一致它更像是一个性能驱动的最终一致系统。真正要求强一致的缓存应该在业务层面加版本号、校验和或者直接用数据库本身的机制来兜底。缓存的定位从来都是“加速”不是“记账本”。4.3 组合使用多级缓存才是生产常态实际生产里LOCAL和PARTITIONED不是互斥的它们经常组合成多级缓存架构。这个组合逻辑很清晰请求进来先查本节点的LOCAL缓存。命中就直接返回这个路径最快。LOCAL缓存miss了再查分布式PARTITIONED缓存比如JCache的分区模式或者Redis Cluster。分布式缓存也miss才走CacheLoader去查数据库查完之后回填两级缓存。这种架构下LOCAL缓存通常只放热点数据命中率可以做得非常高。MIT的论文里有过一个经典数据哪怕只有很短的本地过期时间热点数据的本地命中率都能达到80%以上。剩下的20%落到分布式缓存数据库的压力就非常小了。我在做电商促销活动的时候就是这么干的。秒杀时段大量请求都集中在几十个SKU上本地缓存把这几十个SKU的热度发挥到极致分布式缓存兜底剩余的非热点查询。数据库几乎无感整个系统稳如老狗。4.4 两种模式的完整对比对比维度LOCAL模式PARTITIONED模式数据存储位置当前JVM节点内按key哈希分布到集群各节点数据总容量受单节点内存限制可随节点数水平扩展访问延迟纯内存极快可能跨节点网络访问延迟较高数据一致性集群内无共享天然不一致有副本机制仍需考虑一致策略节点故障影响只影响本节点缓存需要副本提升仍可能短暂不可用复杂度低简单易用高需处理路由、迁移、容错适用场景数据量小、性能敏感、一致性要求低数据量大、集群部署、需要水平扩展这个表格建议你收藏。面试时如果被问到选型能流利地把这几个维度讲出来比单纯背定义有说服力得多。5. 面试官问这道题时到底想听到什么样的回答5.1 这道题背后的三个考察层次我作为面试官问JCache拓扑这道题从来不是想听候选人背出两个名词而是在看三个层次第一层知不知道。听说过LOCAL和PARTITIONED知道一个是本地、一个是分区。这一层只能说明你有基本的概念面。第二层懂不懂原理。知道PARTITIONED的key是怎么路由的理解一致性和容量之间的关系能说清楚两种模式各自适合什么场景。这一层的人至少是对分布式缓存有实践或深入研究过的。第三层能不能落地。能主动说出多级缓存组合、分区副本容错、本地缓存的和分布式缓存的一致性代价。这一层的人是真的设计过生产级缓存方案。你可以自己对照一下现在能到第几层。这篇文章讲完我希望你能摸到第三层的边缘。5.2 一个能扛住追问的现场回答框架面试时如果真的被问到这道题建议按这个顺序组织回答既简洁又有层次一句话定义JCache定义了LOCAL和PARTITIONED两种缓存存储模式前者数据只存当前JVM后者数据按key哈希分布到多个节点。展开对比LOCAL访问快、实现简单但容量受单机限制集群内数据各自独立PARTITIONED容量可水平扩展但访问可能需要网络跳转需要处理节点容错和数据迁移。补一个关键细节PARTITIONED通常用一致性哈希做路由节点增减时rehash成本很小配合副本来保证节点故障后数据不丢。联系项目场景说一个你实际用过的组合比如“我当时先在本地点缓存放热点再配合分区缓存兜底冷数据”。这个回答框架最大的优点是给了面试官至少三个可以追问的方向而每个方向你自己都已经准备好了。当你能掌控追问节奏的时候面试的主导权就在你手上了。5.3 高频追问清单和应对思路面试官听完你的回答后大概率会顺着往下追。我列几个高频追问你们可以提前准备好追问1你说PARTITIONED用了一致性哈希那节点扩容时具体会发生什么应对思路说清楚先加虚拟节点、迁移数据、最终一致地对外提供服务。强调只有邻近一小部分key需要迁移大部分数据不动。追问2LOCAL缓存和数据库之间的数据一致性怎么保证应对思路配合过期时间和主动失效允许短时间不一致。如果业务对一致性敏感就用版本号或者干脆不用本地缓存。追问3PARTITIONED模式下单点写入了数据但还没同步到副本这时候主节点挂了怎么办应对思路这就是数据丢失的窗口。生产上通常用同步复制来缩小这个窗口或者接受极小的概率在业务上做兜底。能说出“同步复制 vs 异步复制”的权衡就是一次漂亮的加分回答。追问4JCache标准API怎么配置这两种模式应对思路标准API本身不直接暴露Topology配置项它是通过具体Provider的配置来定义的。比如Hazelcast的JCache在配置分布式Cache时默认就是分区存储而本地Cache也可以通过配置关闭分布式特性。能说出“规范定义语义Provider提供配置”这句话说明你是真懂。6. 聊点我的真实感受在分布式缓存这个领域LOCAL和PARTITIONED是个绕不开的话题但它绝不是两个名词那么简单。LOCAL模式教会我的是“能用本地解决的别引入网络”很多系统性能问题不是缓存不够好而是用错了缓存形态PARTITIONED模式教会我的是“没有免费的午餐”数据分散了容量上去了但你得为路由、迁移、容错付出运维代价。我常跟团队里的人说考面试题不是目的借一道题把一个技术领域吃透才是目的。这道JCache的题目背后站着整个分布式缓存的知识体系。你把LOCAL和PARTITIONED搞明白再看Redis Cluster、一致性哈希、多级缓存这些概念就会有一种“原来它们都是通的”的感觉。如果你正在准备面试建议别只背答案自己动手把这两种模式写在代码里跑起来看看在本地缓存里put几个key再在分区模式下put同样的key观察数据落在哪个节点、访问跨不跨网络。纸上得来终觉浅这句话在技术上真的适用。毕竟面试官也只能从你的话里判断你有没有动手经验而你若真有动手经验说出来的每句话都会自带一种笃定感。