RabbitMQ Topic Exchange 按照规则进行消息路由,注意这里使用的Topic表达方式并不是正则表达式.在入门教程[点击链接]里面,简单描述了一下如何编写规则:
* (star) can substitute for exactly one word.
# (hash) can substitute for zero or more words.
上面这个说法还是容易让人误解的,我尝试解释一下:
. 用来将routing key 分割成若干部分(Part)
* 匹配一个完整的Part
# 匹配一个或者多个Part
验证上面的结论很简单,我们可以使用Client创建不同Exchange来验证这一点;还有一个更为简单的方法就是找到RabbitMQ这部分实现逻辑,顾名思义很容易找到..\rabbitmq-server-2.8.7\src\rabbit_exchange_type_topic.erl文件,最新版本的代码这部分逻辑已经写得比较复杂,回溯到更早的版本rabbitmq-server-2.2.0\src\rabbit_exchange_type_topic.erl,这个就清晰多了,看它的代码:
split_topic_key(Key) -> string:tokens(binary_to_list(Key), "."). topic_matches(PatternKey, RoutingKey) -> P = split_topic_key(PatternKey), R = split_topic_key(RoutingKey), topic_matches1(P, R). topic_matches1(["#"], _R) -> true; topic_matches1(["#" | PTail], R) -> last_topic_match(PTail, [], lists:reverse(R)); topic_matches1([], []) -> true; topic_matches1(["*" | PatRest], [_ | ValRest]) -> topic_matches1(PatRest, ValRest); topic_matches1([PatElement | PatRest], [ValElement | ValRest]) when PatElement == ValElement -> topic_matches1(PatRest, ValRest); topic_matches1(_, _) -> false. last_topic_match(P, R, []) -> topic_matches1(P, R); last_topic_match(P, R, [BacktrackNext | BacktrackList]) -> topic_matches1(P, R) or last_topic_match(P, [BacktrackNext | R], BacktrackList).
看代码,一切都明了了,看下面的测试代码:
rabbit_exchange_type_topic:topic_matches
Eshell V5.9 (abort with ^G) 1> rabbit_exchange_type_topic:topic_matches(<<"a.#">>,<<"a.b">>). true 2> rabbit_exchange_type_topic:topic_matches(<<"a.#">>,<<"a.bc">>). true 3> rabbit_exchange_type_topic:topic_matches(<<"a.#">>,<<"a.bc.bc">>). true 4> rabbit_exchange_type_topic:topic_matches(<<"a.#">>,<<"a1.b">>). false 5> rabbit_exchange_type_topic:topic_matches(<<"b.a.#">>,<<"a1.b">>). false 6> rabbit_exchange_type_topic:topic_matches(<<"b.a.#">>,<<"a.b">>). false 7> rabbit_exchange_type_topic:topic_matches(<<"a.*">>,<<"a.b">>). true 8> rabbit_exchange_type_topic:topic_matches(<<"a.*">>,<<"a.bc">>). true 9> rabbit_exchange_type_topic:topic_matches(<<"a.a*">>,<<"a.bc">>). false 10> rabbit_exchange_type_topic:topic_matches(<<"a.a*">>,<<"a.ac">>). false 11> rabbit_exchange_type_topic:topic_matches(<<"a.a#">>,<<"a.ac">>). false 12> rabbit_exchange_type_topic:topic_matches(<<"a.*">>,<<"a.bc.a">>). false 13> rabbit_exchange_type_topic:topic_matches(<<"a.*.*">>,<<"a.bc.a">>). true 14> rabbit_exchange_type_topic:topic_matches(<<"a.b*">>,<<"a.bc">>). false 15> rabbit_exchange_type_topic:topic_matches(<<"a.*.*">>,<<"a.b*">>). false 16> rabbit_exchange_type_topic:topic_matches(<<"a.*">>,<<"a.b*">>). true 17> rabbit_exchange_type_topic:topic_matches(<<"a.b*">>,<<"a.b*">>). true 18> rabbit_exchange_type_topic:topic_matches(<<"*.a">>,<<"a.a">>). true 19> rabbit_exchange_type_topic:topic_matches(<<"*.a">>,<<"a.a.b">>). false 20> rabbit_exchange_type_topic:topic_matches(<<"*.a.b">>,<<"a.a">>). false 21> rabbit_exchange_type_topic:topic_matches(<<"#.a">>,<<"a.a.b">>). false 22> rabbit_exchange_type_topic:topic_matches(<<"#.a">>,<<"a.a">>). true 23> rabbit_exchange_type_topic:topic_matches(<<"#.a">>,<<"a.a.a">>). true 24> 24> rabbit_exchange_type_topic:topic_matches(<<"a.*.a">>,<<"a.a.a">>). true 25> rabbit_exchange_type_topic:topic_matches(<<"a.*a.a">>,<<"a.aa.a">>). false 26> 26> rabbit_exchange_type_topic:topic_matches(<<"*">>,<<"a.aa.a">>). false 27> rabbit_exchange_type_topic:topic_matches(<<"*">>,<<"a">>). true 28> rabbit_exchange_type_topic:topic_matches(<<"a.*.#">>,<<"a.b">>). true 29> rabbit_exchange_type_topic:topic_matches(<<"a.*.#">>,<<"a.b.c">>). true 30> rabbit_exchange_type_topic:topic_matches(<<"*.#">>,<<"a.b.c">>). true 31> rabbit_exchange_type_topic:topic_matches(<<"*.#">>,<<"a.b.c">>). true 32>
检验一下是否真的理解了,尝试回答一下Tutorials文档最后的几个变态问题:
- Will "*" binding catch a message sent with an empty routing key?
- Will "#.*" catch a message with a string ".." as a key? Will it catch a message with a single word key?
- How different is "a.*.#" from "a.#"?
最后,小图一张,晚安!