《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();
            }
        }
      }
    
posted @ 2020-11-15 11:38  Jimmyhe  阅读(156)  评论(0编辑  收藏  举报