Background: publish and subscribe (pub|sub) function is provided after redis2.8
To implement redis expired key monitoring, you only need to bind the message channel of the key expiration event ( keyevent@* :expired) with the listener in the listener container . keyevent@* : the * in expired means matching all db0-db15 databases in redis, keyevent@0 : expired means only listening to the key expiration event of the db0 database
Implementation of spring-data-redis
1. The KeyExpirationEventMessageListener listener is provided in spring-data-redis, and the KeyExpirationEventMessageListener can be directly inherited and the onMessage method
code can be rewritten to realize the key expiration.
package com.wl.redis.listener;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
/**
* Created by Administrator on 2020/12/10.
*/
@Component
public class KeyExpirationListener extends KeyExpirationEventMessageListener {
public KeyExpirationListener(RedisMessageListenerContainer listenerContainer){
super(listenerContainer);
}
public void onMessage(Message message, @Nullable byte[] pattern) {
String key = message.toString();
System.out.println("监听到key:" + key + "过期");
}
}
KeyExpirationEventMessageListener has only one constructor with RedisMessageListenerContainer parameters, so we need to inject RedisMessageListenerContainer
package com.wl.redis.config;
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.RedisMessageListenerContainer;
/**
* Created by Administrator on 2020/12/10.
*/
@Configuration
public class RedisConfig {
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
Binding the channel to the listener is implemented
in KeyExpirationEventMessageListener. In KeyExpirationEventMessageListener, the doRegister method of its parent class is overridden. Bind __keyevent@*__:expired to the listener. Part of the source code is as follows
private static final Topic KEYEVENT_EXPIRED_TOPIC = new PatternTopic("__keyevent@*__:expired");
@Override
protected void doRegister(RedisMessageListenerContainer listenerContainer) {
listenerContainer.addMessageListener(this, KEYEVENT_EXPIRED_TOPIC);
}
Test set order 123456 ex 4
console output 监听到key:order过期
2. Custom key expired monitor
Implement MessageListener interface
package com.wl.redis.listener;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
/**
* Created by Administrator on 2020/12/11.
*/
@Component
public class CustomerKeyExpirationListener implements MessageListener {
@Override
public void onMessage(Message message, @Nullable byte[] pattern) {
String key = message.toString();
System.out.println("监听到key:" + key + "过期");
}
}
Container binds channel and listener
package com.wl.redis.config;
import com.wl.redis.listener.CustomerKeyExpirationListener;
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;
/**
* Created by Administrator on 2020/12/10.
*/
@Configuration
public class RedisConfig {
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
, CustomerKeyExpirationListener customerKeyExpirationListener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(customerKeyExpirationListener,new PatternTopic("__keyevent@*__:expired"));
return container;
}
}
Recommendations for using redis key expired monitoring
1. The name of the key that needs to be monitored should be different from other normal ones that do not need to be monitored. E.g
public void onMessage(Message message, @Nullable byte[] pattern) {
String key = message.toString();
if(!key.startsWith("expiration")){
return;
}
System.out.println("监听到key:" + key + "过期");
}
2. The key has been deleted when the monitoring is triggered, and only the value of the key can be obtained but the value of its value cannot be obtained. Therefore, the key should contain the information you need, or the information you need can be queried through the key
3. Actively deleting the key before the key expires will not trigger the overdue monitoring event. In the scenario of canceling the order without payment after the order expires, if the order has been paid or cancelled before the key expires, the key should be deleted
4. In a distributed scenario, The listener will listen multiple times, so a lock is needed (to prevent the same key from being monitored and executed multiple times). For example
package com.wl.redis.listener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Created by Administrator on 2020/12/10.
*/
@Component
public class KeyExpirationListener extends KeyExpirationEventMessageListener {
@Autowired
private StringRedisTemplate redisTemplate;
public KeyExpirationListener(RedisMessageListenerContainer listenerContainer){
super(listenerContainer);
}
public void onMessage(Message message, @Nullable byte[] pattern) {
String key = message.toString();
if(!key.startsWith("expiration")){
return;
}
//加锁(不同的key过期获取的锁是不一样的)
String lockKey = "lock_" + key;
boolean lock = lock(lockKey);
if(!lock){
System.out.println("===return" + key + "===");
return;
}
try {
System.out.println("监听到key:" + key + "过期");
//释放锁(可以不用释放)
// 这里睡眠5秒后解锁,防止程序太快,导致服务1已经执行完毕,服务2才刚刚开始获取锁
Thread.sleep(5000);
unlock(lockKey);
} catch (InterruptedException e) {
//
}
}
private Boolean lock(String lockKey){
Long timeOut = redisTemplate.getExpire(lockKey);
SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() {
List<Object> exec = null;
@Override
@SuppressWarnings("unchecked")
public Boolean execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().setIfAbsent(lockKey,"lock");
if(timeOut == null || timeOut == -2) {
operations.expire(lockKey, 30, TimeUnit.SECONDS);
}
exec = operations.exec();
if(exec.size() > 0) {
return (Boolean) exec.get(0);
}
return false;
}
};
return redisTemplate.execute(sessionCallback);
}
private void unlock(String lockKey){
redisTemplate.delete(lockKey);
}
}
Start two services and add test data
for(int i = 1;i<=10;i++){
stringRedisTemplate.opsForValue().set("expiration_order_" + i,i + "",6,TimeUnit.SECONDS);
Thread.sleep(1000);
}
The two services are output as follows
===returnexpiration_order_1===
===returnexpiration_order_2===
===returnexpiration_order_3===
===returnexpiration_order_4===
===returnexpiration_order_5===
监听到key:expiration_order_6过期
监听到key:expiration_order_7过期
===returnexpiration_order_8===
===returnexpiration_order_9===
===returnexpiration_order_10===
监听到key:expiration_order_1过期
监听到key:expiration_order_2过期
监听到key:expiration_order_3过期
监听到key:expiration_order_4过期
监听到key:expiration_order_5过期
===returnexpiration_order_6===
===returnexpiration_order_7===
监听到key:expiration_order_8过期
监听到key:expiration_order_9过期
监听到key:expiration_order_10过期
5. If your redis is restarted after the publication and subscription become invalid, please modify the configuration file of redis.conf as follows
6.
notify-keyspace-events EA
Use redis to publish and subscribe. The
above example has implemented the binding (subscription) of the channel and the listener. The following only needs to implement the publish message.
package com.wl.redis.publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.Topic;
import org.springframework.stereotype.Component;
/**
* Created by Administrator on 2020/12/11.
*/
@Component
public class RedisPublisher {
@Autowired
private RedisTemplate redisTemplate;
public void sendMessage(String channel,String message){
redisTemplate.convertAndSend(channel,message);
}
}
listener
package com.wl.redis.listener;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
/**
* Created by Administrator on 2020/12/11.
*/
@Component
public class CustomerListener implements MessageListener{
@Autowired
private RedisTemplate redisTemplate;
@Override
public void onMessage(Message message, @Nullable byte[] pattern) {
//反序列化解决message乱码
RedisSerializer<?> serializer = redisTemplate.getValueSerializer();
System.out.println("================" + serializer.deserialize(message.getBody()).toString());
}
}
config
package com.wl.redis.config;
import com.wl.redis.listener.CustomerKeyExpirationListener;
import com.wl.redis.listener.CustomerListener;
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.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
/**
* Created by Administrator on 2020/12/10.
*/
@Configuration
public class RedisConfig {
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
,CustomerListener customerListener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// container.addMessageListener(customerKeyExpirationListener,new PatternTopic("__keyevent@*__:expired"));
container.addMessageListener(customerListener,new ChannelTopic("customer"));
return container;
}
}
test
publisher.sendMessage("customer","你好");
Note that if the RedisSerializer is not deserialized in the CustomerListener, it may cause the received message to be garbled
Implementation of Jedis
1. The listener inherits the JedisPubSub class
package com.wl.redis.jedis;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPubSub;
/**
* Created by Administrator on 2020/12/11.
*/
@Component
public class JedisKeyExpirationListener extends JedisPubSub {
public void onMessage(String channel, String message) {
System.out.println("channel:"+ channel + " message:" + message+":");
}
}
Subscribe to __keyevent@0__:expired (note that @ is not followed by an asterisk but 0, only db0 can be monitored, otherwise it will not be monitored)
@Autowired
private JedisKeyExpirationListener jedisKeyExpirationListener;
static JedisPool pool = new JedisPool("192.168.92.128",6380);
@Override
public void run(String... args) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
pool.getResource().subscribe(jedisKeyExpirationListener,"__keyevent@0__:expired");
}
}).start();
Jedis jedis = pool.getResource();
jedis.set("wl","你好");
jedis.expire("wl",5);
jedis.close();
}
Finally, introduce a free redis client
https://github.com/qishibo/AnotherRedisDesktopManager