Let’s first look at a question:
How to handle automatic cancellation of orders upon expiration, such as automatically changing the order status if the order is not paid for 30 minutes?
Solution:
You can use the natural key automatic expiration mechanism of redis. When placing an order, write the order id into redis. The expiration time is 30 minutes. Check the order status after 30 minutes. If the payment is not made, it will be processed but the key has expired. Is there any notification from redis? The answer is yes.
Enable redis key expiration reminder
Modify redis related event configuration. Find the redis configuration file redis.conf and check the configuration item of "notify-keyspace-events". If not, add "notify-keyspace-events Ex". If there is a value, add Ex. The relevant parameters are explained as follows:
K:keyspace事件,事件以__keyspace@<db>__为前缀进行发布; E:keyevent事件,事件以__keyevent@<db>__为前缀进行发布; g:一般性的,非特定类型的命令,比如del,expire,rename等; $:字符串特定命令; l:列表特定命令; s:集合特定命令; h:哈希特定命令; z:有序集合特定命令; x:过期事件,当某个键过期并删除时会产生该事件; e:驱逐事件,当某个键因maxmemore策略而被删除时,产生该事件; A:g$lshzxe的别名,因此”AKE”意味着所有事件。
redis test:
Open a redis-cli and monitor the key expiration event of db0
127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "__keyevent@0__:expired" 3) (integer) 1
Open another redis-cli and send the scheduled expiration key
127.0.0.1:6379> setex test_key 3 test_value
Observe the previous redis-cli and you will find that you have received the expired keytest_key, but you cannot receive the expired value test_value
127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "__keyevent@0__:expired" 3) (integer) 1 1) "pmessage" 2) "__keyevent@0__:expired" 3) "__keyevent@0__:expired" 4) "test_key"
Use in springboot
Add dependency in pom
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
Define the configuration RedisListenerConfig
import edu.zut.ding.listener.RedisExpiredListener;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.listener.PatternTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;/** * @Author lsm * @Date 2018/10/27 20:56 */@Configurationpublic class RedisListenerConfig { @Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory);// container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired")); return container; } }
Define the listener and implement the KeyExpirationEventMessageListener interface. Check the source code and find that this interface monitors all db expiration events keyevent@*:expired"
import edu.zut.ding.constants.SystemConstant;import edu.zut.ding.enums.OrderState;import edu.zut.ding.service.OrderService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.connection.Message;import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;import org.springframework.data.redis.listener.RedisMessageListenerContainer;import org.springframework.stereotype.Component;/** * 监听所有db的过期事件__keyevent@*__:expired" * @author lsm */@Componentpublic class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } /** * 针对redis数据失效事件,进行数据处理 * @param message * @param pattern */ @Override public void onMessage(Message message, byte[] pattern) { // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key String expiredKey = message.toString(); if(expiredKey.startsWith("Order:")){ //如果是Order:开头的key,进行处理 } } }
Or open the container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired")); comment in RedisListenerConfig, then define the listener and monitor the __keyevent@0__:expired event, which is the db0 expiration event . The definition of this place is relatively flexible, you can define what events to monitor.
import org.springframework.data.redis.connection.Message;import org.springframework.data.redis.connection.MessageListener;/** * @author lsm */public class RedisExpiredListener implements MessageListener { /** * 客户端监听订阅的topic,当有消息的时候,会触发该方法; * 并不能得到value, 只能得到key。 * 姑且理解为: redis服务在key失效时(或失效后)通知到java服务某个key失效了, 那么在java中不可能得到这个redis-key对应的redis-value。 * * 解决方案: * 创建copy/shadow key, 例如 set vkey "vergilyn"; 对应copykey: set copykey:vkey "" ex 10; * 真正的key是"vkey"(业务中使用), 失效触发key是"copykey:vkey"(其value为空字符为了减少内存空间消耗)。 * 当"copykey:vkey"触发失效时, 从"vkey"得到失效时的值, 并在逻辑处理完后"del vkey" * * 缺陷: * 1: 存在多余的key; (copykey/shadowkey) * 2: 不严谨, 假设copykey在 12:00:00失效, 通知在12:10:00收到, 这间隔的10min内程序修改了key, 得到的并不是 失效时的value. * (第1点影响不大; 第2点貌似redis本身的Pub/Sub就不是严谨的, 失效后还存在value的修改, 应该在设计/逻辑上杜绝) * 当"copykey:vkey"触发失效时, 从"vkey"得到失效时的值, 并在逻辑处理完后"del vkey" * */ @Override public void onMessage(Message message, byte[] bytes) { byte[] body = message.getBody();// 建议使用: valueSerializer byte[] channel = message.getChannel(); System.out.print("onMessage >> " ); System.out.println(String.format("channel: %s, body: %s, bytes: %s" ,new String(channel), new String(body), new String(bytes))); } }
The above is the detailed content of How does redis monitor expired keys?. For more information, please follow other related articles on the PHP Chinese website!