消息队列,Message Queue,常用于解决并发系统中的资源一致性问题,提升峰值的处理能力,同时保证消息的顺序性、可恢复性、必送达性,对应用进行解耦,或者实现异步通讯等。市面上的 MQ应用有很多(例如:Kafka,RabbitMQ,Disque),同时也可以基于 Redis 来实现,比较典型的方案有:
讨论之前,先推荐使用 Redis5.0中的Stream方案。
典型的命令为:
LPUSH,将一个或多个消息插入到列表头部
BRPOP,从队列中取出消息,阻塞模式
就是一个典型的基于FIFL队列的解决方案。其中LPUSH是生产者做的事,而BRPOP是消费者做的事。
该模式有很多优点:
同时也有些劣势:
注意,没有好不好的技术,只有适合不适合。
实现:
在java中直接使用redis的时候,直接使用简单的两个指令lpush和rpop或者rpush和lpop就可以实现消息队列的操作。当与spring结合时,可以使用RedisTemplate和StringRedisTemplate;这两个Template是spring封装了对Redis的一些常用的操作,来实现消息队列,这两个区别于序列类。
当redis数据库里面本来存的是字符串数据或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可,如果数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。
1-这里使用了RedisTemplate对list列表进行LPUSH和BRPOP操作,省略配置redis部分,直接上代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
/**
* Created by XiChuan on 2018-11-27.
*/
@Component
public class RedisMq {
private static String key = "redis-test";
@Autowired
private RedisTemplate redisTemplate;
/**
* 发送消息
* @param message
*/
public void push(String message){
redisTemplate.opsForList().leftPush(key,message);
}
/**
* 获取消息,可以对消息进行监听,没有超过监听事件,则返回消息为null
* rightPop:1.key,2.超时时间,3.超时时间类型
* @return
*/
public String pop(){
return (String) redisTemplate.opsForList().rightPop(key,60, TimeUnit.SECONDS);
}
}
2-调用
@Controller
public class Sender{
@Autowired
RedisMq redisMq;
//在redis中存储消息
@GetMapping("/push")
public Object pushMsg(@RequestParam("msg")String msg){
redisMq.push(msg);
return "SUCCESS";
}
//从redis中获取消息
@GetMapping("/pop")
public Object popMsg(){
return redisMq.pop();
}
}
SUBSCRIBE,用于订阅信道
PUBLISH,向信道发送消息
UNSUBSCRIBE,取消订阅
生产者和消费者通过相同的一个信道(Channel)进行交互。信道其实也就是队列。通常会有多个消费者。多个消费者订阅同一个信道,当生产者向信道发布消息时,该信道会立即将消息逐一发布给每个消费者。可见,该信道对于消费者是发散的信道,每个消费者都可以得到相同的消息。典型的对多的关系。
典型的优点是:
也有些缺点:
可见,Pub/Sub 模式不适合做消息存储,消息积压类的业务,而是擅长处理广播,即时通讯,即时反馈的业务。
redis的发布订阅模式,使发布者和订阅者完全解耦
首先项目依赖和配置文件
pom.xml 引入redis依赖 application.properties配置略
org.springframework.boot
spring-boot-starter-data-redis
首先订阅者需要自己实现具体操作方法,并注册为bean
@Component
public class Recv{
public void recvMsg(String msg){ //具体操作方法 名称随意
System.out.println("收到消息:"+msg);
}
}
然后需要一个 RedisMessageListenerContainer监听容器来将订阅者与指定频道绑定
@Configuration
public class RedisConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter recvAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(recvAdapter, new PatternTopic("channel1"));//将订阅者1与channel1频道绑定
return container;
}
@Bean
MessageListenerAdapter recvAdapter(Recv receiver){ //与channel1绑定的适配器
return new MessageListenerAdapter(receiver, "recvMsg");/*收到消息时执行Recv类中的
recvMsg方法*/
}
@Bean
StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
这样,频道和订阅者 订阅者处理数据的方法就绑定好了,接下来向指定频道中发送数据时,订阅这个频道的所有client都会收到消息并执行绑定的方法。
@Componet
public class Sender{
@Autowired
private StringRedisTemplate stringRedisTemplate;
/*当这个方法被调用时,将会发布消息到channel1 然后订阅者执行对应的方法
这里就会在控制台打印hello world*/
public void send(){
stringRedisTemplate.convertAndSend("channel1","hello world");
}
}
ZADD KEY score member,压入集合
ZRANGEBYSCORE,依据score获取成员
关于有序集合zSet的使用可以用来实现延迟消息队列:
https://blog.csdn.net/weixin_40663800/article/details/103523949 具体看这篇文章
可以看:https://blog.csdn.net/weixin_37703281/article/details/93463032