Reids之消息队列

  • 1 消息队列
  • 1.1 消息队列
  • 1.2 延迟消息队列


1 消息队列

平时说的消息队列,一般都是指RabbitMQ,RocketMQ,ActiveMQ以及大数据里面的Kafka,这些是我们比较常见的消息中间件,也是非常专业的消息中间件。而作为专业的中间件,它里面提供了很多关于消息中间件的功能。

但是,我们在使用消息中间件的时候,不是每一次都需要专业的消息中间件,假如我们只有一个消息队列,一个消费者,那就没有必要使用上面这些专业的消息中间件,这种情况我们可以直接使用Redis来做消息队列。

Redis的消息队列不是特别专业,没有很多高级特性,适用于简单的场景,如果对于消息可靠性有极高的追求,那么不适合使用Redis做消息队列

1.1 消息队列

Redis作为消息队列,使用它里面的List数据结构就可以实现。我们可以使用Ipush/rpush操作来实现入队,然后使用Ipop/rpop来实现出队。
Redis之list

在java的客户端,我们会维护一个死循环来不停的从队列中读取消息,并处理。

  • 如果队列有消息,那么会直接获取;
  • 如果没有消息,就会陷入死循环,直到有下一次有消息进入,这种死循环会造成大量的资源浪费。
  • (没消息了就会阻塞,这个时候可以设置一个超时时间,等有消息了后才被唤醒),这里我们可以使用blpop/brpop(设置超时的等待时间)(没有数据会进入休眠状态,有数据后才会被唤醒,消息延迟为0)。

1.2 延迟消息队列

延迟队列可以通过zset来实现,因为zset中有一个score,我们可以把时间作为score,将value存到Redis中,然后通过轮询的方式,去不断的读取消息出来(就是在设置的时候,设置一个时间,在多少秒以后进行读取)。

如果消息是一个字符串,直接发生即可,如果是一个对象,则需要对对象进行序列化,这里我们使用JSON来实现序列化和反序列化。

首先在项目中,添加JSON依赖

<dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.2</version>
        </dependency>

接下来,封装一个消息对象。

public class QlMessage {
    private String id;
    private Object data;
    @Override
    public String toString() {
        return "QlMessage{" +
                "id='" + id + '\'' +
                ", data=" + data +
                '}';
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
}

封装的消息队列:

public class DelayMsgQueue {
    private Jedis jedis;
    private String queue;
    public DelayMsgQueue(Jedis jedis, String queue) {
        this.jedis = jedis;
        this.queue = queue;
    }
    //定义消息的入队方法,data是要发送的消息
    public void queue(Object data){
        //构造一个QlMessage
        QlMessage msg = new QlMessage();
        msg.setId(UUID.randomUUID().toString());
        msg.setData(data);
        //序列化
        try {
            String s = new ObjectMapper().writeValueAsString(msg);
            System.out.println("msg publish:"+new Date());
            //消息发送,score延迟5秒,即需要等到5秒后才可以查询到你添加的数据
            jedis.zadd(queue,System.currentTimeMillis()+5000,s);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
    /**
     * 消息消费
     */
    public void loop(){
        //如果当前线程没有被打断
        while (!Thread.interrupted()){
            //读取sorce在0找当前时间戳之间的消息,最后的值设置的是每次只读一条
            Set<String> zrange = jedis.zrangeByScore(queue, 0, System.currentTimeMillis(), 0, 1);
            if(zrange.isEmpty()){
                //如果消息是空的,则消息500毫秒,然后继续
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    break;
                }
                continue;
            }
            //如果读取到消息,则直接读取消息出来
            String next = zrange.iterator().next();
            if(jedis.zrem(queue,next)>0){
                //抢到了,接下来处理业务
                try {
                    QlMessage msg = new ObjectMapper().readValue(next, QlMessage.class);
                    System.out.println("receive msg:"+msg);//收到消息
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

测试代码:

class DelayMsgTest {
    public static void main(String[] args) {
        Redis redis=new Redis();
        redis.exectu(jedis -> {
            //构造一个消息队列
            DelayMsgQueue queue = new DelayMsgQueue(jedis, "java-delay-queue");
            //构造消息生产者
            Thread producer = new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        queue.queue("www.baidu.com"+i);
                    }
                }
            };
            //构造一个消息消费者h
            Thread consumer = new Thread(){
                @Override
                public void run() {
                    queue.loop();
                }
            };
            //启动
            producer.start();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            consumer.start();
            //休息 7 秒后,停止程序
            try {
                TimeUnit.SECONDS.sleep(7);
                consumer.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

运行结果:

msg publish:Tue Jun 15 23:25:38 CST 2021
msg publish:Tue Jun 15 23:25:39 CST 2021
msg publish:Tue Jun 15 23:25:39 CST 2021
msg publish:Tue Jun 15 23:25:39 CST 2021
msg publish:Tue Jun 15 23:25:39 CST 2021
receive msg:QlMessage{id='4b9609cd-401c-4406-8778-3caa26f79cfe', data=www.baidu.com0}:Tue Jun 15 23:25:44 CST 2021
receive msg:QlMessage{id='8c579e33-cb54-4c0a-90e4-28ee468d7dcb', data=www.baidu.com1}:Tue Jun 15 23:25:44 CST 2021
receive msg:QlMessage{id='4ac6b7df-bd82-4142-b916-a75df9d4f44f', data=www.baidu.com2}:Tue Jun 15 23:25:44 CST 2021
receive msg:QlMessage{id='dfcba13b-036c-4978-8f09-725e365e3aa0', data=www.baidu.com3}:Tue Jun 15 23:25:44 CST 2021
receive msg:QlMessage{id='c553e040-e361-4dbc-8bd0-ad99dfc332ec', data=www.baidu.com4}:Tue Jun 15 23:25:45 CST 2021