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.#"?

 

最后,小图一张,晚安!