Xcache:多级缓存框架介绍
多年前,我造过一个轮子,也是多级缓存框架,名字也叫做Xcache
。
多年后,本着重复造轮子的精神,我又造了个新轮子:https://github.com/patricklaux/xcache
因为是新造的轮子,所以看起来似乎更圆了一些,跑起来似乎也更顺滑了一些。
非要自夸一下的话,那就是这么些年的编程生涯,在踩过许多坑之后,编码质量更好了点,架构设计更合理了些。
1. 整体架构
说明:
Cache API
:缓存接口。Cache Annotation
:缓存注解。Cache
:缓存对象。Store
:缓存数据存储,每个缓存对象实例最多可支持三级缓存数据存储。Codec
:数据编解码(序列化与反序列化)。Compressor
:数据压缩。CacheLoader
:数据加载,用于从数据源读取数据。CacheRefresh
:缓存数据刷新,定时通过CacheLoader
加载并刷新缓存数据。CacheSync
:缓存数据同步,用于维护各实例间私有缓存数据的一致性。CacheMetrics
:缓存指标采集,用于记录缓存调用次数及命中率等指标。MetricsSystem
:缓存指标信息的存储、计算与展示。MQ
:消息队列,用于转发数据同步消息(已有实现采用Redis Stream
)。DataSource
:数据源。Redis
orOther……
:缓存数据存储仓库。
2. 框架特性
- 支持多级缓存:一级缓存采用
Caffeine
,二级缓存采用Redis
,最多可支持三级缓存。 - 缓存数据同步:通过缓存事件广播,多个应用实例的缓存数据保持一致。
- 缓存指标统计:支持调用次数、命中次数等指标,数据呈现可自由扩展。
- 缓存数据刷新:定时自动刷新缓存数据,避免慢查询导致应用响应缓慢。
- 随机存活时间:可选择使用随机存活时间,避免大量数据集中过期,导致数据源压力过大。
- 数据回源加锁:同一个键同一时刻仅允许一个线程回源查询,减轻数据源压力。
- 数据存在断言:可选择实现数据存在断言接口,譬如
Bloom Filter
,减少无效回源查询。 - 支持缓存空值:可选择缓存空值,减少无效回源查询。
- 缓存数据压缩:可选择数据压缩,降低内存(磁盘)消耗。
- 支持缓存注解:
Cacheable
,CacheableAll
,CachePut
,CachePutAll
,…… ,CacheClear
- 适配
Spring Cache
注解:如希望使用Spring Cache
注解,可依赖Xcache
适配项目,即可解锁更多功能。
3. 缓存流程
原则:数据查询顺序从一级缓存到三级缓存,数据写入、删除顺序从三级缓存到一级缓存。
实际应用中,可能只有一级缓存或两级缓存,并不一定有全部三级缓存,这是可配置的。
3.1. 数据查询
- 流程:按照一级缓存 → 二级缓存 → 三级缓存的顺序依次查询。
- 图示:
3.2. 数据写入
- 流程:按照三级缓存 → 二级缓存 → 一级缓存的顺序依次写入。
- 图示:
3.3. 数据删除
-
流程:按照三级缓存 → 二级缓存 → 一级缓存的顺序依次删除。
-
图示:
4. 运行环境
SpringBoot
:3.3.0+
JDK
:21+
5. 开始使用
以下代码片段来自于xcache-samples,如需获取更详细信息,您可以克隆示例项目到本地进行调试。
git clone https://github.com/patricklaux/xcache-samples.git
5.1. 第一步:引入缓存依赖
如直接通过调用方法操作缓存,不使用缓存注解,仅需引入xcache-spring-boot-starter
。
主要依赖:Caffeine
(内嵌缓存),Lettuce
(Redis
客户端),Jackson
(序列化)
<dependencies> <dependency> <groupId>com.igeeksky.xcache</groupId> <artifactId>xcache-spring-boot-starter</artifactId> <version>${xcache.version}</version> </dependency> <!-- ... other ... --> </dependencies>
5.2. 第二步:编写缓存配置
xcache: #【1】xcache 配置的根节点 group: shop #【2】分组名称(必填),主要用于区分不同的应用 template: #【3】缓存公共配置模板(必填),列表类型,可配置一至多个 - id: t0 #【4】 模板ID(必填) first: #【5】 一级缓存配置 provider: caffeine #【6】使用 caffeine 作为一级缓存(默认值:caffeine)
说明:
- 【1-4】仅有的 4 个必填项。
Xcache
提供了丰富的配置项,大部分有默认值,因此可以省略。 - 【3~8】缓存公共配置模板:同一应用中,一般会有多个缓存实例,配置通常相同。为减少重复配置,可使用公共配置模板。
另,每一个配置项都有详细介绍,可借助ide
的自动提示功能快速查看配置描述。
或直接查看com.igeeksky.xcache.props.CacheProps
,了解详细的配置信息。
5.3. 第三步:调用缓存方法
/** * 用户缓存服务 */ @Service public class UserCacheService { private final UserDao userDao; private final Cache<Long, User> cache; private final CacheLoader<Long, User> cacheLoader; public UserCacheService(UserDao userDao, CacheManager cacheManager) { this.userDao = userDao; this.cache = cacheManager.getOrCreateCache("user", Long.class, User.class); this.cacheLoader = new UserCacheLoader(this.userDao); } /** * 根据用户ID获取单个用户信息 * * @param id 用户ID * @return 用户信息 */ public User getUser(Long id) { // 1. 首先查询缓存,如果缓存命中,则直接返回缓存数据; // 2. 如果缓存未命中,则调用 cacheLoader 从数据源加载数据。 return cache.getOrLoad(id, cacheLoader); } /** * 根据用户ID批量获取用户信息 * * @param ids 用户ID集合 * @return 用户信息集合 */ public Map<Long, User> getUsers(Set<Long> ids) { // 1. 首先查询缓存,如果缓存全部命中,则直接返回缓存数据; // 2. 如果缓存全部未命中或部分命中,则调用 cacheLoader 从数据源加载未命中数据。 return cache.getAllOrLoad(ids, this.cacheLoader); } /** * 新增用户 * * @param user 新用户信息 * @return 保存到数据库后返回的用户信息 */ public User saveUser(User user) { User created = userDao.save(user); // 将新增用户信息写入缓存 cache.put(user.getId(), created); return created; } /** * 更新用户信息 * * @param user 待更新的用户信息 * @return 保存到数据库后返回的用户信息 */ public User updateUser(User user) { User updated = userDao.update(user); // 将更新后的用户信息写入缓存 cache.put(user.getId(), updated); // 如果为了更好地保持数据一致性,这里可选择直接删除缓存数据,后续查询时再从数据源加载 // cache.remove(user.getId()); return updated; } /** * 批量更新用户信息 * * @param users 待更新的用户信息集合 * @return 保存到数据库后返回的用户信息集合 */ public Map<Long, User> updateUsers(List<User> users) { Map<Long, User> updated = userDao.batchUpdate(users); // 将更新后的用户信息写入缓存 cache.putAll(updated); // 如果为了更好地保持数据一致性,这里可选择直接删除缓存数据,后续查询时再从数据源加载 // cache.removeAll(updated.keySet()); return updated; } /** * 删除用户信息 * * @param id 用户ID */ public void deleteUser(Long id) { userDao.delete(id); // 删除缓存数据 cache.remove(id); } /** * 批量删除用户信息 * * @param ids 用户ID集合 */ public void deleteUsers(Set<Long> ids) { userDao.batchDelete(ids); // 批量删除缓存数据 cache.removeAll(ids); } /** * 清空数据 */ public void clear() { userDao.clear(); // 清空缓存数据 cache.clear(); } /** * CacheLoader 实现类 * <p> * 用于数据回源操作,当缓存中不存在指定数据时,会调用此方法从数据源加载数据。 * * @param userDao */ private record UserCacheLoader(UserDao userDao) implements CacheLoader<Long, User> { @Override public User load(Long id) { return this.userDao.findUser(id); } @Override public Map<Long, User> loadAll(Set<? extends Long> ids) { return this.userDao.findUserList(ids); } } }
6. 获取帮助
如果您在使用 Xcache 过程中遇到任何问题或有任何建议,欢迎通过上述渠道获取帮助或参与讨论。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
LazyLLM教程 | 第6讲:检索更准:RAG召回效果优化的底层逻辑与技巧
从前面的课程中我们知道了 RAG(Retrieval-Augmented Generation)系统是一种结合检索和生成两个核心组件实现基于外部知识源回答用户查询的智能系统,可以在一定程度上克服大模型幻觉问题在特定领域问答任务上给出与上下文相关的答案。RAG 的基本思想是先通过检索模块从大量文档中找到与用户问题相关的上下文,然后将这些信息提供给生成模型,以生成更加精准和可靠的答案。 尽管 RAG 系统的基础架构较为清晰,并且已有许多工具和框架可以帮助开发者快速搭建,但要真正实现高精度、高效率的输出仍然充满挑战。一个优秀的 RAG 系统不仅仅是简单地将文档存入向量数据库再叠加一个大语言模型,需要针对检索和生成的不同环节进行深度优化,确保信息检索的准确性和生成内容的可靠性。 本篇文章围绕 RAG 系统的效果评测方法展开,重点介绍如何评估检索与生成组件的性能与提升检索组件效果的相关策略。 首先,我们将深入探讨检索组件的评估标准,包括召回率(Recall)和上下文相关性(Context Relevance),以衡量检索的覆盖度和精准度。接下来,我们会介绍生成组件的评估指标,例如忠诚度(Fait...
- 下一篇
得物新商品审核链路建设分享
一、 前言 得物近年来发展迅猛,平台商品类目覆盖越来越广,商品量级越来越大。而以往得物的上新动作更多依赖于传统方式,效率较低,无法满足现有的上新诉求。那么如何能实现更加快速的上新、更加高效的上新,就成为了一个至关重要的命题。 近两年AI大模型技术的发展,使得发布和审核逐渐向AI驱动的方式转变成为可能。因此,我们可以探索利用算法能力和大模型能力,结合业务自身规则,构建更加全面和精准的规则审核点,以实现更高效的工作流程,最终达到我们的目标。 本文围绕AI审核,介绍机审链路建设思想、规则审核点实现快速接入等核心逻辑。 二、如何实现高效审核 对于高效审核的理解,主要可以拆解成“高质量”、“高效率”。目前对于“高质量”的动作包括,基于不同的类目建设对应的机审规则、机审能力,再通过人工抽查、问题Case分析的方式,优化算法能力,逐步推进“高质量”的效果。 而“高效率”,核心又可以分成业务高效与技术高效。 业务高效 逐步通过机器审核能力优化审核流程,以解决资源不足导致上新审核时出现进展阻碍的问题。 通过建设机审配置业务,产品、业务可以直观的维护类目-机审规则-白名单配置,从而高效的调整机审策略。 技...
相关文章
文章评论
共有0条评论来说两句吧...