Redis 应用--延迟消息队列
我们平时说的消息队列是指:RabbitMQ,RockerMQ,ActiveMQ 以及大数据的 Kafka,这是我们常见的也是非常专业的消息中间件,里面提供了丰富的功能;
但是当我需要使用消息中间件时,并非都需要使用以上专业的消息中间件,比如:我们只有一个消息队列,只有一个消费者,那就没必要使用上面非常专业的消息中间件,这种场景可以直接使用 Redis 来做消息队列(Redis 消息队列 并不专业,没有很多高级特性,适用于简单场景)若对消息可靠性有极高的要求,就不适合使用 Redis;
1、普通消息队列 Redis 作为消息中间件,可以使用 Redis List 数据结构即可实现,使用 lpus/rpush 实现消息入队,使用 lpop/rpop 实现消息出队
127.0.0.1:6379> LPUSH wdh01-queue java linux oracle hive (integer) 4 127.0.0.1:6379> length wdh01-queue (error) ERR unknown command 'length' 127.0.0.1:6379> LLEN wdh01-queue (integer) 4 127.0.0.1:6379> LPOP wdh01-queue "hive" 127.0.0.1:6379> RPOP wdh01-queue "java" 127.0.0.1:6379>
在客户端维护一个死循环,读取消息并处理,若队列有消息则获取,否则陷入死循环,直到下一次有消息进行处理;这种死循环会造成大量资源浪费;此时可以使用 blpop/nrpop (无消息进入阻塞状态,有效及唤醒,无延迟)
2、延迟消息队列
延迟消息队列可以通过 Zset 实现,Zset 中有一个 score ,我们可以使用时间作为 score,将 value 存到 Redis 中,通过轮询方式不断读取消息
首先 消息是一个字符串直接发送即可,若消息是对象需要对其进行序列化,我这使用 JSON 实现对象序列化和反序列化,项目中需添加下 JSON 依赖;
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.3</version> </dependency> ,
构造消息对象
public class Wdh01Message { private String id; private Object date; public String getId() { return id; } public void setId(String id) { this.id = id; } public Object getDate() { return date; } public void setDate(Object date) { this.date = date; } @Override public String toString() { return "Wdh01Message{" + "id='" + id + '\'' + ", date=" + date + '}'; } }
封装消息队列
public class DelayMsgQueue { private Jedis jedis; private String queue; public DelayMsgQueue(Jedis jedis, String queue) { this.jedis = jedis; this.queue = queue; } /** * 消息入队 * * @param object 要入队的消息 */ public void queue(Object object) { // 构造 msg Wdh01Message msg = new Wdh01Message(); msg.setId(UUID.randomUUID().toString()); msg.setDate(object); //序列化 try { String s = new ObjectMapper().writeValueAsString(msg); //消息发送,延迟 5s System.out.println("--- msg push ---- " + new Date()); jedis.zadd(queue, System.currentTimeMillis() + 5000, s); } catch (Exception e) { e.printStackTrace(); } } /** * 消息消费 */ public void loop() { while (!Thread.interrupted()) { //读取时间 在 0 ~ 当前 score 的消息,每次读取 一条 Set<String> strings = jedis.zrangeByScore(queue, 0, System.currentTimeMillis(), 0, 1); if (strings.isEmpty()) { // 消息为空,休息一下,稍后重试 try { Thread.sleep(500); } catch (Exception e) { break; } continue; } // 读取到消息,直接读取 String next = strings.iterator().next(); // 移除消息 if (jedis.zrem(queue, next) > 0) { // 强盗消息,处理业务 try { // 反序列化 Wdh01Message wdh01 = new ObjectMapper().readValue(next, Wdh01Message.class); System.out.println("--- get msg ----"+ new Date() + " -- "+ wdh01); } catch (JsonProcessingException e) { e.printStackTrace(); } } } } }
测试
public class DelayMsgTest { public static void main(String[] args) { Redis redis = new Redis(); redis.execute(jedis -> { DelayMsgQueue delayMsgQueue = new DelayMsgQueue(jedis, "wdh-queue"); // 消息生产着 Thread produer = new Thread() { @Override public void run() { for (int i = 0; i < 5; i++) { delayMsgQueue.queue("wdh01 --- " + i); } } }; // 消息消费者 Thread consumer = new Thread() { @Override public void run() { delayMsgQueue.loop(); } }; // 启动 produer.start(); consumer.start(); // 休息 7s try { Thread.sleep(20000); consumer.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } }); } }