发送者选择的分布
RabbitMQ 2.4.0 引入了一项扩展,允许发布者在 CC 和 BCC 消息头中指定多个路由键。BCC 标头在消息传递之前被从消息中移除。直接和主题交换是仅有的使用路由键的标准交换类型,因此此功能的路由逻辑仅适用于这些交换类型。
我为什么要使用这个功能?
1. 自定义路由逻辑
当路由规则过于复杂,无法用标准交换器表达时,您通常会求助于外部或自定义交换器。CC/BCC 标头允许对等方通过填充匹配路由的标头来实现潜在的复杂路由规则。
想象一下 RabbitMQ 代理接收 Java Log4J 消息,而我们只对在非工作时间收到的 SEVERE 级别的消息感兴趣。这假设有一个 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 特定的扩展都会使将 RabbitMQ 替换为其他 AMQP 代理变得更加困难——发件人选择的路由也不例外。
如果您的应用程序已经使用了名为 CC 或 BCC 的标头,那么您应该使用不同的键,或者联系 RabbitMQ 团队寻求帮助。