之前的学习中,我们了解的工作队列实现的是:一个消息只发送到一个消费者。
现在我们来学习一下新模式:发布/订阅模式
之前我们在原理中介绍了exchange,但好像并没有使用。而是直接往队列里发消息和取消息。现在我们使用rabbitmq的消息模型,利用交换机来转发消息。
首先我们来了解一下exchange的四种类型:fanout、direct、topic、headers
fanout:
1、消息与队列匹配规则:该类型交换机会将收到的消息广播给所有与之绑定的队列
2、该情况下,生产者只关系消息发送到哪个交换机,由交换机决定消息发送到哪些队列。而消费者只关系订阅哪个队列
direct:
1、消息分发规则:消息会被推送至绑定键(binding key)和消息发布附带的路由键(routing key)完全匹配的消息
2、图示说明:消息1附带路由键“error”、与绑定键“error”匹配,而队列Q4、Q5与交换机X间都存在绑定键“error”所以消息1被分发到Q4、Q5;消息2附带路由键“info”,而队列Q4与交换机间存在绑定建“info”,所以消息2被分发到队列Q4。
3、分发到队列的消息不再带有发送者的任何信息,如果消息实体里包含了发送者的信息,那么消费者可以获取发送者信息
topic:
1、消息分发规则:一个附带特殊的路由键(routing key)会被转发到绑定键(binding key)与之匹配的队列中
2、routing key与binding key匹配规则: routing key必须是由点隔开的一系列的标识符组成。标识符可以是任何东西,但一般都与消息的某些特效有关。如:"stock.usd.nyse","nyse.vmw","quick.orange.rabbit"。上限为255字节。 绑定键与选择键形式一样。 两种特殊情况:*匹配一个标识符、#匹配0个或多个标识符
3、图示说明: 消息1附带路由键“fast.orange.*”与绑定键“#”、“*.orange.*”匹配,所以消息1被分发给队列Q6、Q7;消息2附带路由键“lazy.orange.a.b”与绑定键“#”、“lazy.#”匹配,所以消息2被分发给队列Q6、Q8。
headers:
1、消息分发规则: headers类型的交换机发消息不依赖routing key。而是使用发消息时basicProperties对象中的headers匹配。headers是一个键值对类型。发送者发消息时将这些键值对放在basicProperties对象的headers字段中。队列绑定交换机时绑定一些键值对。两者匹配时。队列就可以收到消息。
匹配模式有两种:
在队列绑定到交换机时用x-match来指定,all代表定义的多个键值对都要满足,any代表只要满足一个就可以。fanout,direct,topic中的routing 可以需要字符串,而headers没有这个要求。键值对可以是任何类型。
2、图示说明: 消息1附带的键值对与Q9绑定键值对的color匹配、speed不匹配,但是Q9的x-match设置为any,因此只要有一项匹配消息1就可以被分发到Q9。消息1与Q10完全匹配,消息2与Q10部分匹配,由于Q10的x-match设置为all,所以只能接受到消息1。
案例:创建一个简单的日志系统,有两个程序组成:一个输出日志消息,一个接收并打印日志消息
在该系统中,每一个cusumer都会接到所有消息,其中一个cusumer直接保存消息到磁盘,另一个cusumer输出消息
在该案例中,我们使用fanout类型的exchange,通过 channel.exchangeDeclare("logs,"fanout")来设置参数
在rabbitmq中,默认的exchange的名字是空字符串。
在coding之前我们还要了解一下些小知识:
临时队列:
当我们指定了特定名字的队列时,我们要让消费者指向同一个队列。当生产者消费者共用一个队列时。队列名字非常重要。 对于本案例的日志系统而言,我们想让每个消费者都接到所有的日志消息。而不是其中一部分。我们关心的是当前广播的消息而不是之前的。所以我们要做两件事情满足我们的要求:
1、我们连接rabbitmq服务时,需要一个新鲜的空的队列。我们自定义随机名,更好的是让rabbitmq服务器为我们的队列随机起名
2、当我们关闭消费者时,队列应该自动删除
如何创建队列名呢?
String queueName = channel.queueDeclare().getQueue();
这样可以获取随机的队列名 调用无参的queueDeclare方法时,就创建一个非持久、独特的会自动删除的队列
绑定:
我们创建了一个fanout类型的exchange和一个队列。我们需要告诉exchange发送消息到我们的队列。exchange与队列之间的关系称为绑定(binding)。
channel.queueBind(queueName,"logs",""); 这样我们就将创建的队列和exchange建立了关系。
exchange会将消息发送到这个队列 rabbitmqctl list_binding 查看所有已经存在的绑定关系
Coding一下,你就知道
producer:
cusumer:
在这个模式下:
对于producer而言,我们将消息发送给logs交换机,而不是无名的交换机。发消息时我们提供一个路由键routing key。但它的值会被fanout类型的交换机忽略。
如果路由器还没有绑定队列,这些发送给路由器的消息将会丢失。但这对我们无所谓,如果还没有cusumer监听,我们可以安全地丢弃这些消息。