一、消息服务的分类
1.JMS(Java Message Servcie)Java消息服务
基于java消息代理的规范,ActiveMQ、HornetMQ。
- 通过java api进行调用
- 不可跨平台
- 支持pear-2-pear,pub/sub两种模式
- 支持TextMessage、MapMessage、BytesMessage、StreamMessage、ObjectMessage几种消息格式
2.AMQP(Advance Message Queuing Protocol)
高级消息队列协议
RabbitMQ
- 通过网络级协议调用
- 跨平台支持
- 支持direct exchange、fanout exchange、topic exchange、headers exchange、system exchange几种消息订阅模式
二、RabbitMQ介绍
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
三、相关概念
- Server:接收客户端的连接,实现AMQP实体服务。
- Connection:连接,应用程序与Server的网络连接,TCP连接。
- Channel:信道,消息读写等操作在信道中进行。客户端可以建立多个信道,每个信道代表一个会话任务。
- Message:消息,应用程序和服务器之间传送的数据,消息可以非常简单,也可以很复杂。有Properties和Body组成。Properties为外包装,可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body就是消息体内容。
- Virtual Host:虚拟主机,用于逻辑隔离。一个虚拟主机里面可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名称的Exchange或Queue。
- Exchange:交换器,接收消息,按照路由规则将消息路由到一个或者多个队列。如果路由不到,或者返回给生产者,或者直接丢弃。RabbitMQ常用的交换器常用类型有direct、topic、fanout、headers四种,后面详细介绍。
- Binding:绑定,交换器和消息队列之间的虚拟连接,绑定中可以包含一个或者多个RoutingKey。
- RoutingKey:路由键,生产者将消息发送给交换器的时候,会发送一个RoutingKey,用来指定路由规则,这样交换器就知道把消息发送到哪个队列。路由键通常为一个“.”分割的字符串,例如“com.rabbitmq”。
- Queue:消息队列,用来保存消息,供消费者消费。
四、交换机介绍
1、direct exchange
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。
- 绑定的queue需要跟routekey完全匹配才能路由到该队列。
- 一个queue可以根据不同的routekey多次绑定到同一个交换机。
- *、#通配符在该模式下不生效。
- 同一个queue如果有多个连接时,默认以轮询的方式接收到消息。
上图在springboot中实现如下:
@Test
void directExchange(){
//创建direct交换机
amqpAdmin.declareExchange(new DirectExchange("direct.exchange"));
//创建orange队列
amqpAdmin.declareQueue(new Queue("fruit.queue",true,false,true,null));
//绑定fruit.queue到direct.exchange,routekey为orange的消息将会匹配到该队列
amqpAdmin.declareBinding(new Binding("fruit.queue", Binding.DestinationType.QUEUE,"direct.exchange","orange",null));
//创建color队列
amqpAdmin.declareQueue(new Queue("color.queue",true,false,true,null));
//绑定color.queue到direct.exchange,routekey为black、green的消息将会匹配到该队列
amqpAdmin.declareBinding(new Binding("color.queue", Binding.DestinationType.QUEUE,"direct.exchange","black",null));
amqpAdmin.declareBinding(new Binding("color.queue", Binding.DestinationType.QUEUE,"direct.exchange","green",null));
}
执行完后在RabbitMQ 控制台看到绑定情况如下:
测试情况如下:
@Test
void sendCheliangMessage(){
Map<String,Object> map = new HashMap<>();
map.put("title","a web message");
map.put("time",new Date());
rabbitTemplate.convertAndSend("direct.exchange","",map); //都不能收到消息
rabbitTemplate.convertAndSend("direct.exchange","orange",map); //fruit.queue 能收到消息
rabbitTemplate.convertAndSend("direct.exchange","apple",map); //都不能收到消息
rabbitTemplate.convertAndSend("direct.exchange","black",map); //color.queue能收到消息
rabbitTemplate.convertAndSend("direct.exchange","green",map); //color.queue能收到消息
rabbitTemplate.convertAndSend("direct.exchange","red",map); //都能收到消息
}
2、fanout exchange
fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。
- 消息通过广播直接发送到绑定的queue。
- routekey在该模式下不起作用。
- 同一个queue如果有多个连接时,默认以轮询的方式接收到消息。
上图在springboot中实现如下:
@Test
void fanoutExchange(){
//创建fanout交换机
amqpAdmin.declareExchange(new FanoutExchange("fanout.exchange"));
//创建fanout.queue.1队列
amqpAdmin.declareQueue(new Queue("fanout.queue.1",true,false,true,null));
//绑定fanout.queue.1到fanout.exchange,routekey在该模式下不生效
amqpAdmin.declareBinding(new Binding("fanout.queue.1", Binding.DestinationType.QUEUE,"fanout.exchange","",null));
//创建fanout.queue.2队列
amqpAdmin.declareQueue(new Queue("fanout.queue.2",true,false,true,null));
//绑定fanout.queue.2到fanout.exchange,routekey在该模式下不生效
amqpAdmin.declareBinding(new Binding("fanout.queue.2", Binding.DestinationType.QUEUE,"fanout.exchange","",null));
}
执行完后在RabbitMQ 控制台看到绑定情况如下:
测试情况如下:
@Test
void sendCheliangMessage(){
Map<String,Object> map = new HashMap<>();
map.put("title","a web message");
map.put("time",new Date());
//广播模式下queue1和queue2消息都能正常收到
rabbitTemplate.convertAndSend("fanout.exchange","",map);
}
3、topic exchange
topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:
- routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
- binding key与routing key一样也是句点号“. ”分隔的字符串
- binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
上图在springboot中实现如下:
@Test
void topicExchange(){
//创建topic交换机
amqpAdmin.declareExchange(new TopicExchange("topic.exchange"));
//创建topic.queue.1队列
amqpAdmin.declareQueue(new Queue("topic.queue.1",true,false,true,null));
//绑定topic.queue.1到topic.exchange,能匹配到routekey为两个单词,其中第二个单词为orange的消息
amqpAdmin.declareBinding(new Binding("topic.queue.1", Binding.DestinationType.QUEUE,"topic.exchange","*.orange",null));
//创建topic.queue.2队列
amqpAdmin.declareQueue(new Queue("topic.queue.2",true,false,true,null));
//绑定topic.queue.2到topic.exchange,能匹配routekey三个单词,其中最后一个单词为rabbit的消息,以及能匹配以单词lazy开头的消息
amqpAdmin.declareBinding(new Binding("topic.queue.2", Binding.DestinationType.QUEUE,"topic.exchange","*.*.rabbit",null));
amqpAdmin.declareBinding(new Binding("topic.queue.2", Binding.DestinationType.QUEUE,"topic.exchange","lazy.#",null));
}
创建完成后rabbitMQ控制台绑定情况如下:
消息测试情况如下:
@Test
void sendCheliangMessage(){
Map<String,Object> map = new HashMap<>();
map.put("title","a web message");
map.put("time",new Date());
rabbitTemplate.convertAndSend("topic.exchange","hangzhou.orange",map); //queue.1 能收到
rabbitTemplate.convertAndSend("topic.exchange","china.hangzhou.orange",map); //都收不到
rabbitTemplate.convertAndSend("topic.exchange","hangzhou.rabbit",map); //都收不到
rabbitTemplate.convertAndSend("topic.exchange","china.hangzhou.rabbit",map); //queue.2能收到
rabbitTemplate.convertAndSend("topic.exchange","lazy.orange",map); //queue1 queue2 都能收到
rabbitTemplate.convertAndSend("topic.exchange","lazy.orange",map); //queue1 queue2 都能收到 *.orange lazy.#
rabbitTemplate.convertAndSend("topic.exchange","lazy.rabbit",map); //queue2 都能收到 匹配 lazy.#"
rabbitTemplate.convertAndSend("topic.exchange","lazy.hangzhou.rabbit",map); //queue2 能收到 匹配*.*.rabbit lazy.#
rabbitTemplate.convertAndSend("topic.exchange","lazy.hangzhou.china",map); //queue2 能收到 匹配lazy.#
}
四、springboot监听
在springboot进行RabbitMQ的消息监听步骤如下:
1.在application中添加@EnableRabbit注解。
2.新建service增加@Service注解,在监听函数中增加@RabbitListener注解
@Service
public class FanoutService {
//在默认情况下此模式,执行时如果rabbitMQ中未建queue则会报错,关闭应用后queue也被删除了。
@RabbitListener(queues="fanout.queue.1")
public void receive(Object obj){
System.out.println("fanoutService Receive:"+obj.toString());
}
}
public static final String RABBITMQ_EXCHANGENAME_WEB="hz-cheliang-exchanges-web-topic";
public static final String CHELIANG_UP_WEB_MSG_QUEUE="hz-cheliang-up-queue-msg-web";//上行消息到web监控平台的队列
public static final String CHELIANG_UP_APP_MSG_QUEUE="hz-cheliang-up-queue-msg-app";//上行消息到app监控平台的队列
@Service
public class AppService {
//启动时会自动创建queue
@RabbitListener(bindings = @QueueBinding(
value=@Queue(value = RabbitmqApplication.CHELIANG_UP_APP_MSG_QUEUE,durable = "true",autoDelete = "true"),
exchange = @Exchange(value = RabbitmqApplication.RABBITMQ_EXCHANGENAME_WEB,type="topic"),
key = "cheliang_app.*")
)
public void reeceive(Object obj){
System.out.println("AppService Receive:"+obj.toString());
}
}
3.对RabbitMQ服务信息进行配置。
spring.rabbitmq.addresses=172.16.36.157
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.port=5672