转!!redis集群基于zset和key失效事件监听机制 实现延迟队列定时任务
史上最全的延迟任务实现方式汇总!附代码(强烈推荐)
https://zhuanlan.zhihu.com/p/130994601
Redis集群下的过期监听事件notify-keyspace-events
http://events.jianshu.io/p/a95a4da09bec
SpringBoot整合Redis,订阅、发布、过期事件
https://mp.weixin.qq.com/s?__biz=MzU2NjcyMTM0Mg==&mid=2247485252&idx=1&sn=90e3b578904f155733ca1980f52ba0f2&chksm=fca96915cbdee0031cdfae63af6509c11047956c228afdf69cfec73e8a71fffd1b853e33d5fc&scene=21#wechat_redirect
1)基于redis-key失效事件,实现延迟任务
package com.cmcc.open.ota.config.redisson; import io.lettuce.core.RedisURI; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.models.partitions.RedisClusterNode; import io.lettuce.core.cluster.pubsub.RedisClusterPubSubAdapter; import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection; import io.lettuce.core.cluster.pubsub.api.async.NodeSelectionPubSubAsyncCommands; import io.lettuce.core.cluster.pubsub.api.async.PubSubAsyncNodeSelection; import io.lettuce.core.pubsub.RedisPubSubAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.stereotype.Component; import java.util.List; /** * @Description: redis key 失效 事件监听 */ @SuppressWarnings("rawtypes") @Component public class ClusterSubscriber extends RedisPubSubAdapter implements ApplicationRunner { private static Logger log = LoggerFactory.getLogger(ClusterSubscriber.class); //过期事件监听 // private static final String EXPIRED_CHANNEL = "__keyevent@*__:expired"; private static final String EXPIRED_CHANNEL = "__keyevent@0__:expired"; // @Value("${spring.redis.cluster.nodes}") // private String clusterNodes; @Value("${spring.redis.password}") private String password; @Autowired private RedisProperties redisProperties; @Override public void run(ApplicationArguments args) throws Exception { log.info("过期事件,启动监听......"); //项目启动后就运行该方法 startListener(); } /** * 启动监听 */ @SuppressWarnings("unchecked") public void startListener() { //redis集群监听 List<String> nodes = redisProperties.getCluster().getNodes(); String[] redisNodes = nodes.toArray(new String[nodes.size()]); //监听其中一个端口号即可 RedisURI redisURI = RedisURI.create("redis://" + redisNodes[0]); redisURI.setPassword(password); RedisClusterClient clusterClient = RedisClusterClient.create(redisURI); StatefulRedisClusterPubSubConnection<String, String> pubSubConnection = clusterClient.connectPubSub(); //redis节点间消息的传播为true pubSubConnection.setNodeMessagePropagation(true); //过期消息的接受和处理 pubSubConnection.addListener(new RedisClusterPubSubAdapter(){ @Override public void message(RedisClusterNode node, Object channel, Object message) { String msg = message.toString(); log.info("过期事件的监听" + msg); //TODO } }); //异步操作 PubSubAsyncNodeSelection<String, String> masters = pubSubConnection.async().masters(); NodeSelectionPubSubAsyncCommands<String, String> commands = masters.commands(); //设置订阅消息类型,一个或多个 commands.subscribe(EXPIRED_CHANNEL); } }
2)基于redis-zset实现延迟任务
package com.cmcc.open.ota.config; import com.alibaba.excel.util.CollectionUtils; import com.cmcc.open.ota.util.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Service; import java.time.Instant; import java.util.Set; @Slf4j @Service public class DelayTaskDemo implements ApplicationRunner { @Autowired private RedisUtil redisUtil; //2.基于redis-zset public void runDelayTaskBaseRedisZset(String zsetKey) { while (true) { log.info("DelayTask check"); Instant nowInstant = Instant.now(); long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); long nowSecond = nowInstant.getEpochSecond(); Set<String> data = redisUtil.zsetRangeByScore(zsetKey, lastSecond, nowSecond); if (!CollectionUtils.isEmpty(data)) { for (String msg : data) { log.info("msg={}", msg.toString()); } } redisUtil.zsetremoveRangeByScore(zsetKey, lastSecond, nowSecond); try { Thread.sleep(1000l); } catch (InterruptedException e) { log.error(e.getMessage(), e); } } } @Override public void run(ApplicationArguments args) throws Exception { String zsetKey = "test123"; Instant nowInstant = Instant.now(); log.info("now={}", nowInstant); log.info("now +1s ={}", nowInstant.plusSeconds(1).getEpochSecond()); redisUtil.addZset(zsetKey, "order3", nowInstant.plusSeconds(3).getEpochSecond()); redisUtil.addZset(zsetKey, "order5", nowInstant.plusSeconds(5).getEpochSecond()); redisUtil.addZset(zsetKey, "order7", nowInstant.plusSeconds(7).getEpochSecond()); runDelayTaskBaseRedisZset(zsetKey); } }
其中RedisUtil工具类
@Component public final class RedisUtil { @Autowired private RedisTemplate redisTemplate; // =============================common============================ public boolean addZset(String zsetKey, String value, double score) { return redisTemplate.opsForZSet().add(zsetKey, value, score); } public Set<String> zsetRangeByScore(String zsetKey, double min, double max) { return redisTemplate.opsForZSet().rangeByScore(zsetKey, min, max); } public Long zsetremoveRangeByScore(String zsetKey, double min, double max) { return redisTemplate.opsForZSet().removeRangeByScore(zsetKey, min, max); } }