发送方选择的分布
RabbitMQ 2.4.0 引入了一种扩展,允许发布者在 CC 和 BCC 消息头中指定多个路由键。BCC 头部在消息传递之前从消息中删除。直接和主题交换是唯一使用路由键的标准交换类型,因此此功能的路由逻辑仅适用于这些交换类型。
为什么要这样做?
1. 自定义路由逻辑
通常,当路由规则过于复杂而无法用标准交换来表达时,您会求助于外部或自定义交换。CC/BCC 标头允许对等方通过填充这些标头来实现可能很复杂的路由规则,这些标头包含匹配的路由。
想象一下,RabbitMQ 代理接收 Java Log4J 消息,我们对在办公时间之外到达的严重级别消息感兴趣。这假设 AMQP Log4J 处理程序将日志消息转发到 RabbitMQ 交换机,并且客户端(可能连接到寻呼机)从队列中检索这些消息。假设队列名为“out-of-hours-emergencies”,并且由寻呼机客户端声明。
问题是如何选择性地路由满足这些条件(严重级别和时间)的消息。Java 日志记录 API 具有足够的复杂性,可以在日志消息到达代理之前在日志处理程序中执行一些选择性处理和过滤,因此该问题可能在简单情况下在代理的上游得到解决。出于本示例的目的,我们希望在代理中集中管理所有日志生产者的路由。
日志处理程序可以通过在标头中放置信息来使用 AMQP 消息装饰有关日志事件的信息。然后可以根据这些标头使用内置的 amq.headers 交换机来路由消息。因此,第一个约束可能在不诉诸于附加功能的情况下得到满足,前提是事件严重级别出现在消息标头中。我们要求的第二个约束(仅在办公时间之外收到的消息)无法以相同的方式使用内置交换机来满足。内置交换类型只能根据消息的内容执行路由,而不能根据消息到达的时间执行路由。即使消息包含时间戳,内置交换机也无法通过不等式进行匹配。
我们可以通过依靠智能消费者来解决这个问题,该消费者在重新发布收到的消息之前填充 BCC 标头。我们示例中的相关标准将是“out-of-hours-emergencies”,因此智能消费者在重新发布在非办公时间到达的严重日志消息之前将此添加到 BCC 标头中。它可以使用其处置的任何信息来做出此确定,包括日期、时间、消息内容或来自其他来源的信息。可以使用相同的方式选择性地将任意数量的标准添加到 BCC 标头中。具有相同名称的队列将从我们的智能消费者接收所有消息,这些消息重新发布了 BCC 标头中包含此字符串的消息。此时,寻呼机客户端从“out-of-hours-emergencies”队列中检索消息并寻呼操作员。
此技术可以路由以特定于域的格式编码的消息。了解格式的智能对等体可以解包消息,用相关字段填充 BCC 标头并重新发布。智能对等体以类似于外部交换的方式起作用。
2. 机密路由
这在路由键是生产者和消费者事先同意的安全令牌的情况下很有用。通配符使主题交换在这种情况下毫无用处。使用路由键设置为“topsecret.eyesonly”发布的消息可以被任何使用通配符“#”绑定的消费者获取。
生产者可以通过填充选定接收者的路由键的 BCC 标头,将消息发送到接收者的任意子集。接收者将无法了解其他接收者的身份,因为 BCC 标头在传递之前从消息中删除。
路由信息可能仍会以其他方式泄露,例如管理和监控插件或 rabbitmqctl 管理实用程序。这些需要适当的保护。
AMQP 无法做到这一点吗?
虽然无法删除标头,但可以使用标准 AMQP 功能实现一些类似的效果。
- 生产者可以发送多条消息,每条消息使用不同的路由键。这会浪费网络带宽和代理资源,因为代理无法优化重复消息的存储。
- 生产者可以声明一个临时交换机,为每个预期接收者创建一个临时绑定。这需要很多工作,并且每次接收者集发生变化时都需要重复。
我该怎么用?
确保使用 RabbitMQ 2.4.0 或更高版本。可以使用任何 AMQP 客户端。将 CC 或 BCC 标头设置为路由键列表。标头值必须是 AMQP 数组类型,即使它只包含单个值。消息将根据 CC 和 BCC 标头中的组合路由键以及 basic.publish 方法(在本例中为“routingkey1”、“routingkey2”和“routingkey3”)路由到所有目标。
Java 示例代码
BasicProperties props = new BasicProperties();
Map<String, Object> headers = new HashMap<String, Object>();
List<String> ccList = new ArrayList<String>();
ccList.add("routingkey2");
ccList.add("routingkey3");
headers.put("CC", ccList);
props.setHeaders(headers);
channel.basicPublish(exchange, "routingkey1", props, payload);
互操作性有什么影响?
任何 AMQP 客户端都可以使用此功能。生产者只需要能够在消息中设置标头。
使用任何 RabbitMQ 特定扩展将使用其他 AMQP 代理替换 RabbitMQ 变得更加困难——发送方选择的分布也不例外。
如果您的应用程序已经使用名为 CC 或 BCC 的标头,那么您应该使用不同的键,或者联系 RabbitMQ 团队寻求帮助。