《Redis深度历险》三(异步消息队列)
在异步消息队列的应用
队列延迟
在队列中,如果空了,消费者不断的循环pop新的,会导致CPU和redis的QPS升高。若使用sleep强制降下来,当消费者很多的话,redis依然会不断的轮询查询。
blpop/brpop可以在队列没有数据的时候休眠,一旦有数据就可以醒过来,延时几乎为0.
# 对非空列表进行操作
redis> RPUSH job programming
(integer) 1
redis> MULTI
OK
redis> BLPOP job 30
QUEUED
redis> EXEC # 不阻塞,立即返回
1) 1) "job"
2) "programming"
# 对空列表进行操作
redis> LLEN job # 空列表
(integer) 0
redis> MULTI
OK
redis> BLPOP job 30
QUEUED
redis> EXEC # 不阻塞,立即返回
1) (nil)
空闲连接
如果线程闲置久了,redis服务端会自动断开,blpop会抛出异常,要注意。
锁冲突处理
直接抛出特定类型异常
用户重试
sleep
sleep 会阻塞当前的消息处理线程,会导致队列的后续消息处理出现延迟。如果碰撞的比 较频繁或者队列里消息比较多,sleep 可能并不合适。如果因为个别死锁的 key 导致加锁不成 功,线程会彻底堵死,导致后续消息永远得不到及时处理。
延时队列
延时队列可以通过 Redis 的 zset(有序列表) 来实现。我们将消息序列化成一个字符串作 为 zset 的 value,这个消息的到期处理时间作为 score,然后用多个线程轮询 zset 获取到期 的任务进行处理,多个线程是为了保障可用性,万一挂了一个线程还有其它线程可以继续处 理。因为有多个线程,所以需要考虑并发争抢任务,确保任务不能被多次执行。
-
java延时队列的实现
package com.hjj.test; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import javafx.concurrent.Task; import redis.clients.jedis.Jedis; import java.lang.reflect.Type; import java.util.Set; import java.util.UUID; /** * @author Jimmy He * @date 2020-09-06 */ public class RedisDelayingQueue<T> { static class TaskItem<T>{ public String id; public T msg; } private Type TaskType = new TypeReference<TaskItem<T>>(){}.getType(); private Jedis jedis; private String queueKey; public RedisDelayingQueue(Jedis jedis,String queueKey){ this.jedis=jedis; this.queueKey = queueKey; } public void delay(T msg){ TaskItem<T> task = new TaskItem<>(); // 分配唯一的uuid task.id = UUID.randomUUID().toString(); task.msg = msg; // fastjson序列化 String s = JSON.toJSONString(task); // 塞入延时队列 jedis.zadd(queueKey,System.currentTimeMillis() + 5000,s); } public void loop(){ while (!Thread.interrupted()){ // 只取一条 Set<String> values = jedis.zrangeByScore(queueKey, 0, System.currentTimeMillis(), 0, 1); if (values.isEmpty()){ try{ // 休息5s再取 Thread.sleep(500); } catch (InterruptedException e) { break; } continue; } String s = values.iterator().next(); // 抢到了 if (jedis.zrem(queueKey,s) > 0){ // 反序列化 TaskItem<T> task = JSON.parseObject(s, TaskType); this.handleMsg(task.msg); } } } public void handleMsg(T msg){ System.out.println(msg); } public static void main(String[] args) { Jedis jedis = new Jedis(); final RedisDelayingQueue<String> queue = new RedisDelayingQueue<>(jedis, "q-demo"); Thread producer = new Thread() { public void run() { for (int i = 0; i < 10; i++) { queue.delay("codehole" + i); } } }; Thread consumer = new Thread() { public void run() { queue.loop(); } }; producer.start(); consumer.start(); try { producer.join(); Thread.sleep(6000); consumer.interrupt(); consumer.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }