1、首先redis配置文件中需要打开配置:搜索:notify-keyspace-events Ex 找到后,放开注释,保存 如下:
2、pom.xml引入依赖
3、定义配置RedisListenerConfig
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;
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
4、定义监听器,实现KeyExpirationEventMessageListener
接口
注:单机模式下测试推送正常,但是到线上的时候,用户收到了两条推送。
这时候可以考虑采用redis的setNx命令实现锁竞争,同一时间内只有一个服务能对同一个key进行抢占使用,也就是发送消息,其他没有获取到锁的服务,则直接放弃执行消息推送。
setNx命令原理就是如果key不存在的话,保存成功就返回 1 否则就返回 0。
redis的命令是单线程执行的,所以同一时间段内肯定只能有一个人能拿到成功的结果。
可以通过 redisTemplate.opsForValue().setIfAbsent(key,value,time)的方式完成 操作。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
@Autowired
private RedisTemplate<String,String> redisTemplate;
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
System.err.println(message);
System.err.println(new String(pattern));
//注意message.toString()可以获取失效的key
String expiredKey = message.toString();
if(this.lockBySecondsTime(expiredKey, 30)){
// 用户做自己的业务处理即可,
}
}
public boolean lockBySecondsTime(String key, long expirationTime){
// 此方法只适用于在 expirationTime 时间段内进行锁竞争的场景。如果超过 expirationTime 时间段,
// 锁自动失效,之前获取到锁的线程还在运行,就失去了分布式锁的意义,慎重根据自己的场景来使用。
Long timeStamp = new Date().getTime() + (expirationTime * 1000);
// 通过setNx获取锁
return ifAbsent(key, String.valueOf(timeStamp), expirationTime, TimeUnit.SECONDS);
}
public boolean ifAbsent(String key, String value, long expirationTime , TimeUnit timeUnit) {
Boolean res = (Boolean) redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.stringCommands().set(key.getBytes(), value.getBytes(),
Expiration.from(expirationTime, timeUnit), RedisStringCommands.SetOption.ifAbsent());
}
});
return res == null ? false : res;
}
}