消息队列
为什么需要消息队列
所有东西的出现都存在一定的原因,消息队列一定是解决了很多问题,所以它才会存在。这个应该也是当我们了解到一个新的知识下的时候首先需要去做的是明确为什么存在这个技术,这个技术可以解决哪些问题,这样再去进行相关的学习,这样你才会对于它的作用理解的更加深刻!
为什么需要消息队列呢?这个可以从一个实际的例子出发:
取快递的例子,菜鸟驿站就是快递员把东西放在菜鸟驿站,我们有时间再去拿,菜鸟驿站就可以理解成一个消息队列。它实现了什么,就是消息队列的作用。
1、解耦:如果没有这样的菜鸟驿站,需要收货人和快递员约定时间,去完成快递交易。这样对于收货人以及快递小哥都需要协调互相的时间以及地点等,存在很大的耦合,但是很明显,菜鸟驿站就直接解决了这个问题。
2、异步:快递小哥把东西放在菜鸟驿站就可以继续去送其他人的快递,而不需要等待把东西送到收货人手中再去进行接下来的工作。
3、 削峰:当快递很多的时候,不同的多个快递让我去不同的地方拿快递,而菜鸟驿站实现了我去一个地方同时拿到多个快递,当然就削峰了。就会存在一个缓冲的感觉。
关于异步我有话说:
消息队列不能仅仅理解成一个放消息的东西。所谓的消息是一个广义的。
设想一个情景:在双十一的前提下,用户提交订单之后,为没有支付的订单设定一个半个小时过期时间。在半个小时内支付了订单就会使得订单生效,但是对于没有在半个小时内支付的,订单到半个小时会失效,并且取消之间预减的库存。还有淘宝
这些场景都可以用到消息延迟!!!
rabbitmq支持延迟消息但不支持定时消息
rabbitMq简单原理
过程
整个过程就是生产者发送一个带上标签的消息发给交换机上,交换机以及标签放到相应的queue中,消费者从队列中获得消息的过程。
**注意:**这里的消费者从队列中获得相关信息的时候,队列不是讲所有的消息都发送给所有连接的消费者,而是采用轮询的方式轮流发送。所以RabbitMQ不支持广播消费。
所谓标签,其实专业一点就是生产者给交换机发送消息的时候要附带一个RoutingKey。而在交换机和队列联通的链路上每个都有各自的BindingKey,只有RoutingKey以及BindingKey匹配的时候才会放到相应的队列中。(但是不同的交换机机制不同)
交换机种类
direct:此类交换机当仅当两个完全匹配上才会放到相应的队列中
topic:允许模糊匹配,存在通配符#以及*
fanout:忽略匹配,会发送到该交换机连接的所有的queue
headers:不依据RoutingKey,而是依据发送消息的headers
rabbitMQ消息队列实现收到延迟消息
之前在ubuntu上安装过,还搭过相关的集群,写的是很完整的,可以参考:
今天在windows10进行安装:
安装erlang
参考别人的文章:https://blog.youkuaiyun.com/linsongbin1/article/details/80170487
安装rabbitMq
参考别人的文章:https://blog.youkuaiyun.com/linsongbin1/article/details/80170567
安装一个rabbitMQ延迟消息的一个插件
https://dl.bintray.com/rabbitmq/community-plugins/3.7.x/rabbitmq_delayed_message_exchange/rabbitmq_delayed_message_exchange-20171201-3.7.x.zip
上面是地址,注意要和rabbitmq版本进行匹配!
几个用得到的命令
这里存在有很多的重启rabbitMQ服务的需求,因此罗列一下相关的命令(对于windows下需要在安装目录下的sbin下输入相关的命令哦!):
开启服务:rabbitmq-server -detached
停止服务:rabbitmqctl stop
集成rabbitMq
RabbitMqConfig.class
关于这个配置文件主要就是为了保证rabbitmq的连接保证。
@Configuration
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitMqConfig {
private String host;
private int port;
private String userName;
private String password;
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory cachingConnectionFactory=new CachingConnectionFactory();
cachingConnectionFactory.setUsername(userName);
cachingConnectionFactory.setPort(port);
cachingConnectionFactory.setHost(host);
cachingConnectionFactory.setPassword(password);
cachingConnectionFactory.setVirtualHost("/");
cachingConnectionFactory.setPublisherConfirms(true);
return cachingConnectionFactory;
}
@Bean
public RabbitTemplate rabbitTemplate(){
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory());
return rabbitTemplate;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
QueueConfig.class
@Configuration
public class QueueConfig {
//定义一个交换机
@Bean
public CustomExchange delayExchange(){
Map<String,Object> args=new HashMap<>();
args.put("x-delay-type","direct");
return new CustomExchange("test_exchange","x-delayed-message",true,false,args);
}
//定义一个队列
@Bean
public Queue queue(){
Queue queue=new Queue("test_queue_1",true);
return queue;
}
//绑定这个队列以及这个交换机(bindingKey=test_queue_1)
@Bean
public Binding binding(){
return BindingBuilder.bind(queue()).to(delayExchange()).with("test_queue_1").noargs();
}
}
上面就是完成了以上工作,test_queue_1就是这个BindingKey
MessageServiceImpl .class
@Service
public class MessageServiceImpl {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String queueName,String msg){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("消息发送时间:"+sdf.format(new Date()));
//向名字是test_exchange发送msg这个消息,并且给这个消息加个头x-delay
rabbitTemplate.convertAndSend("test_exchange", queueName, msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//给消息加上header,表明是一个延迟消息,并且设置过期时间3秒
message.getMessageProperties().setHeader("x-delay",3000);
return message;
}
});
}
}
上面所谓的那个queueName,其实就是RoutingKey,需要生产者指定。
所以关于延迟消息其实上一个消息的header保证的!
MessageReceiver.class
@Component
public class MessageReceiver {
@RabbitListener(queues = "test_queue_1")
public void receiver(String msg){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("消息接收时间:"+sdf.format(new Date()));
System.out.println("接收到的消息:"+msg);
}
}
这个消息的接受者连接到test_queue_1这个队列,可以连接多个。
sendController.class
@RestController
public class sendController {
@Autowired
private MessageServiceImpl messageService;
@GetMapping("/send/{msg}")
public void send(@PathVariable("msg")String msg){
messageService.sendMessage("test_queue_1",msg);
}
}
http://localhost:8071/rabbitmq/send/xxxxx
多测试几次就会成功!会在3秒后收到消息!
eService;
@GetMapping("/send/{msg}")
public void send(@PathVariable("msg")String msg){
messageService.sendMessage("test_queue_1",msg);
}
}
http://localhost:8071/rabbitmq/send/xxxxx
多测试几次就会成功!会在3秒后收到消息!