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