-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1926 lines (1789 loc) · 111 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://haotrr.github.io</id>
<title>Haotrr</title>
<updated>2020-06-11T06:16:28.764Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://haotrr.github.io"/>
<link rel="self" href="https://haotrr.github.io/atom.xml"/>
<subtitle>A beautiful world you deserve.</subtitle>
<logo>https://haotrr.github.io/images/avatar.png</logo>
<icon>https://haotrr.github.io/favicon.ico</icon>
<rights>All rights reserved 2020, Haotrr</rights>
<entry>
<title type="html"><![CDATA[2018 与我]]></title>
<id>https://haotrr.github.io/post/2018-and-me/</id>
<link href="https://haotrr.github.io/post/2018-and-me/">
</link>
<updated>2018-12-31T15:00:40.000Z</updated>
<summary type="html"><![CDATA[<p>感谢微博这一年来记录下了我的所思所想,今年用几条微博来回顾我的 2018。</p>
]]></summary>
<content type="html"><![CDATA[<p>感谢微博这一年来记录下了我的所思所想,今年用几条微博来回顾我的 2018。</p>
<!-- more -->
<blockquote>
<p>感谢微博这一年来记录下了我的所思所想,今年用几条微博来回顾我的 2018。</p>
</blockquote>
<blockquote>
<p><a href="https://weibo.com/1762280947/FEAzlihp5">Anti-Enterprise,不要让大公司裹挟自己的生活。</a></p>
</blockquote>
<p>新年第一天,收到了坚果 Pro 2,那部漂亮得不像实力派的安卓手机。在受够了苹果对用户的傲慢之后,在 “F**k the Apple again and again” 之后,我决定放弃使用 iPhone,尝试回归 Andriod,一年下来,我发现自己失败了,我不出意外的话,我的下一部手机会是一部 iPhone,我发现自己暂时离不开苹果的生态。我们生活被那些大公司裹挟着,苹果、微信、淘宝、微博等等,我们越是挣扎着想要逃离它们,我们越是离不开它们,现代生活就是这样。</p>
<blockquote>
<p><a href="https://weibo.com/1762280947/G0FL40Z1X">早点回家买秋裤。</a></p>
</blockquote>
<p>那天,多年不穿秋裤的我,急匆匆地赶回家,只是为了上淘宝买秋裤。在那一瞬间,突然发现自己“老了”,开始向岁月屈服。另一件让自己感到可怕的是,有一段时间自己掉发很严重,幸亏赶紧给自己买了霸王洗发露,好好护理了一段时间,才保住了我本来就不浓密的头发。看来,随着年龄增长,我也开始慢慢有了自我保养的意识呢。</p>
<blockquote>
<p><a href="https://weibo.com/1762280947/G3qHrwhrB">一家人在广州。</a></p>
</blockquote>
<p>春节一家人在广州过的。在哪里过年不重要,重要的是家人能够团聚,能够开开心心的聊聊生活,畅谈一番未来。父母总是希望我和老哥能够安份下来,好好工作,然后结婚生子。可我总觉得那样的生活好无趣,我不知道自己是否做好了成为不孝子的觉悟,而眼下,我只能在有限的时间里面好好陪陪父母,并希望他们能够原谅自私的我。</p>
<blockquote>
<p><a href="https://weibo.com/1762280947/G488B1Cvs">18 年上半年的工作任务:换份好工作。</a><br>
这是年初立的最大的 Flag,直到 9 月中旬,我才真正离开了上一家公司。而新公司入职是在 10 月中旬,中间的一个月,给自己放了一个长假,看书,逛展,刷剧,陪老哥到处玩儿,算得上人生一大幸事。然而这段时间也让我思考了很多:我到底想要成为什么样的人?以后的我能靠什么养活自己或者家人?自己是否有勇气去做自己想做的事情?我希望自己能在 30 岁之前想明白这些问题。</p>
</blockquote>
<blockquote>
<p><a href="https://weibo.com/1762280947/G8A89mllx">3.22</a></p>
</blockquote>
<p>3 月 22 日,整整55年后,The Beatles 披头士乐队音乐作品正式登陆中国。最开始用 QQ 音乐听,之后用 Apple Music 听,后来不知为什么 Apple Music 上也没法正常听披头士之后,我用网易云音乐比较多,虽然云村也没有披头士的版权。3 月 22 日凌晨,手机一直响个不停,但睡意太重,我没睁眼查看具体消息,早上起床,看到云音乐推的私信,口中爆出一连串卧槽,现在想起我那股兴奋劲,就像看科比又投进了一个只有他能投进的球一样。今年,对于一个披头士乐迷来说,是幸福的。</p>
<blockquote>
<p><a href="https://weibo.com/1762280947/GeFnmnLY7">2018/05/01 北京今日美术馆</a></p>
</blockquote>
<p>五一请了三天年假,合起来总共有八天的假期,于是策划了这次北京天津之旅。之前我去的最北方是西北甘肃,对于真正的北方,我一直想去看看。这次旅行,最主要的目的其实还是去看《The Beatles, Tomorrow》明日披头士世界巡回展。早早买了展览的联名 T 恤和帆布包,开开心心地在展馆里待一天,感到特别的满足。</p>
<blockquote>
<p><a href="https://weibo.com/1762280947/GnkeGAu71">又回家了</a></p>
</blockquote>
<p>去年冬天的两次回家,是因为两个堂哥结婚,这次夏天回家,则是奔丧。凌晨 2 点接到老哥打过来的电话,告诉我大伯婶过世了。迷迷糊糊地买好车票,安排好手头的工作,请好丧假,有迷迷糊糊地睡去,6 点起床赶高铁,回到家已经是晚上 8 点。大伯婶的去世,突然让我感到很害怕,死神的魔爪已经伸向我的父辈了。2018 年很多人离开了我们,让我想起教主那句感慨:先是我们认识的人。然后是我们身边的人。最后,是我们自己。</p>
<blockquote>
<p><a href="https://weibo.com/1762280947/GsDkzmb59">在想十一怎么过</a></p>
</blockquote>
<p>上大学之后,每年能与老哥相处的时间基本上只有过年那几天。虽然偶尔是微信或视频聊天,但一起吃饭睡觉聊天的机会确实不多。好几年前,老哥就一直念叨这要来上海转转,于是趁着老哥的十一假期和我的职业空档期,策划了这次十一之旅。也没什么特别的安排,就是在上海,杭州,苏州兜了一圈,看看展,逛逛公园,欣赏一下江南水乡,吃点江南的小吃,更多地时候还是聊天,聊工作,聊书籍,聊音乐,甚至聊聊艺术。偶尔这样,就挺好。</p>
<blockquote>
<p><a href="https://weibo.com/1762280947/GCNDc2ZrD">由俭入奢易,由奢入俭难,消费降级?不存在的。</a></p>
</blockquote>
<p>今年年轻人的话题中,消费降级算得上很热门的一个了。室友天天喊着要消费降级,喊出那句:以后超过 20 块的外卖我不点;同事们也时常感叹想买的东西都太贵。依照我的不成熟的看法,该买啥还是得买啥,我们努力工作不就是想要一个好的生活品质吗?不知从何时起,自己已经深深信奉那句:人生苦短,及时行乐。消费降级于我而言,是不存在的。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[RabbitMQ 入门教程(六)- RPC]]></title>
<id>https://haotrr.github.io/post/rabbitmq-tutorial-go-6/</id>
<link href="https://haotrr.github.io/post/rabbitmq-tutorial-go-6/">
</link>
<updated>2018-09-29T08:05:45.000Z</updated>
<summary type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第六篇,介绍如何使用 RabbitMQ 来构建一个 RPC 系统</p>
]]></summary>
<content type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第六篇,介绍如何使用 RabbitMQ 来构建一个 RPC 系统</p>
<!-- more -->
<p><a href="http://www.rabbitmq.com/tutorial-six-go.html">教程六 原文地址: "Remote procedure call(RPC)"</a></p>
<p>在 <a href="/post/rabbitmq-tutorial-go-2/">教程二</a> 中我们已经掌握了如何使用工作队列在多个工作者中分发实时消费的任务。</p>
<p>但是如果我们需要运行一个在远程机器上的函数然后等待它的运行结果呢?这就需要不同的技术了,这种模式通常被称作远程过程调用(Remote Procedure Call)或者 RPC。</p>
<p>在本教程中,我们将使用 RabbitMQ 来构建一个 RPC 系统:包含一个客户端和一个可扩展的 RPC 服务器。目前我们手头上没有值得分发的实时消费的任务,所以我们创建一个伪造用于返回斐波纳契数字(Fibonacci Number)的 RPC 服务。</p>
<h3 id="回调队列">回调队列</h3>
<p>一般来说通过 RabbitMQ 来实现 RPC 是很简单的,客户端发送一个请求消息然后服务器返回一个响应消息。为了接收响应消息,我们需要在请求中增加一个回调(callback)队列地址。可以使用默认队列,我们一起来试一试:</p>
<pre><code class="language-go">q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when usused
true, // exclusive
false, // noWait
nil, // arguments
)
err = ch.Publish(
"", // exchange
"rpc_queue", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
CorrelationId: corrId,
ReplyTo: q.Name,
Body: []byte(strconv.Itoa(n)),
})
</code></pre>
<h3 id="message-properties">Message properties</h3>
<h3 id="关联-id">关联 ID</h3>
<p>在上面的方法中,我们对每一个 RPC 请求都创建了一个回调队列,这样做效率相对低下,幸好我们还有更好的方式 -- 对每一个客户只创建一个回调队列。</p>
<p>这样会造成一个新的问题,队列对接收到的响应消息属于哪一个请求并不清楚。这时就该轮到 correlation_id 派上用场了。对每个请求,我们都将关联 ID 设为唯一值,然后当我们从回调队列中接收一个消息时,我们将使用到这个属性,通过它,我们可以将响应消息匹配的请求。对于那些未知的关联 ID 值,我们只需简单地丢弃消息 -- 它们不属于我们发送的请求。</p>
<p>你也许会问,我们为什么应该忽略回调队列中那些未知的消息而不是抛出一个错误?这是因为在服务端有可能出现竟态条件(race condition)。尽管不太常见,RPC 服务在发送一个响应消息之后但在发送一个确认消息给求情之前有可能会死掉,如果这种情况发生,重启 RPC 服务会重新处理请求,这就是为什么我们序啊哟优雅地处理重复响应的原因,RPC 应该是完全幂等的。</p>
<h3 id="总结">总结</h3>
<figure data-type="image" tabindex="1"><img src="http://img-haotrr.test.upcdn.net/blog/6-1-overall.png" alt="6-1-overall" loading="lazy"></figure>
<p>我们的 RPC 看起来应该是这样子的:<br>
客户机一旦启动,他就创建一个唯一匿名的回调队列。<br>
对每一个 RPC 请求,客户端发送的消息都包含这两个属性:reply_to 用于设置回调队列;correlation_id 用于对每一个请求设置唯一值。<br>
请求被发送到 rpc_queue 队列中。<br>
RPC 工作者(服务器)从 rpc_queue 队列中等待请求,请求一旦出现,它便处理响应的工作并通过 reply_to 字段提供的队列返回一条包含结果的消息给客户端。<br>
客户端通过回调队列等待接收数据。一旦响应消息出现,它便检查其 correlation_id 属性,如果匹配请求上的值便返回响应消息给对应的应用。</p>
<h2 id="整合来看">整合来看</h2>
<p>斐波纳契函数:</p>
<pre><code class="language-golang">func fib(n int) int {
if n == 0 {
return 0
} else if n == 1 {
return 1
} else {
return fib(n-1) + fib(n-2)
}
}
</code></pre>
<p>我们声明了一个斐波纳契函数,并假设正整数为合法输入。(不要指望它对大的整数有效,它有可能是最慢的递归实现了)。</p>
<p>RPC 服务器的代码如下:</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"strconv"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
}
func fib(n int) int {
if n == 0 {
return 0
} else if n == 1 {
return 1
} else {
return fib(n-1) + fib(n-2)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
q, err := ch.QueueDeclare(
"rpc_queue", // name
false, // durable
false, // delete when usused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
err = ch.Qos(
1, // prefetch count
0, // prefetch size
false, // global
)
failOnError(err, "Failed to set QoS")
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
false, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
forever := make(chan bool)
go func() {
for d := range msgs {
n, err := strconv.Atoi(string(d.Body))
failOnError(err, "Failed to convert body to integer")
log.Printf(" [.] fib(%d)", n)
response := fib(n)
err = ch.Publish(
"", // exchange
d.ReplyTo, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
CorrelationId: d.CorrelationId,
Body: []byte(strconv.Itoa(response)),
})
failOnError(err, "Failed to publish a message")
d.Ack(false)
}
}()
log.Printf(" [*] Awaiting RPC requests")
<-forever
}
</code></pre>
<p>服务端代码相当明了:<br>
像之前那样我们从建立连接,创建信道和声明队列开始。<br>
我们也许想要运行多个服务进程,为了均衡每个服务器的负载,我们需要在信道上设置 prefetch 属性。<br>
我们使用 Channel.Consume 来获取 Go 中从队列中接收消息的信道。然后我们进入处理具体工作和返回响应的 gorountine。</p>
<p>RPC 客户端的代码如下:</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"math/rand"
"os"
"strconv"
"strings"
"time"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
}
func randomString(l int) string {
bytes := make([]byte, l)
for i := 0; i < l; i++ {
bytes[i] = byte(randInt(65, 90))
}
return string(bytes)
}
func randInt(min int, max int) int {
return min + rand.Intn(max-min)
}
func fibonacciRPC(n int) (res int, err error) {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when usused
true, // exclusive
false, // noWait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
corrId := randomString(32)
err = ch.Publish(
"", // exchange
"rpc_queue", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
CorrelationId: corrId,
ReplyTo: q.Name,
Body: []byte(strconv.Itoa(n)),
})
failOnError(err, "Failed to publish a message")
for d := range msgs {
if corrId == d.CorrelationId {
res, err = strconv.Atoi(string(d.Body))
failOnError(err, "Failed to convert body to integer")
break
}
}
return
}
func main() {
rand.Seed(time.Now().UTC().UnixNano())
n := bodyFrom(os.Args)
log.Printf(" [x] Requesting fib(%d)", n)
res, err := fibonacciRPC(n)
failOnError(err, "Failed to handle RPC request")
log.Printf(" [.] Got %d", res)
}
func bodyFrom(args []string) int {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "30"
} else {
s = strings.Join(args[1:], " ")
}
n, err := strconv.Atoi(s)
failOnError(err, "Failed to convert arg to integer")
return n
}
</code></pre>
<p>现在是时候好好看一看整个例子的源代码了: <a href="https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/rpc_server.go">rpc_server.go</a> 和 <a href="https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/rpc_client.go">rpc_client.go</a> 。</p>
<p>RPC 服务现在已经就位,我们可以开始运行:</p>
<pre><code class="language-sh">go run rpc_server.go
# => [x] Awaiting RPC requests
</code></pre>
<p>请求一个斐波纳契数字,运行客户端:</p>
<pre><code class="language-sh">go run rpc_client.go 30
# => [x] Requesting fib(30)
</code></pre>
<p>上面展示的程序不是 RPC 服务的唯一实现,但其有几个很重要的优势:</p>
<ul>
<li>如果 RPC 服务器太慢,可以通过增加另外一个服务实例来扩展,请尝试在新的终端运行第二个 rpc_server.go。</li>
<li>对于客户端,RPC 只需发送和接收一个消息,最终,RPC 客户端对每个 RPC 请求只需要一个网络循环。</li>
</ul>
<p>我们的代码仍然相当简单,但是不要试图用它来解决更加复杂(但很重要)的问题,比如:</p>
<ul>
<li>如果没有服务程序在运行,客户端该怎样反应?</li>
<li>RPC 客户端是否需要某种超时机制?</li>
<li>如果服务器失灵并且抛出异常,应该返回给客户端吗?</li>
<li>在处理接消息之前对其进行保护(如检查边界,类型等)以免处理了非法的信息。</li>
</ul>
<blockquote>
<p>如果想要更加深入地探究,你会发现 <a href="https://www.rabbitmq.com/management.html">management UI</a> 对查看队列非常有用。</p>
</blockquote>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[RabbitMQ 入门教程(五)- 主题交换机]]></title>
<id>https://haotrr.github.io/post/rabbitmq-tutorial-go-5/</id>
<link href="https://haotrr.github.io/post/rabbitmq-tutorial-go-5/">
</link>
<updated>2018-09-29T08:05:34.000Z</updated>
<summary type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第五篇,介绍 RabbitMQ 中较复杂但非常实用的 topic 交换机。</p>
]]></summary>
<content type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第五篇,介绍 RabbitMQ 中较复杂但非常实用的 topic 交换机。</p>
<!-- more -->
<p><a href="http://www.rabbitmq.com/tutorial-five-go.html">教程五 原文地址: "Topics"</a></p>
<p>在 <a href="/post/rabbitmq-tutorial-go-4/">教程四</a> 中我们改善了日志系统,为了伪造出广播功能,我们使用 direct 交换机来替换 fanout 交换机,同时我们获得了选择性接收日志的能力。</p>
<p>尽管使用 direct 交换机提升了我们的系统,但它仍有限制 -- 无法基于多个条件来路由。</p>
<p>在我们的日志系统中,我们可能不仅仅想要基于日志严重级别来,而且同时可以几乎发送日志的来源来订阅。你此前可能了解过同时基于日志严重级别(info/warn/crit..)和设备(auth/cron/kern...)的 Unix 工具 syslog。</p>
<p>这样能够给我们提供更多的灵活性 -- 我们也许只想要监听那些来之 cron 严重的错误日志和来自 kern 的所有日志。</p>
<p>为了实现这样的日志系统,我们需要了解更加复杂的 topic 交换机。</p>
<h2 id="topic-交换机">Topic 交换机</h2>
<p>发送至 topic 交换机的消息不是使用任意字符的 <code>routing_key</code> -- 而是必须是一串使用点符号(.)分隔的单词。这些单词可以是任何字符串,但通常描述了消息的某种特性,合理的路由规则如:“stock.usd.nyse”,“nyse.vmw”,“quick.orange.rabbit” 等等。可以在路由规则中使用任意多个单词,但每个单词的长度上限为 255 字节。</p>
<p>绑定规则必须与路由规则相同,因为 topic 交换机背后的逻辑和 direct 交换机背后的逻辑一样 -- 发送给交换机的带有特定路由规则的消息会被投递到所有与绑定规则匹配的队列中。然而,对绑定规则有如下两个特殊情况:</p>
<ul>
<li>(star)可以替代一个单词</li>
<li>(hash)可以替代零个或多个单词</li>
</ul>
<p>最好通过实例来解释:</p>
<figure data-type="image" tabindex="1"><img src="http://img-haotrr.test.upcdn.net/blog/5-1-overall.png" alt="5-1-overall" loading="lazy"></figure>
<p>在上面的例子中,我们发送的消息都是用于描述动物的,这些待发送的消息的路由规则都包含三个单词(两个点),路由规则的第一个单词描述速度,第二个描述颜色,第三个描述物种:“<speed>.<colour>.<species>”。</p>
<p>同时创建三个绑定:Q1 使用路由规则 <em>.orange.</em> 绑定,Q2 使用路由规则 <em>.</em>.rabbit 和 lazy.#。</p>
<p>这些绑定可以概括为:</p>
<ul>
<li>Q1 对所有橘色的动物的消息感兴趣</li>
<li>Q2 想要获取所有关于兔子的和所有跑得慢的动物的消息</li>
</ul>
<p>一个路由规则为 quick.orange.rabbit 的消息会被同时投递到两个队列中,路由规则为 lazy.orange.elephant 的消息也是一样。另一方面,路由规则为 quick.orange.fox 的消息会被投递到第一个队列中,路由规则为 lazy.brown.fox 的消息只会被投递到第二个队列中。路由规则为 lazy.pink.rabbit 的消息只会被投递到第二个队列中一次,尽管它同时匹配两个绑定。路由规则为 quick.brown.fox 不匹配任何绑定,所以会被丢弃。</p>
<p>如果我们打破上面的规则,发送路由规则为有一个或四个单词的诸如 orange 和 quick.orange.male.rabbit 这样的消息会怎样呢?情况是,这些消息不会被任何绑定匹配从而被丢弃。</p>
<p>另外一方面,路由规则为诸如 lazy.orange.male.rabbit 这样的消息尽管有四个单词,它仍然会匹配最后一个绑定从而被投递到第二个队列。</p>
<h2 id="note">note</h2>
<h2 id="整体来看">整体来看</h2>
<p>我们将在日志系统中使用 topic 交换机,并一开始就假设日志消息的路由规则包含两个单词:<facility>.<severity></p>
<p>代码几乎与上一篇教程一模一样。</p>
<p><code>emit_log_topic.go</code> 文件中的代码如下:</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_topic", // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
body := bodyFrom(os.Args)
err = ch.Publish(
"logs_topic", // exchange
severityFrom(os.Args), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", body)
}
func bodyFrom(args []string) string {
var s string
if (len(args) < 3) || os.Args[2] == "" {
s = "hello"
} else {
s = strings.Join(args[2:], " ")
}
return s
}
func severityFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "anonymous.info"
} else {
s = os.Args[1]
}
return s
}
</code></pre>
<p><code>receive_logs_topic.go</code> 文件中的代码:</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"os"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_topic", // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when usused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
if len(os.Args) < 2 {
log.Printf("Usage: %s [binding_key]...", os.Args[0])
os.Exit(0)
}
for _, s := range os.Args[1:] {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "logs_topic", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"logs_topic", // exchange
false,
nil)
failOnError(err, "Failed to bind a queue")
}
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto ack
false, // exclusive
false, // no local
false, // no wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
forever := make(chan bool)
go func() {
for d := range msgs {
log.Printf(" [x] %s", d.Body)
}
}()
log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
</code></pre>
<p>接收所有日志:</p>
<pre><code class="language-sh">go run receive_logs_topic.go "#"
</code></pre>
<p>从 kern 设备接收所有日志:</p>
<pre><code class="language-sh">go run receive_logs_topic.go "kern.*"
</code></pre>
<p>或者只想要接收严重级别(critical)的日志:</p>
<pre><code class="language-sh">go run receive_logs_topic.go "*.critical"
</code></pre>
<p>也可以创建多个绑定:</p>
<pre><code class="language-sh">go run receive_logs_topic.go "kern.*" "*.critical"
</code></pre>
<p>发送一条如有规则为 kern.critical 的消息:</p>
<pre><code class="language-sh">go run emit_log_topic.go "kern.critical" "A critical kernel error"
</code></pre>
<p>请尽情地探究这些程序的玩法。既然代码中并没有对路由规则或绑定规则做任何假设,你可以尝试使用超过两个以上的路由规则参数。</p>
<p>完整的代码文件参见 <a href="https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/emit_log_topic.go">emit_log_topic.go</a> 和 <a href="https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/receive_logs_topic.go">receive_logs_topic.go</a></p>
<p>下一步,在 <a href="/post/rabbitmq-tutorial-go-6/">教程六</a> 中我们将探究发送作为远程调用过程的循环消息。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[RabbitMQ 入门教程(四)- 路由绑定]]></title>
<id>https://haotrr.github.io/post/rabbitmq-tutorial-go-4/</id>
<link href="https://haotrr.github.io/post/rabbitmq-tutorial-go-4/">
</link>
<updated>2018-09-29T08:05:24.000Z</updated>
<summary type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第四篇,介绍如何对 RabbitMQ 进行路由绑定。</p>
]]></summary>
<content type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第四篇,介绍如何对 RabbitMQ 进行路由绑定。</p>
<!-- more -->
<p><a href="http://www.rabbitmq.com/tutorial-four-go.html">教程四 原文地址: "Routing"</a></p>
<p>在 <a href="/post/rabbitmq-tutorial-go-3/">教程三</a> 中我们构建了一个简单的日志系统,我们可以将日志信息广播给很多接受者。</p>
<p>本教程中,我们将向这个日志系统增加一些新的功能,使接受者能够只订阅部分的日志信息。比如,我们可以只将错误日志信息保存到文件(磁盘空间),同时将所有的日志信息打印到终端。</p>
<h2 id="绑定">绑定</h2>
<p>上一教程中,我们已经创建了绑定,让我们先来回顾相关代码:</p>
<pre><code class="language-go">err = ch.QueueBind(
q.Name, // queue name
"", // routing key
"logs", // exchange
false,
nil)
</code></pre>
<p>一个绑定是一个交换机和一个队列的相互关联,简单说就是:一个队列对某个交换机上的信息感兴趣。</p>
<p>绑定有一个名为 <code>routing_key</code> 的参数,为了避免与 <code>Channel.Publish</code> 中的参数相混淆,我们将其成为绑定规则(<code>binding key</code>),下面的代码演示了怎样使用绑定规则创建一个绑定。</p>
<pre><code class="language-go">err = ch.QueueBind(
q.Name, // queue name
"black", // routing key
"logs", // exchange
false,
nil)
</code></pre>
<p>绑定规则的意义取决于交换机类型。在之前的 fanout 交换机中,我们直接忽略了这个参数值。</p>
<h2 id="direct-交换机">Direct 交换机</h2>
<p>在上一篇教程中,我们的日志系统将所有消息广播给所有消费者,现在,我们通过扩展上例来允许我们根据日志的严重程度来筛选日志信息。比如,我们可能想要将日志使写入磁盘的程序只接收严格错误日志,从而不会浪费空间来储存警告或通知级别的日志信息。</p>
<p>我们之前使用的是 fanout 交换机,但其灵活性不高 -- 它只会做无脑的广播操作。</p>
<p>我们将使用 direct 交换机来替代。Direct 交换机背后的路由算法很简单 -- 消息将会进入与其 <code>routing key</code> 匹配到的 <code>binding key</code> 的队列。</p>
<p>为了演示上述算法,请看下面的图示:</p>
<figure data-type="image" tabindex="1"><img src="http://img-haotrr.test.upcdn.net/blog/4-1-direct-exchange.png" alt="4-1-direct-exchange" loading="lazy"></figure>
<p>图示中,我们可以看到有两个队列绑定到了 direct 交换机 X 上,第一个队列使用路由规则 orange 绑定;第二个队列则有连个绑定,一个绑定规则为 black,另一个为 green。</p>
<p>图示中,发布到交换机的消息中,带有路由规则 orange 被路由到了队列 Q1,带有路由规则 black 或 green 的则被路由到队列 Q2,其它的消息则会被丢弃。</p>
<h2 id="多重绑定">多重绑定</h2>
<figure data-type="image" tabindex="2"><img src="http://img-haotrr.test.upcdn.net/blog/4-2-direct-exchange-multiple.png" alt="4-2-direct-exchange-multiple" loading="lazy"></figure>
<p>使用相同的路由规则绑定多个队列完全是合法的,上面的图示中,我们使用路由规则 black 往 X 上增加了一个与 Q1 的绑定,这样,direct 交换机就像 fanout 交换机一样回报消息广播给所有匹配的队列:一个带有路由规则 black 的消息会被同时投递到 Q1 和 Q2 上。</p>
<h2 id="发送日志">发送日志</h2>
<p>我们使用上述的模型来重构日志系统,使用 direct 交换机来替换原先的 fanout 交换机来发送消息。我们始于哦能够日志严重级别来作为路由规则,这样我们的接收程序就可以选择它想要接收的日志。我们先来关注日志的发送。</p>
<p>像之前一样,我们首先需要创建一个交换机:</p>
<pre><code class="language-go">err = ch.ExchangeDeclare(
"logs_direct", // name
"direct", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
</code></pre>
<p>然后我们就可以发送消息了:</p>
<pre><code class="language-go">err = ch.ExchangeDeclare(
"logs_direct", // name
"direct", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
body := bodyFrom(os.Args)
err = ch.Publish(
"logs_direct", // exchange
severityFrom(os.Args), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
</code></pre>
<p>为了简化操作我们假设日志严重级别分为:info、warning 和 error。</p>
<h2 id="订阅">订阅</h2>
<p>接收消息的操作与上一教程中相似,除了一点 -- 我们要为每一个我们在意的日志严重级别创建一个绑定。</p>
<pre><code class="language-go">q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when usused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
if len(os.Args) < 2 {
log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
os.Exit(0)
}
for _, s := range os.Args[1:] {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "logs_direct", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"logs_direct", // exchange
false,
nil)
failOnError(err, "Failed to bind a queue")
}
</code></pre>
<h2 id="整体来看">整体来看</h2>
<figure data-type="image" tabindex="3"><img src="http://img-haotrr.test.upcdn.net/blog/4-3-overall.png" alt="4-3-overall" loading="lazy"></figure>
<p><code>emit_log_direct.go</code> 文件内容:</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_direct", // name
"direct", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
body := bodyFrom(os.Args)
err = ch.Publish(
"logs_direct", // exchange
severityFrom(os.Args), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", body)
}
func bodyFrom(args []string) string {
var s string
if (len(args) < 3) || os.Args[2] == "" {
s = "hello"
} else {
s = strings.Join(args[2:], " ")
}
return s
}
func severityFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "info"
} else {
s = os.Args[1]
}
return s
}
</code></pre>
<p><code>receive_log_direct.go</code> 文件内容:</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"os"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_direct", // name
"direct", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when usused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
if len(os.Args) < 2 {
log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
os.Exit(0)
}
for _, s := range os.Args[1:] {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "logs_direct", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"logs_direct", // exchange
false,
nil)
failOnError(err, "Failed to bind a queue")
}
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto ack
false, // exclusive
false, // no local
false, // no wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
forever := make(chan bool)
go func() {
for d := range msgs {
log.Printf(" [x] %s", d.Body)
}
}()
log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
</code></pre>
<p>如果只想要将 warning 和 error(而不是 info)级别的日志消息保存至文件中,只需要打开终端并键入:</p>
<pre><code class="language-sh">go run receive_logs_direct.go warning error > logs_from_rabbit.log
</code></pre>
<p>如果想要在屏幕中显示所有的日志消息,打开终端并输入:</p>
<pre><code class="language-sh">go run receive_logs_direct.go info warning error
# => [*] Waiting for logs. To exit press CTRL+C
</code></pre>
<p>如果只想要发送 error 级别的日志,只需要输入:</p>
<pre><code class="language-sh">go run emit_log_direct.go error "Run. Run. Or it will explode."
# => [x] Sent 'error':'Run. Run. Or it will explode.'
</code></pre>
<p>完整的源代码参见:<a href="https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/emit_log_direct.go">emit_log_direct.go</a> 和 <a href="https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/receive_log_direct.go">receive_log_direct.go</a></p>
<p>请移步 <a href="/post/rabbitmq-tutorial-go-5/">教程五</a> 探究如何基于模式来监听消息。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[RabbitMQ 入门教程(三)- 发布订阅]]></title>
<id>https://haotrr.github.io/post/rabbitmq-tutorial-go-3/</id>
<link href="https://haotrr.github.io/post/rabbitmq-tutorial-go-3/">
</link>
<updated>2018-09-29T08:05:01.000Z</updated>
<summary type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第三篇,介绍 RabbitMQ 的发布订阅模式。</p>
]]></summary>
<content type="html"><![CDATA[<p>本系列是 RabbitMQ 官方教程 Go 版本的中译版本,本文为第三篇,介绍 RabbitMQ 的发布订阅模式。</p>
<!-- more -->
<p><a href="http://www.rabbitmq.com/tutorial-three-go.html">教程三 原文地址: "Publish/Subscribe"</a></p>
<p>在<a href="/post/rabbitmq-tutorial-go-2/">上一教程</a>中我们创建了工作队列,在工作队列中,每个任务假设被分发给唯一的工作者,本教程将处理完全不同的情况 -- 将一个消息同时分发给不同的消费者,这种模式被成为“发布/订阅”模式。</p>
<p>为了演示“发布/订阅”模式,我们将创建一个简单的日志系统,包含两个程序 -- 一个发送日志消息,一个用于接收日志消息并将之打印。</p>
<p>在我们的日志系统中,每一份接收程序的拷贝都会待应接收到的消息,这样我们就能够在运行一个用于接收并将日志写入磁盘的程序的同时,我们还可以运行一个用于接收并将日志打印到屏幕的程序。</p>
<p>本质上,发布日志消息就是将其广播给所有接受者。</p>
<h2 id="交换机">交换机</h2>
<p>在系列教程的前面部分,我们使用队列来发送和接收消息,现在,我们将介绍 RabbitMQ 中完整的消息模型。</p>
<p>先让我们快速地复习一下之前学习过的内容:</p>
<ul>
<li>一个生产者是一个用于发送消息的应用程序。</li>
<li>一个队列是一个用于存储消息的缓存。</li>
<li>一个消费者是一个用于接收消息的应用程序。</li>
</ul>
<p>RabbitMQ 中最核心的思想是:一个生成者永远不会向一个队列直接发送消息。实际上,一个消费者根本不需要知道一个消息是否将会投递到一个队列中。</p>
<p>相反,一个消费者只会将消息发送到交换机(exchange)上。交换机非常简单,一方面它从生成者那里接收消息,另一方面它将消息推送给队列。交换机必须明确地知道它将接收怎样的消息,消息将会被附加到特定的队列?消息将会被附加到很多队列?或者消息会被丢弃?这些规则将通过交换机类型(exchange type)定义。</p>
<figure data-type="image" tabindex="1"><img src="http://img-haotrr.test.upcdn.net/blog/3-1-exchanges.png" alt="3-1-exchanges" loading="lazy"></figure>
<p>RabbitMQ 提供了四种交换机:direct,topic,headers 和 fanout。我们主要关注最后一种 -- fanout。下面,让我们创建一个 fanout 类型的交换机,并命名为 <code>logs</code>:</p>
<pre><code class="language-go">err = ch.ExchangeDeclare(
"logs", // name
"fanout", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
</code></pre>
<p>fanout 类型的交换机非常简单,从名字就可推断出来,它将所有接收到的消息广播到它所知的所有队列上,这恰好就是我们日志系统所需要的。</p>
<blockquote>
<p>Note:</p>
<p><strong>列出所有交换机</strong></p>
<p>列出服务器上的交换机可以使用命令 <code>rabbitmqctl</code>:</p>
<pre><code class="language-sh">sudo rabbitmqctl list_exchangs
</code></pre>
</blockquote>
<p>现在,我们可以将消息发布到我们命名的交换机上:</p>
<pre><code class="language-go">err = ch.ExchangeDeclare(
"logs", // name
"fanout", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
body := bodyFrom(os.Args)
err = ch.Publish(
"logs", // exchange
"", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
</code></pre>
<h2 id="临时队列">临时队列</h2>
<p>你应该还记得我们之前使用的特定名字的队列(还记得 <code>hello</code> 和 <code>task_queue</code> 吗?),能够对队列进行命名相当重要 -- 我们需要将不同的工作者指向相同的队列。当在不同的生产者和消费者中共享同一个队列时给一个队列命名非常重要。</p>
<p>但对于我们的日志系统却并非如此,我们想要监控所有的日志消息,而不只是其中的部分,而且我们只对当前消息而不是旧的消息感兴趣,为了解决这个问题,我们需要做两件事情。</p>
<p>首先,每次我们连接到 RabbitMQ,我们都需要一个全新的、空的队列,我们可以创建一个随机命名的队列来实现,或者使用更好的方式 -- 让服务器来随机地为我们分配一个队列名字。</p>
<p>第二,一旦我们断开连接,队列自动删除。</p>
<p>在 <a href="http://godoc.org/github.com/streadway/amqp">amqp</a> 客户端中,当我们将队列名字设为一个空字符串时,我们就创建了一个随机命名的非持久化队列。</p>
<pre><code class="language-go">q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
</code></pre>
<p><code>QueueDeclare</code> 方法返回时,RabbitMQ 生成了一个随机命名的队列实例,随机的名字与 <code>amq.gen-JzTY20BRgKO-HjmUJj0wLg</code> 类似。</p>
<p>当连接断开时,队列会被删除,因为它被声明为独有的(exclusive)。</p>
<p>可以访问 <a href="https://www.rabbitmq.com/queues.html">guide on queues</a> 来了解更多关于 <code>exclusive</code> 和其它的队列属性。</p>
<h2 id="绑定">绑定</h2>
<figure data-type="image" tabindex="2"><img src="http://img-haotrr.test.upcdn.net/blog/3-2-bindings.png" alt="3-2-bindings" loading="lazy"></figure>
<p>此前,我们已经创建了一个 fanout 类型的交换机和队列了,现在我们需要告诉交换机将消息发送给队列。交换机和队列的关系叫做绑定(binding)。</p>
<pre><code class="language-go">err = ch.QueueBind(
q.Name, // queue name
"", // routing key
"logs", // exchange
false,
nil
)
</code></pre>
<p>现在我们的 <code>log</code> 交换机将会把消息附加到我们声明的队列中。</p>
<h2 id="整体运行">整体运行</h2>
<figure data-type="image" tabindex="3"><img src="http://img-haotrr.test.upcdn.net/blog/3-3-overall.png" alt="3-3-overall" loading="lazy"></figure>
<p>用于发送日志的生产者程序与教程二中的程序并没有太大区别,最主要的改变就是我们将消费发布到了我们自己命名的 <code>logs</code> 交换机而不是未命名的交换机。我们本需要在发送时设置一个 <code>routingKey</code>,但 <code>fanout</code> 类型的交换机会忽略它。下面时完整的 <code>emit_log.go</code> 文件内容:</p>
<pre><code class="language-go">package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
panic(fmt.Sprintf("%s: %s", msg, err))
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(