监听redis的KEY失效 作为一个触发器来完成小程序的定时提醒功能

本文介绍了如何在微信小程序中使用Redis的监听器配合订阅消息服务,实现业务逻辑中定时提醒功能,即使有几分钟的误差也无妨。重点在于配置Redis监听和处理键失效事件的过程。

前言

1):业务逻辑是这样的>>小程序中有一个提醒功能,该功能有一个提醒时间,时间到了就给用户发送一个提醒,这里的提醒采用的微信的订阅消息服务

2):由于这个业务的并没有要求太严格的实时性,误差几分钟都是可以的。所以这里采用redis的监听器

 

内容

1):配置redis  (在redis.conf中放开这行)

2):在代码中添加监听

@Configuration
public class RedisListenerConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

3):添加键失效后需要触发的代码

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    private static Logger logger = LoggerFactory.getLogger(RedisKeyExpirationListener.class);
 

    private static final Topic KEYEVENT5_EXPIRED_TOPIC = new PatternTopic("__keyevent@13__:expired"); //设置监听库为13

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }


    @Override
    public void doRegister(RedisMessageListenerContainer listenerContainer) {
        listenerContainer.addMessageListener(this, KEYEVENT5_EXPIRED_TOPIC);
    }

    /**
     * 针对redis数据失效事件,进行数据处理
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
    String expiredKey = message.toString();//获取当前失效的ID

    /** (你的业务逻辑)
    * 在这个地方根据失效的键去数据库查询相关数据
       然后调用微信的订阅通知再发送给用户
    */


}

 

 

4)需要看微信订阅通知的 请点击这里

<think>我们之前讨论的是AOP和CGLIB的区别,现在用户的新问题是关于Redis缓存的使用:当使用查询条件作为key缓存数据时,数据库修改后如何定位并更新对应的缓存。 用户的问题:使用Redis做缓存,以查询条件为key,数据库修改后定位对应缓存进行修改的方法。 分析: 1. 用户将查询条件(可能是组合条件)作为key,将查询结果作为value存入Redis。 2. 当数据库中的数据发生修改(增、删、改)时,需要更新缓存,但问题是如何根据数据库的修改来定位到哪些缓存key需要更新?因为同一个数据可能被多个不同的查询条件缓存(即同一个数据出现在多个查询结果中)。 这是一个常见的缓存更新问题,尤其是在复杂查询条件作为key的情况下,因为难以直接建立数据记录与缓存key之间的映射关系。 解决方案通常有以下几种: 方案一:基于数据表的缓存清理(简单但可能过度清理) - 为每个数据表维护一个缓存版本号(或时间戳),作为缓存key的一部分。 - 当表中任何数据发生修改时,更新该表的版本号。 - 这样,所有依赖该表的缓存key都会因为版本号变化而失效(因为key改变了,或者可以在读取时判断版本号是否匹配)。 - 但这样会导致整个表的缓存都失效,可能误伤过多。 方案二:建立数据记录与缓存key的映射关系(精确但复杂) - 维护一个反向索引,记录每条数据记录被哪些查询条件缓存过(即哪些key的缓存结果中包含该记录)。 - 当一条记录被修改时,通过反向索引找到所有包含该记录的缓存key,然后删除这些缓存。 - 但是,这种反向索引的构建和维护成本较高,需要额外的存储空间,并且在分布式环境下可能更复杂。 方案三:基于消息的通知机制(需要系统支持) - 利用数据库的binlog(如MySQL)或者触发器,在数据修改时发送消息。 - 在消息消费者中,根据修改的数据内容,计算出可能受影响的查询条件(需要业务逻辑),然后删除对应的缓存key。 - 这种方式实时性较好,但需要额外的消息系统和复杂的消费者逻辑。 方案四:缓存设置较短的过期时间(简单但可能不一致) - 设置一个较短的缓存过期时间(如几分钟),这样即使数据修改后缓存没有立即更新,也会很快过期。 - 这种方法适用于对数据实时性要求不高的场景,但不能保证强一致性。 方案五:在业务代码中硬编码更新逻辑(适用于简单场景) - 在修改数据库的地方,显式地删除已知的可能会受影响的缓存key。 - 这种方式简单直接,但是当查询条件多变时,难以维护,容易遗漏。 结合用户的问题,重点在于“如何定位缓存key”,所以方案二和方案五都需要知道哪些key与修改的数据相关。 在实际应用中,通常会采用折中的方法: 1. 如果查询条件相对固定,可以在更新数据时,根据业务规则计算出可能受影响的查询条件,然后删除这些key。 2. 如果查询条件非常灵活(比如任意组合),可以考虑使用方案二(反向索引)或者方案一(整个表缓存版本控制)。 另外,还可以考虑使用Redis的Hash结构,将查询条件拆分为多个字段,然后利用Hash的字段更新能力进行部分更新,但这通常不适用于复杂的查询条件。 具体步骤示例(以方案二为例): 1. 在缓存查询结果时,记录下该结果集包含哪些数据记录(例如,记录主键列表),然后为每个数据记录维护一个集合(set),集合中存储所有包含该记录的所有缓存key。 2. 当某个数据记录被修改时,通过这个反向映射找到所有包含该记录的缓存key,然后删除这些key。 3. 注意:当缓存过期或被删除时,需要清理反向映射(可以使用Redis过期事件通知,或者由业务代码维护一致性)。 但是,方案二实现起来较为复杂,需要考虑并发和性能问题。 考虑到实际开发中,更常见的做法是采用缓存过期结合延迟双删等策略,或者使用方案五(在更新数据时显式删除已知的缓存key)。 建议:根据业务场景选择合适的方式。如果查询条件简单且固定,推荐方案五;如果查询条件复杂且变化多,可以考虑方案一(表级版本控制)或方案三(消息通知)。 下面是一个简单的代码示例(方案五): 假设有一个用户查询缓存,key由查询条件(如按姓名查询)生成: ```java // 缓存key生成方法 public String getUserCacheKey(String name) { return "user:query:" + name; } // 查询用户 public List<User> getUsersByName(String name) { String key = getUserCacheKey(name); List<User> users = redis.get(key); if (users == null) { users = userDao.findByName(name); redis.set(key, users, 10*60); // 10分钟过期 } return users; } // 更新用户(同时删除相关缓存) public void updateUser(User user) { userDao.update(user); // 假设更新操作可能改变了用户的姓名,那么旧的姓名和新的姓名对应的缓存都需要删除 // 如果不知道旧的姓名,则无法删除旧的缓存,这里就存在不一致的可能 // 所以,我们通常需要先获取旧数据(或者要求调用方同时传入旧姓名) // 这里假设我们通过id先查询出旧用户 User oldUser = userDao.findById(user.getId()); // 删除旧查询条件对应的缓存 redis.del(getUserCacheKey(oldUser.getName())); // 如果新的姓名和旧的不同,删除新姓名对应的缓存(实际上新姓名可能原本没有缓存,但为了安全) redis.del(getUserCacheKey(user.getName())); // 注意:如果该用户还可能出现在其他查询条件中(比如按年龄查询),那么这些也需要删除,所以方案五只适用于简单场景 } ``` 对于复杂的场景,可能需要引入更高级的缓存解决方案,如使用Redis的发布订阅功能,或者使用专门的缓存管理框架。 结论:在复杂场景下,定位并更新以查询条件为key的缓存是一个挑战,需要根据具体业务设计和权衡。 最后,生成相关问题: </think>### Redis缓存中以查询条件为Key的更新策略 当使用查询条件作为Redis缓存Key时,数据库修改后需要精准定位并更新缓存。以下是核心解决方案: #### 1. **反向索引机制(推荐方案)** **原理**:建立「数据记录ID → 缓存Key集合」的映射关系 **实现步骤**: a. 写入缓存时记录索引: ```java // 缓存用户查询结果(姓名查询) String cacheKey = "user:name:" + name; redis.set(cacheKey, userData); // 建立反向索引(user:1 对应的缓存Key集合) redis.sadd("index:user:1", cacheKey); ``` b. 数据修改时触发更新: ```java void updateUser(User user) { // 1. 更新数据库 db.update(user); // 2. 通过ID查关联缓存Key Set<String> cacheKeys = redis.smembers("index:user:" + user.getId()); // 3. 批量删除缓存 cacheKeys.forEach(key -> redis.del(key)); // 4. 更新反向索引(可选) redis.del("index:user:" + user.getId()); // 清空旧索引 } ``` **优势**:精准定位,支持多条件查询[^1] **代价**:额外存储索引(通常小于原始数据的10%) #### 2. **缓存Key标准化+模式匹配** **原理**:通过统一命名规则实现模糊匹配 **操作流程**: ```java // Key命名规则:entity:queryType:value // 示例:user:name:Alice, user:age:30 // 更新时批量删除匹配Key public void clearUserCaches(int userId) { // 使用SCAN命令避免阻塞 String pattern = "user:*:" + userId + "*"; redis.scan(pattern).forEach(key -> redis.del(key)); } ``` **适用场景**:查询条件结构固定的场景[^2] #### 3. **版本号控制** **原理**:在Key中嵌入数据版本标识 **实现方式**: ```sql -- 数据库添加版本字段 ALTER TABLE users ADD version INT DEFAULT 1; ``` ```java // 缓存Key携带版本号 String cacheKey = "user_v" + dbVersion + ":name:" + name; // 数据更新时版本号+1 void updateUser(User user) { db.execute("UPDATE users SET ..., version=version+1 WHERE id=?"); invalidateCaches(user.getId()); } ``` ### 策略对比表 | 方法 | 精确度 | 实现复杂度 | 存储开销 | 适用场景 | |---------------------|--------|------------|----------|------------------------| | **反向索引** | ★★★ | ★★☆ | 中 | 多条件组合查询 | | **模式匹配** | ★★☆ | ★☆☆ | 低 | 固定结构Key | | **版本号控制** | ★★★ | ★★☆ | 低 | 读多写少场景 | ### 最佳实践建议 1. **写操作同步更新**:在DB事务提交后立即执行缓存删除(双写一致性) 2. **设置兜底过期时间**:即使更新失败,缓存也会自动过期 ```bash EXPIRE user:name:Alice 3600 # 1小时自动过期 ``` 3. **批量化操作**:使用Redis Pipeline减少网络开销 4. **监控补偿机制**:通过Binlog监听实现最终一致性[^3] > 注:当查询条件极端复杂(如任意字段组合查询)时,建议采用**异步重建缓存**策略:触发更新后标记缓存失效,下次查询时重新生成。 --- ### 示例场景 **用户信息修改场景**: 1. 初始缓存:`user:name:Alice` = {id:1, name:Alice, age:30} 2. 反向索引:`index:user:1` = ["user:name:Alice"] 3. 修改操作:将Alice改名为Alicia 4. 更新流程: - 删除旧Key:`DEL user:name:Alice` - 创建新缓存:`SET user:name:Alicia {...}` - 更新索引: ```java // 删除旧索引关联 SREM index:user:1 "user:name:Alice" // 添加新索引关联 SADD index:user:1 "user:name:Alicia" ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xixililicm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值