Ubuntu14.04+RabbitMQ3.6.3+Golang的最佳实践

来源:互联网 发布:不锈钢橱柜品牌 知乎 编辑:程序博客网 时间:2024/06/10 22:10


博文作者:迦壹 
博客地址:Ubuntu14.04+RabbitMQ3.6.3+Golang的最佳实践 
转载声明:可以转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明,谢谢合作!

1、RabbitMQ介绍

1.1、什么是RabbitMQ?

  RabbitMQ 是由 LShift 提供的一个 Advanced Message Queuing Protocol (AMQP) 的开源实现,由以高性能、健壮以及可伸缩性出名的 Erlang 写成,因此也是继承了这些优点。

1.2、什么是AMQP?

  AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。它从生产者接收消息并递送给消费者,在这个过程中,根据规则进行路由,缓存与持久化。

  AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

  而在AMQP中主要有两个组件:Exchange 和 Queue (在 AMQP 1.0 里还会有变动),如下图所示,绿色的 X 就是 Exchange ,红色的是 Queue ,这两者都在 Server 端,又称作 Broker ,这部分是 RabbitMQ 实现的,而蓝色的则是客户端,通常有 Producer 和 Consumer 两种类型:

  idoall.org

1.3、RabbitMQ的基础概念

  • Broker:简单来说就是消息队列服务器实体
  • Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列
  • Queue:消息队列载体,每个消息都会被投入到一个或多个队列
  • Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来
  • Routing Key:路由关键字,exchange根据这个关键字进行消息投递
  • vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离
  • producer:消息生产者,就是投递消息的程序
  • consumer:消息消费者,就是接受消息的程序
  • channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务

1.4、RabbitMQ的特性

  • 可靠性:包括消息持久化,消费者和生产者的消息确认
  • 灵活路由:遵循AMQP协议,支持多种Exchange类型实现不同路由策略
  • 分布式:集群的支持,包括本地网络与远程网络
  • 高可用性:支持主从备份与镜像队列
  • 多语言支持:支持多语言的客户端
  • WEB界面管理:可以管理用户权限,exhange,queue,binding,与实时监控
  • 访问控制:基于vhosts实现访问控制
  • 调试追踪:支持tracing,方便调试

2、RabbitMQ的官网在哪里?

  http://www.rabbitmq.com/

3、RabbitMQ在哪里下载?

  http://www.rabbitmq.com/download.html

4、如何安装RabbitMQ

4.1、通过安装RabbitMQ的源来安装

  在Ubuntu上安装RabbitMQ非常简单

  1. lion@ubuntu1404:~$ sudo echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d/rabbitmq.list
  2. lion@ubuntu1404:~$ wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -
  3. lion@ubuntu1404:~$ sudo apt-get update
  4. lion@ubuntu1404:~$ sudo apt-get install rabbitmq-server

  其他系统安装方法:http://www.rabbitmq.com/download.html

4.2、通过源码安装

  本文中的实例,主要通过源码安装来演示。

4.2.1、安装Erlang

  相关安装文档:http://erlang.org/erldoc

  1. lion@node1:~$ sudo apt-get install -y erlang-nox erlang-dev erlang-src

4.2.2、Rabbitmq 3.6.3安装

  相关安装文档:http://www.rabbitmq.com/install-generic-unix.html。

  我们先下载源码并解压

  1. lion@node1:~$ mkdir -p _app
  2. lion@node1:~/_app$ wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.3/rabbitmq-server-generic-unix-3.6.3.tar.xz
  3. lion@node1:~/_app$ xz -d rabbitmq-server-generic-unix-3.6.3.tar.xz
  4. lion@node1:~/_app$ tar -xvf rabbitmq-server-generic-unix-3.6.3.tar
  5. lion@node1:~/_app$ cd rabbitmq_server-3.6.3

  设置环境变量$RABBITMQ_HOME

  1. lion@node1:~$ vi .bashrc

  在.bashrc中添加以下内容

  1. export RABBITMQ_HOME="/home/lion/_app/rabbitmq_server-3.6.3"
  2. export PATH="$RABBITMQ_HOME/sbin:$PATH"

  让环境变量生效

  1. lion@node1:~$ source .bashrc

  启动Rabbitmq

  1. lion@node1:~$ rabbitmq-server

  安装以后可以通过下面的命令,停止、启动:

  1. lion@node1:~$ rabbitmqctl stop
  2. lion@node1:~$ rabbitmqctl start

4.3、开启web管理插件

  创建一个用户lion,并设置密码123456:

  1. lion@node1:~$ rabbitmqctl add_user lion 123456

  可以通过下面的命令,查看现有的用户更表

  1. lion@node1:~$ rabbitmqctl list_users
  2. Listing users ...
  3. guest [administrator]
  4. lion []

  这个时候lion用户是不能访问web管理插件的,需要配置用户角色,用户角色可分为五类,超级管理员, 监控者, 策略制定者, 普通管理者以及其他。

  • 超级管理员(administrator)

      可登陆管理控制台(启用management plugin的情况下),可查看所有的信息,并且可以对用户,策略(policy)进行操作。

  • 监控者(monitoring)

      可登陆管理控制台(启用management plugin的情况下),同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)

  • 策略制定者(policymaker)

      可登陆管理控制台(启用management plugin的情况下), 同时可以对policy进行管理。但无法查看节点的相关信息。

  • 普通管理者(management)

      仅可登陆管理控制台(启用management plugin的情况下),无法看到节点信息,也无法对策略进行管理。

  • 其他

      无法登陆管理控制台,通常就是普通的生产者和消费者。

      通过下面的命令,可以将lion添加到administrator用户组:

  1. lion@node1:~$ rabbitmqctl set_user_tags lion administrator

  然后可以用下面的命令来启用/信上管理插件:

  1. lion@node1:~$ rabbitmq-plugins enable rabbitmq_management (启用插件)
  2. lion@node1:~$ rabbitmq-plugins disable rabbitmq_management (禁用插件)

  通过浏览访问 http://127.0.0.1:15672/ 
  输入用户名lion,密码123456就可以看到后台了。

rabbitmqctl的更多命令参考:http://www.rabbitmq.com/man/rabbitmqctl.1.man.html

4.4、RabbitMQ 的配置文件介绍

  RabbitMQ的配置文件目录默认是$RABBITMQ_HOME/etc/rabbitmq/rabbitmq-env.conf,如果文件不存在,可以自己创建。

  配置文件全部说明地址:http://www.rabbitmq.com/configure.html#configuration-file

  1. %% -*- mode: erlang -*-
  2. %% ----------------------------------------------------------------------------
  3. %% RabbitMQ Sample Configuration File.
  4. %%
  5. %% See http://www.rabbitmq.com/configure.html for details.
  6. %% ----------------------------------------------------------------------------
  7. [
  8. {rabbit,
  9. [%%
  10. %% Network Connectivity
  11. %% ====================
  12. %%
  13. %% By default, RabbitMQ will listen on all interfaces, using
  14. %% the standard (reserved) AMQP port.
  15. %% 默认的监听端口
  16. %% {tcp_listeners, [5672]},
  17. %% To listen on a specific interface, provide a tuple of {IpAddress, Port}.
  18. %% For example, to listen only on localhost for both IPv4 and IPv6:
  19. %% 也可以使用下面的格式进行指定IP和端口的监听
  20. %% {tcp_listeners, [{"127.0.0.1", 5672},
  21. %% {"::1", 5672}]},
  22. %% SSL listeners are configured in the same fashion as TCP listeners,
  23. %% including the option to control the choice of interface.
  24. %% SSL连接端口配置
  25. %% {ssl_listeners, [5671]},
  26. %% Number of Erlang processes that will accept connections for the TCP
  27. %% and SSL listeners.
  28. %% TCP连接的进程数
  29. %% {num_tcp_acceptors, 10},
  30. %% {num_ssl_acceptors, 1},
  31. %% Maximum time for AMQP 0-8/0-9/0-9-1 handshake (after socket connection
  32. %% and SSL handshake), in milliseconds.
  33. %% 超时时间,单位毫秒
  34. %% {handshake_timeout, 10000},
  35. %% Log levels (currently just used for connection logging).
  36. %% One of 'debug', 'info', 'warning', 'error' or 'none', in decreasing
  37. %% order of verbosity. Defaults to 'info'.
  38. %% 日志的级别,默认是info
  39. %% {log_levels, [{connection, info}, {channel, info}]},
  40. %% Set to 'true' to perform reverse DNS lookups when accepting a
  41. %% connection. Hostnames will then be shown instead of IP addresses
  42. %% in rabbitmqctl and the management plugin.
  43. %%
  44. %% {reverse_dns_lookups, true},
  45. %%
  46. %% Security / AAA
  47. %% ==============
  48. %% 安全配置
  49. %% The default "guest" user is only permitted to access the server
  50. %% via a loopback interface (e.g. localhost).
  51. %% {loopback_users, [<<"guest">>]},
  52. %%
  53. %% Uncomment the following line if you want to allow access to the
  54. %% guest user from anywhere on the network.
  55. %% {loopback_users, []},
  56. %% Configuring SSL.
  57. %% See http://www.rabbitmq.com/ssl.html for full documentation.
  58. %%
  59. %% {ssl_options, [{cacertfile, "/path/to/testca/cacert.pem"},
  60. %% {certfile, "/path/to/server/cert.pem"},
  61. %% {keyfile, "/path/to/server/key.pem"},
  62. %% {verify, verify_peer},
  63. %% {fail_if_no_peer_cert, false}]},
  64. %% Choose the available SASL mechanism(s) to expose.
  65. %% The two default (built in) mechanisms are 'PLAIN' and
  66. %% 'AMQPLAIN'. Additional mechanisms can be added via
  67. %% plugins.
  68. %%
  69. %% See http://www.rabbitmq.com/authentication.html for more details.
  70. %%
  71. %% {auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
  72. %% Select an authentication database to use. RabbitMQ comes bundled
  73. %% with a built-in auth-database, based on mnesia.
  74. %%
  75. %% {auth_backends, [rabbit_auth_backend_internal]},
  76. %% Configurations supporting the rabbitmq_auth_mechanism_ssl and
  77. %% rabbitmq_auth_backend_ldap plugins.
  78. %%
  79. %% NB: These options require that the relevant plugin is enabled.
  80. %% See http://www.rabbitmq.com/plugins.html for further details.
  81. %% The RabbitMQ-auth-mechanism-ssl plugin makes it possible to
  82. %% authenticate a user based on the client's SSL certificate.
  83. %%
  84. %% To use auth-mechanism-ssl, add to or replace the auth_mechanisms
  85. %% list with the entry 'EXTERNAL'.
  86. %%
  87. %% {auth_mechanisms, ['EXTERNAL']},
  88. %% The rabbitmq_auth_backend_ldap plugin allows the broker to
  89. %% perform authentication and authorisation by deferring to an
  90. %% external LDAP server.
  91. %%
  92. %% For more information about configuring the LDAP backend, see
  93. %% http://www.rabbitmq.com/ldap.html.
  94. %%
  95. %% Enable the LDAP auth backend by adding to or replacing the
  96. %% auth_backends entry:
  97. %%
  98. %% {auth_backends, [rabbit_auth_backend_ldap]},
  99. %% This pertains to both the rabbitmq_auth_mechanism_ssl plugin and
  100. %% STOMP ssl_cert_login configurations. See the rabbitmq_stomp
  101. %% configuration section later in this file and the README in
  102. %% https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further
  103. %% details.
  104. %%
  105. %% To use the SSL cert's CN instead of its DN as the username
  106. %%
  107. %% {ssl_cert_login_from, common_name},
  108. %% SSL handshake timeout, in milliseconds.
  109. %%
  110. %% {ssl_handshake_timeout, 5000},
  111. %% Password hashing implementation. Will only affect newly
  112. %% created users. To recalculate hash for an existing user
  113. %% it's necessary to update her password.
  114. %%
  115. %% {password_hashing_module, rabbit_password_hashing_sha256},
  116. %%
  117. %% Default User / VHost
  118. %% ====================
  119. %% 用户访问设置
  120. %% On first start RabbitMQ will create a vhost and a user. These
  121. %% config items control what gets created. See
  122. %% http://www.rabbitmq.com/access-control.html for further
  123. %% information about vhosts and access control.
  124. %%
  125. %% {default_vhost, <<"/">>},
  126. %% {default_user, <<"guest">>},
  127. %% {default_pass, <<"guest">>},
  128. %% {default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
  129. %% Tags for default user
  130. %%
  131. %% For more details about tags, see the documentation for the
  132. %% Management Plugin at http://www.rabbitmq.com/management.html.
  133. %%
  134. %% {default_user_tags, [administrator]},
  135. %%
  136. %% Additional network and protocol related configuration
  137. %% =====================================================
  138. %%
  139. %% Set the default AMQP heartbeat delay (in seconds).
  140. %% 设置默认AMQP心跳延迟(秒)
  141. %% {heartbeat, 600},
  142. %% Set the max permissible size of an AMQP frame (in bytes).
  143. %%
  144. %% {frame_max, 131072},
  145. %% Set the max frame size the server will accept before connection
  146. %% tuning occurs
  147. %%
  148. %% {initial_frame_max, 4096},
  149. %% Set the max permissible number of channels per connection.
  150. %% 0 means "no limit".
  151. %%
  152. %% {channel_max, 128},
  153. %% Customising Socket Options.
  154. %%
  155. %% See (http://www.erlang.org/doc/man/inet.html#setopts-2) for
  156. %% further documentation.
  157. %%
  158. %% {tcp_listen_options, [{backlog, 128},
  159. %% {nodelay, true},
  160. %% {exit_on_close, false}]},
  161. %%
  162. %% Resource Limits & Flow Control
  163. %% ==============================
  164. %%
  165. %% See http://www.rabbitmq.com/memory.html for full details.
  166. %% Memory-based Flow Control threshold.
  167. %%
  168. %% {vm_memory_high_watermark, 0.4},
  169. %% Alternatively, we can set a limit (in bytes) of RAM used by the node.
  170. %%
  171. %% {vm_memory_high_watermark, {absolute, 1073741824}},
  172. %%
  173. %% Or you can set absolute value using memory units.
  174. %%
  175. %% {vm_memory_high_watermark, {absolute, "1024M"}},
  176. %%
  177. %% Supported units suffixes:
  178. %%
  179. %% k, kiB: kibibytes (2^10 bytes)
  180. %% M, MiB: mebibytes (2^20)
  181. %% G, GiB: gibibytes (2^30)
  182. %% kB: kilobytes (10^3)
  183. %% MB: megabytes (10^6)
  184. %% GB: gigabytes (10^9)
  185. %% Fraction of the high watermark limit at which queues start to
  186. %% page message out to disc in order to free up memory.
  187. %%
  188. %% Values greater than 0.9 can be dangerous and should be used carefully.
  189. %% 内存最大使用比例
  190. %% {vm_memory_high_watermark_paging_ratio, 0.5},
  191. %% Interval (in milliseconds) at which we perform the check of the memory
  192. %% levels against the watermarks.
  193. %% 检查内存的间隔(毫秒)
  194. %% {memory_monitor_interval, 2500},
  195. %% Set disk free limit (in bytes). Once free disk space reaches this
  196. %% lower bound, a disk alarm will be set - see the documentation
  197. %% listed above for more details.
  198. %%
  199. %% {disk_free_limit, 50000000},
  200. %%
  201. %% Or you can set it using memory units (same as in vm_memory_high_watermark)
  202. %% {disk_free_limit, "50MB"},
  203. %% {disk_free_limit, "50000kB"},
  204. %% {disk_free_limit, "2GB"},
  205. %% Alternatively, we can set a limit relative to total available RAM.
  206. %%
  207. %% Values lower than 1.0 can be dangerous and should be used carefully.
  208. %% {disk_free_limit, {mem_relative, 2.0}},
  209. %%
  210. %% Misc/Advanced Options
  211. %% =====================
  212. %%
  213. %% NB: Change these only if you understand what you are doing!
  214. %%
  215. %% To announce custom properties to clients on connection:
  216. %%
  217. %% {server_properties, []},
  218. %% How to respond to cluster partitions.
  219. %% See http://www.rabbitmq.com/partitions.html for further details.
  220. %%
  221. %% {cluster_partition_handling, ignore},
  222. %% Make clustering happen *automatically* at startup - only applied
  223. %% to nodes that have just been reset or started for the first time.
  224. %% See http://www.rabbitmq.com/clustering.html#auto-config for
  225. %% further details.
  226. %% 设置集群启动的节点
  227. %% {cluster_nodes, {['rabbit@my.host.com'], disc}},
  228. %% Interval (in milliseconds) at which we send keepalive messages
  229. %% to other cluster members. Note that this is not the same thing
  230. %% as net_ticktime; missed keepalive messages will not cause nodes
  231. %% to be considered down.
  232. %% 集群消息同步的时间(毫秒)
  233. %% {cluster_keepalive_interval, 10000},
  234. %% Set (internal) statistics collection granularity.
  235. %%
  236. %% {collect_statistics, none},
  237. %% Statistics collection interval (in milliseconds).
  238. %%
  239. %% {collect_statistics_interval, 5000},
  240. %% Explicitly enable/disable hipe compilation.
  241. %%
  242. %% {hipe_compile, true},
  243. %% Timeout used when waiting for Mnesia tables in a cluster to
  244. %% become available.
  245. %%
  246. %% {mnesia_table_loading_timeout, 30000},
  247. %% Size in bytes below which to embed messages in the queue index. See
  248. %% http://www.rabbitmq.com/persistence-conf.html
  249. %%
  250. %% {queue_index_embed_msgs_below, 4096}
  251. ]},
  252. %% ----------------------------------------------------------------------------
  253. %% Advanced Erlang Networking/Clustering Options.
  254. %%
  255. %% See http://www.rabbitmq.com/clustering.html for details
  256. %% ----------------------------------------------------------------------------
  257. {kernel,
  258. [%% Sets the net_kernel tick time.
  259. %% Please see http://erlang.org/doc/man/kernel_app.html and
  260. %% http://www.rabbitmq.com/nettick.html for further details.
  261. %%
  262. %% {net_ticktime, 60}
  263. ]},
  264. %% ----------------------------------------------------------------------------
  265. %% RabbitMQ Management Plugin
  266. %%
  267. %% See http://www.rabbitmq.com/management.html for details
  268. %% ----------------------------------------------------------------------------
  269. {rabbitmq_management,
  270. [%% Pre-Load schema definitions from the following JSON file. See
  271. %% http://www.rabbitmq.com/management.html#load-definitions
  272. %%
  273. %% {load_definitions, "/path/to/schema.json"},
  274. %% Log all requests to the management HTTP API to a file.
  275. %% 所有请求的HTTP API文件日志的路径。
  276. %% {http_log_dir, "/path/to/access.log"},
  277. %% Change the port on which the HTTP listener listens,
  278. %% specifying an interface for the web server to bind to.
  279. %% Also set the listener to use SSL and provide SSL options.
  280. %% Web管理的地址和端口
  281. %% {listener, [{port, 12345},
  282. %% {ip, "127.0.0.1"},
  283. %% {ssl, true},
  284. %% {ssl_opts, [{cacertfile, "/path/to/cacert.pem"},
  285. %% {certfile, "/path/to/cert.pem"},
  286. %% {keyfile, "/path/to/key.pem"}]}]},
  287. %% One of 'basic', 'detailed' or 'none'. See
  288. %% http://www.rabbitmq.com/management.html#fine-stats for more details.
  289. %% {rates_mode, basic},
  290. %% Configure how long aggregated data (such as message rates and queue
  291. %% lengths) is retained. Please read the plugin's documentation in
  292. %% http://www.rabbitmq.com/management.html#configuration for more
  293. %% details.
  294. %%
  295. %% {sample_retention_policies,
  296. %% [{global, [{60, 5}, {3600, 60}, {86400, 1200}]},
  297. %% {basic, [{60, 5}, {3600, 60}]},
  298. %% {detailed, [{10, 5}]}]}
  299. ]},
  300. %% ----------------------------------------------------------------------------
  301. %% RabbitMQ Shovel Plugin
  302. %%
  303. %% See http://www.rabbitmq.com/shovel.html for details
  304. %% ----------------------------------------------------------------------------
  305. {rabbitmq_shovel,
  306. [{shovels,
  307. [%% A named shovel worker.
  308. %% {my_first_shovel,
  309. %% [
  310. %% List the source broker(s) from which to consume.
  311. %%
  312. %% {sources,
  313. %% [%% URI(s) and pre-declarations for all source broker(s).
  314. %% {brokers, ["amqp://user:password@host.domain/my_vhost"]},
  315. %% {declarations, []}
  316. %% ]},
  317. %% List the destination broker(s) to publish to.
  318. %% {destinations,
  319. %% [%% A singular version of the 'brokers' element.
  320. %% {broker, "amqp://"},
  321. %% {declarations, []}
  322. %% ]},
  323. %% Name of the queue to shovel messages from.
  324. %%
  325. %% {queue, <<"your-queue-name-goes-here">>},
  326. %% Optional prefetch count.
  327. %%
  328. %% {prefetch_count, 10},
  329. %% when to acknowledge messages:
  330. %% - no_ack: never (auto)
  331. %% - on_publish: after each message is republished
  332. %% - on_confirm: when the destination broker confirms receipt
  333. %%
  334. %% {ack_mode, on_confirm},
  335. %% Overwrite fields of the outbound basic.publish.
  336. %%
  337. %% {publish_fields, [{exchange, <<"my_exchange">>},
  338. %% {routing_key, <<"from_shovel">>}]},
  339. %% Static list of basic.properties to set on re-publication.
  340. %%
  341. %% {publish_properties, [{delivery_mode, 2}]},
  342. %% The number of seconds to wait before attempting to
  343. %% reconnect in the event of a connection failure.
  344. %%
  345. %% {reconnect_delay, 2.5}
  346. %% ]} %% End of my_first_shovel
  347. ]}
  348. %% Rather than specifying some values per-shovel, you can specify
  349. %% them for all shovels here.
  350. %%
  351. %% {defaults, [{prefetch_count, 0},
  352. %% {ack_mode, on_confirm},
  353. %% {publish_fields, []},
  354. %% {publish_properties, [{delivery_mode, 2}]},
  355. %% {reconnect_delay, 2.5}]}
  356. ]},
  357. %% ----------------------------------------------------------------------------
  358. %% RabbitMQ Stomp Adapter
  359. %%
  360. %% See http://www.rabbitmq.com/stomp.html for details
  361. %% ----------------------------------------------------------------------------
  362. {rabbitmq_stomp,
  363. [%% Network Configuration - the format is generally the same as for the broker
  364. %% Listen only on localhost (ipv4 & ipv6) on a specific port.
  365. %% {tcp_listeners, [{"127.0.0.1", 61613},
  366. %% {"::1", 61613}]},
  367. %% Listen for SSL connections on a specific port.
  368. %% {ssl_listeners, [61614]},
  369. %% Number of Erlang processes that will accept connections for the TCP
  370. %% and SSL listeners.
  371. %%
  372. %% {num_tcp_acceptors, 10},
  373. %% {num_ssl_acceptors, 1},
  374. %% Additional SSL options
  375. %% Extract a name from the client's certificate when using SSL.
  376. %%
  377. %% {ssl_cert_login, true},
  378. %% Set a default user name and password. This is used as the default login
  379. %% whenever a CONNECT frame omits the login and passcode headers.
  380. %%
  381. %% Please note that setting this will allow clients to connect without
  382. %% authenticating!
  383. %%
  384. %% {default_user, [{login, "guest"},
  385. %% {passcode, "guest"}]},
  386. %% If a default user is configured, or you have configured use SSL client
  387. %% certificate based authentication, you can choose to allow clients to
  388. %% omit the CONNECT frame entirely. If set to true, the client is
  389. %% automatically connected as the default user or user supplied in the
  390. %% SSL certificate whenever the first frame sent on a session is not a
  391. %% CONNECT frame.
  392. %%
  393. %% {implicit_connect, true}
  394. ]},
  395. %% ----------------------------------------------------------------------------
  396. %% RabbitMQ MQTT Adapter
  397. %%
  398. %% See https://github.com/rabbitmq/rabbitmq-mqtt/blob/stable/README.md
  399. %% for details
  400. %% ----------------------------------------------------------------------------
  401. {rabbitmq_mqtt,
  402. [%% Set the default user name and password. Will be used as the default login
  403. %% if a connecting client provides no other login details.
  404. %%
  405. %% Please note that setting this will allow clients to connect without
  406. %% authenticating!
  407. %%
  408. %% {default_user, <<"guest">>},
  409. %% {default_pass, <<"guest">>},
  410. %% Enable anonymous access. If this is set to false, clients MUST provide
  411. %% login information in order to connect. See the default_user/default_pass
  412. %% configuration elements for managing logins without authentication.
  413. %%
  414. %% {allow_anonymous, true},
  415. %% If you have multiple chosts, specify the one to which the
  416. %% adapter connects.
  417. %%
  418. %% {vhost, <<"/">>},
  419. %% Specify the exchange to which messages from MQTT clients are published.
  420. %%
  421. %% {exchange, <<"amq.topic">>},
  422. %% Specify TTL (time to live) to control the lifetime of non-clean sessions.
  423. %%
  424. %% {subscription_ttl, 1800000},
  425. %% Set the prefetch count (governing the maximum number of unacknowledged
  426. %% messages that will be delivered).
  427. %%
  428. %% {prefetch, 10},
  429. %% TCP/SSL Configuration (as per the broker configuration).
  430. %%
  431. %% {tcp_listeners, [1883]},
  432. %% {ssl_listeners, []},
  433. %% Number of Erlang processes that will accept connections for the TCP
  434. %% and SSL listeners.
  435. %%
  436. %% {num_tcp_acceptors, 10},
  437. %% {num_ssl_acceptors, 1},
  438. %% TCP/Socket options (as per the broker configuration).
  439. %%
  440. %% {tcp_listen_options, [{backlog, 128},
  441. %% {nodelay, true}]}
  442. ]},
  443. %% ----------------------------------------------------------------------------
  444. %% RabbitMQ AMQP 1.0 Support
  445. %%
  446. %% See https://github.com/rabbitmq/rabbitmq-amqp1.0/blob/stable/README.md
  447. %% for details
  448. %% ----------------------------------------------------------------------------
  449. {rabbitmq_amqp1_0,
  450. [%% Connections that are not authenticated with SASL will connect as this
  451. %% account. See the README for more information.
  452. %%
  453. %% Please note that setting this will allow clients to connect without
  454. %% authenticating!
  455. %%
  456. %% {default_user, "guest"},
  457. %% Enable protocol strict mode. See the README for more information.
  458. %%
  459. %% {protocol_strict_mode, false}
  460. ]},
  461. %% ----------------------------------------------------------------------------
  462. %% RabbitMQ LDAP Plugin
  463. %%
  464. %% See http://www.rabbitmq.com/ldap.html for details.
  465. %%
  466. %% ----------------------------------------------------------------------------
  467. {rabbitmq_auth_backend_ldap,
  468. [%%
  469. %% Connecting to the LDAP server(s)
  470. %% ================================
  471. %%
  472. %% Specify servers to bind to. You *must* set this in order for the plugin
  473. %% to work properly.
  474. %%
  475. %% {servers, ["your-server-name-goes-here"]},
  476. %% Connect to the LDAP server using SSL
  477. %%
  478. %% {use_ssl, false},
  479. %% Specify the LDAP port to connect to
  480. %%
  481. %% {port, 389},
  482. %% LDAP connection timeout, in milliseconds or 'infinity'
  483. %%
  484. %% {timeout, infinity},
  485. %% Enable logging of LDAP queries.
  486. %% One of
  487. %% - false (no logging is performed)
  488. %% - true (verbose logging of the logic used by the plugin)
  489. %% - network (as true, but additionally logs LDAP network traffic)
  490. %%
  491. %% Defaults to false.
  492. %%
  493. %% {log, false},
  494. %%
  495. %% Authentication
  496. %% ==============
  497. %%
  498. %% Pattern to convert the username given through AMQP to a DN before
  499. %% binding
  500. %%
  501. %% {user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"},
  502. %% Alternatively, you can convert a username to a Distinguished
  503. %% Name via an LDAP lookup after binding. See the documentation for
  504. %% full details.
  505. %% When converting a username to a dn via a lookup, set these to
  506. %% the name of the attribute that represents the user name, and the
  507. %% base DN for the lookup query.
  508. %%
  509. %% {dn_lookup_attribute, "userPrincipalName"},
  510. %% {dn_lookup_base, "DC=gopivotal,DC=com"},
  511. %% Controls how to bind for authorisation queries and also to
  512. %% retrieve the details of users logging in without presenting a
  513. %% password (e.g., SASL EXTERNAL).
  514. %% One of
  515. %% - as_user (to bind as the authenticated user - requires a password)
  516. %% - anon (to bind anonymously)
  517. %% - {UserDN, Password} (to bind with a specified user name and password)
  518. %%
  519. %% Defaults to 'as_user'.
  520. %%
  521. %% {other_bind, as_user},
  522. %%
  523. %% Authorisation
  524. %% =============
  525. %%
  526. %% The LDAP plugin can perform a variety of queries against your
  527. %% LDAP server to determine questions of authorisation. See
  528. %% http://www.rabbitmq.com/ldap.html#authorisation for more
  529. %% information.
  530. %% Set the query to use when determining vhost access
  531. %%
  532. %% {vhost_access_query, {in_group,
  533. %% "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}},
  534. %% Set the query to use when determining resource (e.g., queue) access
  535. %%
  536. %% {resource_access_query, {constant, true}},
  537. %% Set queries to determine which tags a user has
  538. %%
  539. %% {tag_queries, []}
  540. ]}
  541. ].

5、Golang调用RabbitMQ的案例

  下载Golgang运行amqp协议的包,在Rabbitmq官网上有提供现在的golang包来使用amqp协议与Rabbitmq交互 。

  我们先将包下载到本地,然后就可以直接使用了:

  1. lion@node1:~$ go get github.com/streadway/amqp

5.1、使用Golang来发送第一个hello idoall.org

  在第一个教程中,我们写程序从一个命名的队列(test-idoall-queues)中发送和接收消息。

  producer_hello.go(消息生产者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/streadway/amqp"
  6. )
  7. const (
  8. //AMQP URI
  9. uri = "amqp://guest:guest@localhost:5672/"
  10. //Durable AMQP exchange name
  11. exchangeName = ""
  12. //Durable AMQP queue name
  13. queueName = "test-idoall-queues"
  14. //Body of message
  15. bodyMsg string = "hello idoall.org"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. //调用发布消息函数
  26. publish(uri, exchangeName, queueName, bodyMsg)
  27. log.Printf("published %dB OK", len(bodyMsg))
  28. }
  29. //发布者的方法
  30. //
  31. //@amqpURI, amqp的地址
  32. //@exchange, exchange的名称
  33. //@queue, queue的名称
  34. //@body, 主体内容
  35. func publish(amqpURI string, exchange string, queue string, body string){
  36. //建立连接
  37. log.Printf("dialing %q", amqpURI)
  38. connection, err := amqp.Dial(amqpURI)
  39. failOnError(err, "Failed to connect to RabbitMQ")
  40. defer connection.Close()
  41. //创建一个Channel
  42. log.Printf("got Connection, getting Channel")
  43. channel, err := connection.Channel()
  44. failOnError(err, "Failed to open a channel")
  45. defer channel.Close()
  46. log.Printf("got queue, declaring %q", queue)
  47. //创建一个queue
  48. q, err := channel.QueueDeclare(
  49. queueName, // name
  50. false, // durable
  51. false, // delete when unused
  52. false, // exclusive
  53. false, // no-wait
  54. nil, // arguments
  55. )
  56. failOnError(err, "Failed to declare a queue")
  57. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  58. // Producer只能发送到exchange,它是不能直接发送到queue的。
  59. // 现在我们使用默认的exchange(名字是空字符)。这个默认的exchange允许我们发送给指定的queue。
  60. // routing_key就是指定的queue名字。
  61. err = channel.Publish(
  62. exchange, // exchange
  63. q.Name, // routing key
  64. false, // mandatory
  65. false, // immediate
  66. amqp.Publishing {
  67. Headers: amqp.Table{},
  68. ContentType: "text/plain",
  69. ContentEncoding: "",
  70. Body: []byte(body),
  71. })
  72. failOnError(err, "Failed to publish a message")
  73. }

  consumer_hello(消息消费者).go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/streadway/amqp"
  6. )
  7. const (
  8. //AMQP URI
  9. uri = "amqp://guest:guest@localhost:5672/"
  10. //Durable AMQP exchange nam
  11. exchangeName = ""
  12. //Durable AMQP queue name
  13. queueName = "test-idoall-queues"
  14. )
  15. //如果存在错误,则输出
  16. func failOnError(err error, msg string) {
  17. if err != nil {
  18. log.Fatalf("%s: %s", msg, err)
  19. panic(fmt.Sprintf("%s: %s", msg, err))
  20. }
  21. }
  22. func main(){
  23. //调用消息接收者
  24. consumer(uri, exchangeName, queueName)
  25. }
  26. //接收者方法
  27. //
  28. //@amqpURI, amqp的地址
  29. //@exchange, exchange的名称
  30. //@queue, queue的名称
  31. func consumer(amqpURI string, exchange string, queue string){
  32. //建立连接
  33. log.Printf("dialing %q", amqpURI)
  34. connection, err := amqp.Dial(amqpURI)
  35. failOnError(err, "Failed to connect to RabbitMQ")
  36. defer connection.Close()
  37. //创建一个Channel
  38. log.Printf("got Connection, getting Channel")
  39. channel, err := connection.Channel()
  40. failOnError(err, "Failed to open a channel")
  41. defer channel.Close()
  42. log.Printf("got queue, declaring %q", queue)
  43. //创建一个queue
  44. q, err := channel.QueueDeclare(
  45. queueName, // name
  46. false, // durable
  47. false, // delete when unused
  48. false, // exclusive
  49. false, // no-wait
  50. nil, // arguments
  51. )
  52. failOnError(err, "Failed to declare a queue")
  53. log.Printf("Queue bound to Exchange, starting Consume")
  54. //订阅消息
  55. msgs, err := channel.Consume(
  56. q.Name, // queue
  57. "", // consumer
  58. true, // auto-ack
  59. false, // exclusive
  60. false, // no-local
  61. false, // no-wait
  62. nil, // args
  63. )
  64. failOnError(err, "Failed to register a consumer")
  65. //创建一个channel
  66. forever := make(chan bool)
  67. //调用gorountine
  68. go func() {
  69. for d := range msgs {
  70. log.Printf("Received a message: %s", d.Body)
  71. }
  72. }()
  73. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  74. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  75. <-forever
  76. }

  Console1(运行producer):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_hello.go
  2. 2016/07/23 02:29:51 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 02:29:51 got Connection, getting Channel
  4. 2016/07/23 02:29:51 got queue, declaring "test-idoall-queues"
  5. 2016/07/23 02:29:51 declared queue, publishing 16B body ("hello idoall.org")
  6. 2016/07/23 02:29:51 published 16B OK

  然后运行以下命令,可以看到我们刚才创建的queues在列表中

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl list_queues
  2. Listing queues ...
  3. test-idoall-queues 1

  Console2(运行consumer)打印消息到屏幕,可以看到刚才我们通过producer发送的消息hello idoall.org

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_hello.go
  2. 2016/07/23 03:33:14 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 03:33:14 got Connection, getting Channel
  4. 2016/07/23 03:33:14 got queue, declaring "test-idoall-queues"
  5. 2016/07/23 03:33:14 Queue bound to Exchange, starting Consume
  6. 2016/07/23 03:33:14 [*] Waiting for messages. To exit press CTRL+C
  7. 2016/07/23 03:33:14 Received a message: hello idoall.org

5.2、Rabbitmq的任务分发机制

  在5.1章节中,我们写程序从一个命名的队列中发送和接收消息。在这个章节中,我们将创建一个工作队列,将用于分配在多个工人之间的耗时的任务。

  RabbitMQ的分发机制非常适合扩展,而且它是专门为并发程序设计的。如果任务队伍过多,那么只需要创建更多的Consumer来进行任务处理即可。

  producer_task.go(消息生产者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "strings"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange name
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-task"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. bodyMsg := bodyFrom(os.Args)
  26. //调用发布消息函数
  27. publish(uri, exchangeName, queueName, bodyMsg)
  28. log.Printf("published %dB OK", len(bodyMsg))
  29. }
  30. func bodyFrom(args []string) string {
  31. var s string
  32. if (len(args) < 2) || os.Args[1] == "" {
  33. s = "hello idoall.org"
  34. } else {
  35. s = strings.Join(args[1:], " ")
  36. }
  37. return s
  38. }
  39. //发布者的方法
  40. //
  41. //@amqpURI, amqp的地址
  42. //@exchange, exchange的名称
  43. //@queue, queue的名称
  44. //@body, 主体内容
  45. func publish(amqpURI string, exchange string, queue string, body string){
  46. //建立连接
  47. log.Printf("dialing %q", amqpURI)
  48. connection, err := amqp.Dial(amqpURI)
  49. failOnError(err, "Failed to connect to RabbitMQ")
  50. defer connection.Close()
  51. //创建一个Channel
  52. log.Printf("got Connection, getting Channel")
  53. channel, err := connection.Channel()
  54. failOnError(err, "Failed to open a channel")
  55. defer channel.Close()
  56. log.Printf("got queue, declaring %q", queue)
  57. //创建一个queue
  58. q, err := channel.QueueDeclare(
  59. queueName, // name
  60. false, // durable
  61. false, // delete when unused
  62. false, // exclusive
  63. false, // no-wait
  64. nil, // arguments
  65. )
  66. failOnError(err, "Failed to declare a queue")
  67. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  68. // Producer只能发送到exchange,它是不能直接发送到queue的。
  69. // 现在我们使用默认的exchange(名字是空字符)。这个默认的exchange允许我们发送给指定的queue。
  70. // routing_key就是指定的queue名字。
  71. err = channel.Publish(
  72. exchange, // exchange
  73. q.Name, // routing key
  74. false, // mandatory
  75. false, // immediate
  76. amqp.Publishing {
  77. Headers: amqp.Table{},
  78. ContentType: "text/plain",
  79. ContentEncoding: "",
  80. Body: []byte(body),
  81. })
  82. failOnError(err, "Failed to publish a message")
  83. }

  consumer_task(消息消费者).go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "bytes"
  6. "time"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange nam
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-task"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. //调用消息接收者
  26. consumer(uri, exchangeName, queueName)
  27. }
  28. //接收者方法
  29. //
  30. //@amqpURI, amqp的地址
  31. //@exchange, exchange的名称
  32. //@queue, queue的名称
  33. func consumer(amqpURI string, exchange string, queue string){
  34. //建立连接
  35. log.Printf("dialing %q", amqpURI)
  36. connection, err := amqp.Dial(amqpURI)
  37. failOnError(err, "Failed to connect to RabbitMQ")
  38. defer connection.Close()
  39. //创建一个Channel
  40. log.Printf("got Connection, getting Channel")
  41. channel, err := connection.Channel()
  42. failOnError(err, "Failed to open a channel")
  43. defer channel.Close()
  44. log.Printf("got queue, declaring %q", queue)
  45. //创建一个queue
  46. q, err := channel.QueueDeclare(
  47. queueName, // name
  48. false, // durable
  49. false, // delete when unused
  50. false, // exclusive
  51. false, // no-wait
  52. nil, // arguments
  53. )
  54. failOnError(err, "Failed to declare a queue")
  55. log.Printf("Queue bound to Exchange, starting Consume")
  56. //订阅消息
  57. msgs, err := channel.Consume(
  58. q.Name, // queue
  59. "", // consumer
  60. false, // auto-ack
  61. false, // exclusive
  62. false, // no-local
  63. false, // no-wait
  64. nil, // args
  65. )
  66. failOnError(err, "Failed to register a consumer")
  67. //创建一个channel
  68. forever := make(chan bool)
  69. //调用gorountine
  70. go func() {
  71. for d := range msgs {
  72. log.Printf("Received a message: %s", d.Body)
  73. dot_count := bytes.Count(d.Body, []byte("."))
  74. t := time.Duration(dot_count)
  75. time.Sleep(t * time.Second)
  76. log.Printf("Done")
  77. }
  78. }()
  79. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  80. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  81. <-forever
  82. }

查看结果


  Console1(consumer):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_task.go
  2. 2016/07/23 10:11:40 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 10:11:40 got Connection, getting Channel
  4. 2016/07/23 10:11:40 got queue, declaring "test-idoall-queues-task"
  5. 2016/07/23 10:11:40 Queue bound to Exchange, starting Consume
  6. 2016/07/23 10:11:40 [*] Waiting for messages. To exit press CTRL+C

  Console2(consumer):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_task.go
  2. 2016/07/23 10:11:40 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 10:11:40 got Connection, getting Channel
  4. 2016/07/23 10:11:40 got queue, declaring "test-idoall-queues-task"
  5. 2016/07/23 10:11:40 Queue bound to Exchange, starting Consume
  6. 2016/07/23 10:11:40 [*] Waiting for messages. To exit press CTRL+C

  这个时候我们使用Producer 来 Publish Message:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_task.go First message. && go run producer_task.go Second message.. && go run producer_task.go Third message... && go run producer_task.go Fourth message.... && go run producer_task.go Fifth message.....
  2. 2016/07/23 10:17:13 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 10:17:13 got Connection, getting Channel
  4. 2016/07/23 10:17:13 got queue, declaring "test-idoall-queues-task"
  5. 2016/07/23 10:17:13 declared queue, publishing 14B body ("First message.")
  6. 2016/07/23 10:17:13 published 14B OK
  7. 2016/07/23 10:17:14 dialing "amqp://guest:guest@localhost:5672/"
  8. 2016/07/23 10:17:14 got Connection, getting Channel
  9. 2016/07/23 10:17:14 got queue, declaring "test-idoall-queues-task"
  10. 2016/07/23 10:17:14 declared queue, publishing 16B body ("Second message..")
  11. 2016/07/23 10:17:14 published 16B OK
  12. 2016/07/23 10:17:15 dialing "amqp://guest:guest@localhost:5672/"
  13. 2016/07/23 10:17:15 got Connection, getting Channel
  14. 2016/07/23 10:17:15 got queue, declaring "test-idoall-queues-task"
  15. 2016/07/23 10:17:15 declared queue, publishing 16B body ("Third message...")
  16. 2016/07/23 10:17:15 published 16B OK
  17. 2016/07/23 10:17:16 dialing "amqp://guest:guest@localhost:5672/"
  18. 2016/07/23 10:17:16 got Connection, getting Channel
  19. 2016/07/23 10:17:16 got queue, declaring "test-idoall-queues-task"
  20. 2016/07/23 10:17:16 declared queue, publishing 18B body ("Fourth message....")
  21. 2016/07/23 10:17:16 published 18B OK
  22. 2016/07/23 10:17:16 dialing "amqp://guest:guest@localhost:5672/"
  23. 2016/07/23 10:17:16 got Connection, getting Channel
  24. 2016/07/23 10:17:16 got queue, declaring "test-idoall-queues-task"
  25. 2016/07/23 10:17:16 declared queue, publishing 18B body ("Fifth message.....")
  26. 2016/07/23 10:17:16 published 18B OK

  这时我们再看刚才打开的两个Consumer的结果: 
  Console1(consumer):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_task.go
  2. 2016/07/23 10:11:21 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 10:11:21 got Connection, getting Channel
  4. 2016/07/23 10:11:21 got queue, declaring "test-idoall-queues-task"
  5. 2016/07/23 10:11:21 Queue bound to Exchange, starting Consume
  6. 2016/07/23 10:11:21 [*] Waiting for messages. To exit press CTRL+C
  7. 2016/07/23 10:17:13 Received a message: First message.
  8. 2016/07/23 10:17:14 Done
  9. 2016/07/23 10:17:15 Received a message: Third message...
  10. 2016/07/23 10:17:18 Done
  11. 2016/07/23 10:17:18 Received a message: Fifth message.....
  12. 2016/07/23 10:17:23 Done

  Console2(consumer):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_task.go
  2. 2016/07/23 10:11:40 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 10:11:40 got Connection, getting Channel
  4. 2016/07/23 10:11:40 got queue, declaring "test-idoall-queues-task"
  5. 2016/07/23 10:11:40 Queue bound to Exchange, starting Consume
  6. 2016/07/23 10:11:40 [*] Waiting for messages. To exit press CTRL+C
  7. 2016/07/23 10:17:14 Received a message: Second message..
  8. 2016/07/23 10:17:16 Done
  9. 2016/07/23 10:17:16 Received a message: Fourth message....
  10. 2016/07/23 10:17:20 Done

  默认情况下,RabbitMQ 会顺序的分发每个Message。当每个收到ack后,会将该Message删除,然后将下一个Message分发到下一个Consumer。这种分发方式叫做round-robin,也叫消息轮询

5.3、Message acknowledgment 消息确认

  每个Consumer可能需要一段时间才能处理完收到的数据。如果在这个过程中,Consumer出错了,异常退出了,而数据还没有处理完成,那么非常不幸,这段数据就丢失了。因为我们的代码,一旦RabbitMQ Server发送给Consumer消息后,会立即把这个Message标记为完成,然后从queue中删除。我们将无法再操作这个尚未处理完成的消息。

  实际场景中,如果一个Consumer异常退出了,我们希望它处理的数据能够被另外的Consumer处理,这样数据在这种情况下(通道关闭、连接关闭、TCP连接丢失等情况)就不会丢失了。

  为了保证数据不被丢失,RabbitMQ支持消息确认机制,ack(nowledgments)是从Consumer消费后发送到一个特定的消息告诉RabbitMQ已经收到、处理结束,RabbitMQ可以去安全的删除它了。

  如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message重新排进队列,发送到下一个Consumer。这样就保证了在Consumer异常退出的情况下数据也不会丢失。

  这里并没有用到超时机制。RabbitMQ仅仅通过Consumer的连接中断来确认该Message并没有被正确处理。也就是说,RabbitMQ给了Consumer足够长的时间来做数据处理。

  消息确认默认是关闭的,我们需要通过,d.ACK(false)来告诉RabbitMQ我们已经完成任务。

  producer_acknowledgments(消息生产者).go:

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "strings"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange name
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-acknowledgments"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. bodyMsg := bodyFrom(os.Args)
  26. //调用发布消息函数
  27. publish(uri, exchangeName, queueName, bodyMsg)
  28. log.Printf("published %dB OK", len(bodyMsg))
  29. }
  30. func bodyFrom(args []string) string {
  31. var s string
  32. if (len(args) < 2) || os.Args[1] == "" {
  33. s = "hello idoall.org"
  34. } else {
  35. s = strings.Join(args[1:], " ")
  36. }
  37. return s
  38. }
  39. //发布者的方法
  40. //
  41. //@amqpURI, amqp的地址
  42. //@exchange, exchange的名称
  43. //@queue, queue的名称
  44. //@body, 主体内容
  45. func publish(amqpURI string, exchange string, queue string, body string){
  46. //建立连接
  47. log.Printf("dialing %q", amqpURI)
  48. connection, err := amqp.Dial(amqpURI)
  49. failOnError(err, "Failed to connect to RabbitMQ")
  50. defer connection.Close()
  51. //创建一个Channel
  52. log.Printf("got Connection, getting Channel")
  53. channel, err := connection.Channel()
  54. failOnError(err, "Failed to open a channel")
  55. defer channel.Close()
  56. log.Printf("got queue, declaring %q", queue)
  57. //创建一个queue
  58. q, err := channel.QueueDeclare(
  59. queueName, // name
  60. false, // durable
  61. false, // delete when unused
  62. false, // exclusive
  63. false, // no-wait
  64. nil, // arguments
  65. )
  66. failOnError(err, "Failed to declare a queue")
  67. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  68. // Producer只能发送到exchange,它是不能直接发送到queue的。
  69. // 现在我们使用默认的exchange(名字是空字符)。这个默认的exchange允许我们发送给指定的queue。
  70. // routing_key就是指定的queue名字。
  71. err = channel.Publish(
  72. exchange, // exchange
  73. q.Name, // routing key
  74. false, // mandatory
  75. false, // immediate
  76. amqp.Publishing {
  77. Headers: amqp.Table{},
  78. ContentType: "text/plain",
  79. ContentEncoding: "",
  80. Body: []byte(body),
  81. })
  82. failOnError(err, "Failed to publish a message")
  83. }

  consumer_acknowledgments(消息消费者).go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "bytes"
  6. "time"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange nam
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-acknowledgments"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. //调用消息接收者
  26. consumer(uri, exchangeName, queueName)
  27. }
  28. //接收者方法
  29. //
  30. //@amqpURI, amqp的地址
  31. //@exchange, exchange的名称
  32. //@queue, queue的名称
  33. func consumer(amqpURI string, exchange string, queue string){
  34. //建立连接
  35. log.Printf("dialing %q", amqpURI)
  36. connection, err := amqp.Dial(amqpURI)
  37. failOnError(err, "Failed to connect to RabbitMQ")
  38. defer connection.Close()
  39. //创建一个Channel
  40. log.Printf("got Connection, getting Channel")
  41. channel, err := connection.Channel()
  42. failOnError(err, "Failed to open a channel")
  43. defer channel.Close()
  44. log.Printf("got queue, declaring %q", queue)
  45. //创建一个queue
  46. q, err := channel.QueueDeclare(
  47. queueName, // name
  48. false, // durable
  49. false, // delete when unused
  50. false, // exclusive
  51. false, // no-wait
  52. nil, // arguments
  53. )
  54. failOnError(err, "Failed to declare a queue")
  55. log.Printf("Queue bound to Exchange, starting Consume")
  56. //订阅消息
  57. msgs, err := channel.Consume(
  58. q.Name, // queue
  59. "", // consumer
  60. false, // auto-ack
  61. false, // exclusive
  62. false, // no-local
  63. false, // no-wait
  64. nil, // args
  65. )
  66. failOnError(err, "Failed to register a consumer")
  67. //创建一个channel
  68. forever := make(chan bool)
  69. //调用gorountine
  70. go func() {
  71. for d := range msgs {
  72. log.Printf("Received a message: %s", d.Body)
  73. dot_count := bytes.Count(d.Body, []byte("."))
  74. t := time.Duration(dot_count)
  75. time.Sleep(t * time.Second)
  76. log.Printf("Done")
  77. d.Ack(false)
  78. }
  79. }()
  80. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  81. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  82. <-forever
  83. }

  查看结果


  我们先使用Producer来发送一列消息:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_acknowledgments.go First message. && go run producer_acknowledgments.go Second message.. && go run producer_acknowledgments.go Third message... && go run producer_acknowledgments.go Fourth message.... && go run producer_acknowledgments.go Fifth message.....
  2. 2016/07/23 21:41:40 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 21:41:40 got Connection, getting Channel
  4. 2016/07/23 21:41:40 got queue, declaring "test-idoall-queues-acknowledgments"
  5. 2016/07/23 21:41:40 declared queue, publishing 14B body ("First message.")
  6. 2016/07/23 21:41:40 published 14B OK
  7. 2016/07/23 21:41:41 dialing "amqp://guest:guest@localhost:5672/"
  8. 2016/07/23 21:41:41 got Connection, getting Channel
  9. 2016/07/23 21:41:41 got queue, declaring "test-idoall-queues-acknowledgments"
  10. 2016/07/23 21:41:41 declared queue, publishing 16B body ("Second message..")
  11. 2016/07/23 21:41:41 published 16B OK
  12. 2016/07/23 21:41:41 dialing "amqp://guest:guest@localhost:5672/"
  13. 2016/07/23 21:41:41 got Connection, getting Channel
  14. 2016/07/23 21:41:41 got queue, declaring "test-idoall-queues-acknowledgments"
  15. 2016/07/23 21:41:41 declared queue, publishing 16B body ("Third message...")
  16. 2016/07/23 21:41:41 published 16B OK
  17. 2016/07/23 21:41:42 dialing "amqp://guest:guest@localhost:5672/"
  18. 2016/07/23 21:41:42 got Connection, getting Channel
  19. 2016/07/23 21:41:42 got queue, declaring "test-idoall-queues-acknowledgments"
  20. 2016/07/23 21:41:42 declared queue, publishing 18B body ("Fourth message....")
  21. 2016/07/23 21:41:42 published 18B OK
  22. 2016/07/23 21:41:43 dialing "amqp://guest:guest@localhost:5672/"
  23. 2016/07/23 21:41:43 got Connection, getting Channel
  24. 2016/07/23 21:41:43 got queue, declaring "test-idoall-queues-acknowledgments"
  25. 2016/07/23 21:41:43 declared queue, publishing 18B body ("Fifth message.....")
  26. 2016/07/23 21:41:43 published 18B OK

  通过rabbitmqctl命令,来看下messages_unacknowledged的情况:

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl list_queues name messages_ready messages_unacknowledged
  2. Listing queues ...
  3. test-idoall-queues-task 0 0
  4. test-idoall-queues 0 0
  5. test-idoall-queues-acknowledgments 5 0

  使用Consumer来订阅消息操作到第三条的时候,我们按CTRL+C退出,这个时候相当于消息已经被读取,但是未发送d.ACK(false):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_acknowledgments.go
  2. 2016/07/23 21:56:35 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 21:56:35 got Connection, getting Channel
  4. 2016/07/23 21:56:35 got queue, declaring "test-idoall-queues-acknowledgments"
  5. 2016/07/23 21:56:35 Queue bound to Exchange, starting Consume
  6. 2016/07/23 21:56:35 [*] Waiting for messages. To exit press CTRL+C
  7. 2016/07/23 21:56:35 Received a message: First message.
  8. 2016/07/23 21:56:36 Done
  9. 2016/07/23 21:56:36 Received a message: Second message..
  10. 2016/07/23 21:56:38 Done
  11. 2016/07/23 21:56:38 Received a message: Third message...
  12. ^Csignal: interrupt

  再通过rabbitmqctl命令可以看到,还是有3条消息未处理

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl list_queues name messages_ready messages_unacknowledged
  2. Listing queues ...
  3. test-idoall-queues-task 0 0
  4. test-idoall-queues 0 0
  5. test-idoall-queues-acknowledgments 3 0

5.4、Message durability消息持久化

  如果服务器死机或程序 crash了,数据仍然会丢失。为了确保消息不会丢失,我们需要将queue和Message做持久化操作。

  将durable设置为true可以做持久化处理(生产者和消息者的代码里都要设置),如果是已经存在的一个queue 没有设置过持久化,再重新设置是不起作用的,我们需要重新为queue设置一个名字。

  最后在Producer发布消息的时候,我们需要设置DeliveryMode为amqp.Persistent,持久化的工作就做完了,下面我们来看代码

  producer_durability.go(消息生产者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "strings"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange name
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-durability"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. bodyMsg := bodyFrom(os.Args)
  26. //调用发布消息函数
  27. publish(uri, exchangeName, queueName, bodyMsg)
  28. log.Printf("published %dB OK", len(bodyMsg))
  29. }
  30. func bodyFrom(args []string) string {
  31. var s string
  32. if (len(args) < 2) || os.Args[1] == "" {
  33. s = "hello idoall.org"
  34. } else {
  35. s = strings.Join(args[1:], " ")
  36. }
  37. return s
  38. }
  39. //发布者的方法
  40. //
  41. //@amqpURI, amqp的地址
  42. //@exchange, exchange的名称
  43. //@queue, queue的名称
  44. //@body, 主体内容
  45. func publish(amqpURI string, exchange string, queue string, body string){
  46. //建立连接
  47. log.Printf("dialing %q", amqpURI)
  48. connection, err := amqp.Dial(amqpURI)
  49. failOnError(err, "Failed to connect to RabbitMQ")
  50. defer connection.Close()
  51. //创建一个Channel
  52. log.Printf("got Connection, getting Channel")
  53. channel, err := connection.Channel()
  54. failOnError(err, "Failed to open a channel")
  55. defer channel.Close()
  56. log.Printf("got queue, declaring %q", queue)
  57. //创建一个queue
  58. q, err := channel.QueueDeclare(
  59. queueName, // name
  60. true, // durable
  61. false, // delete when unused
  62. false, // exclusive
  63. false, // no-wait
  64. nil, // arguments
  65. )
  66. failOnError(err, "Failed to declare a queue")
  67. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  68. // Producer只能发送到exchange,它是不能直接发送到queue的。
  69. // 现在我们使用默认的exchange(名字是空字符)。这个默认的exchange允许我们发送给指定的queue。
  70. // routing_key就是指定的queue名字。
  71. err = channel.Publish(
  72. exchange, // exchange
  73. q.Name, // routing key
  74. false, // mandatory
  75. false, // immediate
  76. amqp.Publishing {
  77. Headers: amqp.Table{},
  78. DeliveryMode: amqp.Persistent,
  79. ContentType: "text/plain",
  80. ContentEncoding: "",
  81. Body: []byte(body),
  82. })
  83. failOnError(err, "Failed to publish a message")
  84. }

  consumer_durability.go(消息接收者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "bytes"
  6. "time"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange nam
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-durability"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. //调用消息接收者
  26. consumer(uri, exchangeName, queueName)
  27. }
  28. //接收者方法
  29. //
  30. //@amqpURI, amqp的地址
  31. //@exchange, exchange的名称
  32. //@queue, queue的名称
  33. func consumer(amqpURI string, exchange string, queue string){
  34. //建立连接
  35. log.Printf("dialing %q", amqpURI)
  36. connection, err := amqp.Dial(amqpURI)
  37. failOnError(err, "Failed to connect to RabbitMQ")
  38. defer connection.Close()
  39. //创建一个Channel
  40. log.Printf("got Connection, getting Channel")
  41. channel, err := connection.Channel()
  42. failOnError(err, "Failed to open a channel")
  43. defer channel.Close()
  44. log.Printf("got queue, declaring %q", queue)
  45. //创建一个queue
  46. q, err := channel.QueueDeclare(
  47. queueName, // name
  48. true, // durable
  49. false, // delete when unused
  50. false, // exclusive
  51. false, // no-wait
  52. nil, // arguments
  53. )
  54. failOnError(err, "Failed to declare a queue")
  55. log.Printf("Queue bound to Exchange, starting Consume")
  56. //订阅消息
  57. msgs, err := channel.Consume(
  58. q.Name, // queue
  59. "", // consumer
  60. false, // auto-ack
  61. false, // exclusive
  62. false, // no-local
  63. false, // no-wait
  64. nil, // args
  65. )
  66. failOnError(err, "Failed to register a consumer")
  67. //创建一个channel
  68. forever := make(chan bool)
  69. //调用gorountine
  70. go func() {
  71. for d := range msgs {
  72. log.Printf("Received a message: %s", d.Body)
  73. dot_count := bytes.Count(d.Body, []byte("."))
  74. t := time.Duration(dot_count)
  75. time.Sleep(t * time.Second)
  76. log.Printf("Done")
  77. d.Ack(false)
  78. }
  79. }()
  80. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  81. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  82. <-forever
  83. }

  查看结果


  我们先使用Producer来发送一列消息:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_durability.go First message. && go run producer_durability.go Second message.. && go run producer_durability.go Third message... && go run producer_durability.go Fourth message.... && go run producer_durability.go Fifth message.....
  2. 2016/07/23 22:35:03 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 22:35:03 got Connection, getting Channel
  4. 2016/07/23 22:35:03 got queue, declaring "test-idoall-queues-durability"
  5. 2016/07/23 22:35:04 declared queue, publishing 14B body ("First message.")
  6. 2016/07/23 22:35:04 published 14B OK
  7. 2016/07/23 22:35:04 dialing "amqp://guest:guest@localhost:5672/"
  8. 2016/07/23 22:35:04 got Connection, getting Channel
  9. 2016/07/23 22:35:04 got queue, declaring "test-idoall-queues-durability"
  10. 2016/07/23 22:35:04 declared queue, publishing 16B body ("Second message..")
  11. 2016/07/23 22:35:04 published 16B OK
  12. 2016/07/23 22:35:05 dialing "amqp://guest:guest@localhost:5672/"
  13. 2016/07/23 22:35:05 got Connection, getting Channel
  14. 2016/07/23 22:35:05 got queue, declaring "test-idoall-queues-durability"
  15. 2016/07/23 22:35:05 declared queue, publishing 16B body ("Third message...")
  16. 2016/07/23 22:35:05 published 16B OK
  17. 2016/07/23 22:35:06 dialing "amqp://guest:guest@localhost:5672/"
  18. 2016/07/23 22:35:06 got Connection, getting Channel
  19. 2016/07/23 22:35:06 got queue, declaring "test-idoall-queues-durability"
  20. 2016/07/23 22:35:06 declared queue, publishing 18B body ("Fourth message....")
  21. 2016/07/23 22:35:06 published 18B OK
  22. 2016/07/23 22:35:06 dialing "amqp://guest:guest@localhost:5672/"
  23. 2016/07/23 22:35:06 got Connection, getting Channel
  24. 2016/07/23 22:35:06 got queue, declaring "test-idoall-queues-durability"
  25. 2016/07/23 22:35:06 declared queue, publishing 18B body ("Fifth message.....")
  26. 2016/07/23 22:35:06 published 18B OK

  通过rabbitmqctl list_queues命令,来看下messages_unacknowledged的情况:

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl list_queues
  2. Listing queues ...
  3. test-idoall-queues-task 0
  4. test-idoall-queues 0
  5. test-idoall-queues-durability 5
  6. test-idoall-queues-acknowledgments 0

  重启RabbitMQ-Server

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl stop
  2. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmq-server
  3. RabbitMQ 3.6.3. Copyright (C) 2007-2016 Pivotal Software, Inc.
  4. ## ## Licensed under the MPL. See http://www.rabbitmq.com/
  5. ## ##
  6. ########## Logs: /home/lion/_app/rabbitmq_server-3.6.3/var/log/rabbitmq/rabbit@node1.log
  7. ###### ## /home/lion/_app/rabbitmq_server-3.6.3/var/log/rabbitmq/rabbit@node1-sasl.log
  8. ##########
  9. Starting broker...
  10. completed with 6 plugins.

  再次通过rabbitmqctl list_queues命令查看,可以看到消息是存在的,说明我们的持久化是成功的

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl list_queues
  2. Listing queues ...
  3. test-idoall-queues-durability 5

5.5、Fair dispatch 公平分发

  上面的,分发机制不是那么优雅。默认状态下,RabbitMQ将第n个Message分发给第n个Consumer。当然n是取余后的。它不管Consumer是否还有unacked Message,只是按照这个默认机制进行分发。

  那么如果有个Consumer工作比较重,那么就会导致有的Consumer基本没事可做,有的Consumer却是毫无休息的机会。

  通过 ch.Qos 方法设置预读取消息prefetch count=1 。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。

  producer_fair_dispatch.go(消息生产者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "strings"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange name
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-fair_dispatch"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. bodyMsg := bodyFrom(os.Args)
  26. //调用发布消息函数
  27. publish(uri, exchangeName, queueName, bodyMsg)
  28. log.Printf("published %dB OK", len(bodyMsg))
  29. }
  30. func bodyFrom(args []string) string {
  31. var s string
  32. if (len(args) < 2) || os.Args[1] == "" {
  33. s = "hello idoall.org"
  34. } else {
  35. s = strings.Join(args[1:], " ")
  36. }
  37. return s
  38. }
  39. //发布者的方法
  40. //
  41. //@amqpURI, amqp的地址
  42. //@exchange, exchange的名称
  43. //@queue, queue的名称
  44. //@body, 主体内容
  45. func publish(amqpURI string, exchange string, queue string, body string){
  46. //建立连接
  47. log.Printf("dialing %q", amqpURI)
  48. connection, err := amqp.Dial(amqpURI)
  49. failOnError(err, "Failed to connect to RabbitMQ")
  50. defer connection.Close()
  51. //创建一个Channel
  52. log.Printf("got Connection, getting Channel")
  53. channel, err := connection.Channel()
  54. failOnError(err, "Failed to open a channel")
  55. defer channel.Close()
  56. log.Printf("got queue, declaring %q", queue)
  57. //创建一个queue
  58. q, err := channel.QueueDeclare(
  59. queueName, // name
  60. true, // durable
  61. false, // delete when unused
  62. false, // exclusive
  63. false, // no-wait
  64. nil, // arguments
  65. )
  66. failOnError(err, "Failed to declare a queue")
  67. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  68. // Producer只能发送到exchange,它是不能直接发送到queue的。
  69. // 现在我们使用默认的exchange(名字是空字符)。这个默认的exchange允许我们发送给指定的queue。
  70. // routing_key就是指定的queue名字。
  71. err = channel.Publish(
  72. exchange, // exchange
  73. q.Name, // routing key
  74. false, // mandatory
  75. false, // immediate
  76. amqp.Publishing {
  77. Headers: amqp.Table{},
  78. DeliveryMode: amqp.Persistent,
  79. ContentType: "text/plain",
  80. ContentEncoding: "",
  81. Body: []byte(body),
  82. })
  83. failOnError(err, "Failed to publish a message")
  84. }

  consumer_fair_dispatch.go(消息消费者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "bytes"
  6. "time"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange nam
  13. exchangeName = ""
  14. //Durable AMQP queue name
  15. queueName = "test-idoall-queues-fair_dispatch"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. //调用消息接收者
  26. consumer(uri, exchangeName, queueName)
  27. }
  28. //接收者方法
  29. //
  30. //@amqpURI, amqp的地址
  31. //@exchange, exchange的名称
  32. //@queue, queue的名称
  33. func consumer(amqpURI string, exchange string, queue string){
  34. //建立连接
  35. log.Printf("dialing %q", amqpURI)
  36. connection, err := amqp.Dial(amqpURI)
  37. failOnError(err, "Failed to connect to RabbitMQ")
  38. defer connection.Close()
  39. //创建一个Channel
  40. log.Printf("got Connection, getting Channel")
  41. channel, err := connection.Channel()
  42. failOnError(err, "Failed to open a channel")
  43. defer channel.Close()
  44. log.Printf("got queue, declaring %q", queue)
  45. //创建一个queue
  46. q, err := channel.QueueDeclare(
  47. queueName, // name
  48. true, // durable
  49. false, // delete when unused
  50. false, // exclusive
  51. false, // no-wait
  52. nil, // arguments
  53. )
  54. failOnError(err, "Failed to declare a queue")
  55. //每次只取一条消息
  56. err = channel.Qos(
  57. 1, // prefetch count
  58. 0, // prefetch size
  59. false, // global
  60. )
  61. failOnError(err, "Failed to set QoS")
  62. log.Printf("Queue bound to Exchange, starting Consume")
  63. //订阅消息
  64. msgs, err := channel.Consume(
  65. q.Name, // queue
  66. "", // consumer
  67. false, // auto-ack
  68. false, // exclusive
  69. false, // no-local
  70. false, // no-wait
  71. nil, // args
  72. )
  73. failOnError(err, "Failed to register a consumer")
  74. //创建一个channel
  75. forever := make(chan bool)
  76. //调用gorountine
  77. go func() {
  78. for d := range msgs {
  79. log.Printf("Received a message: %s", d.Body)
  80. dot_count := bytes.Count(d.Body, []byte("."))
  81. t := time.Duration(dot_count)
  82. time.Sleep(t * time.Second)
  83. log.Printf("Done")
  84. d.Ack(false)
  85. }
  86. }()
  87. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  88. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  89. <-forever
  90. }

  查看结果


  我们先使用Producer来发送一列消息:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_fair_dispatch.go First message. && go run producer_fair_dispatch.go Second message.. && go run producer_fair_dispatch.go Third message... && go run producer_fair_dispatch.go Fourth message.... && go run producer_fair_dispatch.go Fifth message.....
  2. 2016/07/23 23:09:24 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 23:09:24 got Connection, getting Channel
  4. 2016/07/23 23:09:24 got queue, declaring "test-idoall-queues-fair_dispatch"
  5. 2016/07/23 23:09:24 declared queue, publishing 14B body ("First message.")
  6. 2016/07/23 23:09:24 published 14B OK
  7. 2016/07/23 23:09:24 dialing "amqp://guest:guest@localhost:5672/"
  8. 2016/07/23 23:09:24 got Connection, getting Channel
  9. 2016/07/23 23:09:24 got queue, declaring "test-idoall-queues-fair_dispatch"
  10. 2016/07/23 23:09:24 declared queue, publishing 16B body ("Second message..")
  11. 2016/07/23 23:09:24 published 16B OK
  12. 2016/07/23 23:09:25 dialing "amqp://guest:guest@localhost:5672/"
  13. 2016/07/23 23:09:25 got Connection, getting Channel
  14. 2016/07/23 23:09:25 got queue, declaring "test-idoall-queues-fair_dispatch"
  15. 2016/07/23 23:09:25 declared queue, publishing 16B body ("Third message...")
  16. 2016/07/23 23:09:25 published 16B OK
  17. 2016/07/23 23:09:26 dialing "amqp://guest:guest@localhost:5672/"
  18. 2016/07/23 23:09:26 got Connection, getting Channel
  19. 2016/07/23 23:09:26 got queue, declaring "test-idoall-queues-fair_dispatch"
  20. 2016/07/23 23:09:26 declared queue, publishing 18B body ("Fourth message....")
  21. 2016/07/23 23:09:26 published 18B OK
  22. 2016/07/23 23:09:27 dialing "amqp://guest:guest@localhost:5672/"
  23. 2016/07/23 23:09:27 got Connection, getting Channel
  24. 2016/07/23 23:09:27 got queue, declaring "test-idoall-queues-fair_dispatch"
  25. 2016/07/23 23:09:27 declared queue, publishing 18B body ("Fifth message.....")
  26. 2016/07/23 23:09:27 published 18B OK

  再依次在两个Console中依次执行下面的命令,可以看到消息被正常的分发了

  Console1(consumer):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_fair_dispatch.go
  2. 2016/07/23 23:10:47 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 23:10:47 got Connection, getting Channel
  4. 2016/07/23 23:10:47 got queue, declaring "test-idoall-queues-fair_dispatch"
  5. 2016/07/23 23:10:47 Queue bound to Exchange, starting Consume
  6. 2016/07/23 23:10:47 [*] Waiting for messages. To exit press CTRL+C
  7. 2016/07/23 23:10:47 Received a message: First message.
  8. 2016/07/23 23:10:48 Done
  9. 2016/07/23 23:10:48 Received a message: Second message..
  10. 2016/07/23 23:10:50 Done
  11. 2016/07/23 23:10:50 Received a message: Fourth message....
  12. 2016/07/23 23:10:54 Done

  Console2(consumer):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_fair_dispatch.go
  2. 2016/07/23 23:10:49 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/23 23:10:49 got Connection, getting Channel
  4. 2016/07/23 23:10:49 got queue, declaring "test-idoall-queues-fair_dispatch"
  5. 2016/07/23 23:10:49 Queue bound to Exchange, starting Consume
  6. 2016/07/23 23:10:49 [*] Waiting for messages. To exit press CTRL+C
  7. 2016/07/23 23:10:49 Received a message: Third message...
  8. 2016/07/23 23:10:52 Done
  9. 2016/07/23 23:10:52 Received a message: Fifth message.....
  10. 2016/07/23 23:10:57 Done

​ 基于AMQP的更多通道和消息属性,可以浏览AMQP API参考

5.6、Exchanges & Bindings

  RabbitMQ 的Messaging Model就是Producer并不会直接发送Message到queue。实际上,Producer并不知道它发送的Message是否已经到达queue。

  Producer发送的Message实际上是发到了Exchange中。它的功能也很简单:从Producer接收Message,然后投递到queue中。Exchange需要知道如何处理Message,是把它放到一个queue中,还是放到多个queue中?这个rule是通过Exchange 的类型定义的。

  我们知道有三种类型的Exchange:direct,,topic,headers 和fanout。fanout就是广播模式,会将所有的Message都放到它所知道的queue中。

  现在我们已经创建了fanout类型的exchange和没有名字的queue(实际上是RabbitMQ帮我们取了名字)。那exchange怎么样知道它的Message发送到哪个queue呢?答案就是通过bindings

  idoall.org

  通过rabbitmqctl可以列出当前所有的Exchange:

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl list_exchanges
  2. Listing exchanges ...
  3. amq.direct direct
  4. amq.fanout fanout
  5. amq.match headers
  6. amq.headers headers
  7. direct
  8. amq.rabbitmq.trace topic
  9. amq.topic topic
  10. amq.rabbitmq.log topic

注意:amq.* 是RabbitMQ默认创建的。

​ 我们假设做一个日志系统,其中一个运行的接收程序Consumer发到消息后写入到磁盘中,同时, 另一个Consumer将收到的日志输出到屏幕上。

  producer_exchange_logs.go(消息生产者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "strings"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange name
  13. exchangeName = "test-idoall-exchange-logs"
  14. //Exchange type - direct|fanout|topic|x-custom
  15. exchangeType = "fanout"
  16. //AMQP routing key
  17. routingKey = ""
  18. )
  19. //如果存在错误,则输出
  20. func failOnError(err error, msg string) {
  21. if err != nil {
  22. log.Fatalf("%s: %s", msg, err)
  23. panic(fmt.Sprintf("%s: %s", msg, err))
  24. }
  25. }
  26. func main(){
  27. bodyMsg := bodyFrom(os.Args)
  28. //调用发布消息函数
  29. publish(uri, exchangeName, exchangeType, routingKey, bodyMsg)
  30. log.Printf("published %dB OK", len(bodyMsg))
  31. }
  32. func bodyFrom(args []string) string {
  33. var s string
  34. if (len(args) < 2) || os.Args[1] == "" {
  35. s = "hello idoall.org"
  36. } else {
  37. s = strings.Join(args[1:], " ")
  38. }
  39. return s
  40. }
  41. //发布者的方法
  42. //
  43. //@amqpURI, amqp的地址
  44. //@exchange, exchange的名称
  45. //@exchangeType, exchangeType的类型direct|fanout|topic
  46. //@routingKey, routingKey的名称
  47. //@body, 主体内容
  48. func publish(amqpURI string, exchange string, exchangeType string, routingKey string, body string){
  49. //建立连接
  50. log.Printf("dialing %q", amqpURI)
  51. connection, err := amqp.Dial(amqpURI)
  52. failOnError(err, "Failed to connect to RabbitMQ")
  53. defer connection.Close()
  54. //创建一个Channel
  55. log.Printf("got Connection, getting Channel")
  56. channel, err := connection.Channel()
  57. failOnError(err, "Failed to open a channel")
  58. defer channel.Close()
  59. //创建一个queue
  60. log.Printf("got Channel, declaring %q Exchange (%q)", exchangeType, exchange)
  61. err = channel.ExchangeDeclare(
  62. exchange, // name
  63. exchangeType, // type
  64. true, // durable
  65. false, // auto-deleted
  66. false, // internal
  67. false, // noWait
  68. nil, // arguments
  69. )
  70. failOnError(err, "Failed to declare a queue")
  71. // 发布消息
  72. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  73. err = channel.Publish(
  74. exchange, // exchange
  75. routingKey, // routing key
  76. false, // mandatory
  77. false, // immediate
  78. amqp.Publishing {
  79. Headers: amqp.Table{},
  80. ContentType: "text/plain",
  81. ContentEncoding: "",
  82. Body: []byte(body),
  83. })
  84. failOnError(err, "Failed to publish a message")
  85. }

  consumer_exchange_logs.go(消息消费者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/streadway/amqp"
  6. )
  7. const (
  8. //AMQP URI
  9. uri = "amqp://guest:guest@localhost:5672/"
  10. //Durable AMQP exchange name
  11. exchangeName = "test-idoall-exchange-logs"
  12. //Exchange type - direct|fanout|topic|x-custom
  13. exchangeType = "fanout"
  14. //AMQP binding key
  15. bindingKey = ""
  16. //Durable AMQP queue name
  17. queueName = ""
  18. )
  19. //如果存在错误,则输出
  20. func failOnError(err error, msg string) {
  21. if err != nil {
  22. log.Fatalf("%s: %s", msg, err)
  23. panic(fmt.Sprintf("%s: %s", msg, err))
  24. }
  25. }
  26. func main(){
  27. //调用消息接收者
  28. consumer(uri, exchangeName, exchangeType, queueName, bindingKey)
  29. }
  30. //接收者方法
  31. //
  32. //@amqpURI, amqp的地址
  33. //@exchange, exchange的名称
  34. //@exchangeType, exchangeType的类型direct|fanout|topic
  35. //@queue, queue的名称
  36. //@key , 绑定的key名称
  37. func consumer(amqpURI string, exchange string, exchangeType string, queue string, key string){
  38. //建立连接
  39. log.Printf("dialing %q", amqpURI)
  40. connection, err := amqp.Dial(amqpURI)
  41. failOnError(err, "Failed to connect to RabbitMQ")
  42. defer connection.Close()
  43. //创建一个Channel
  44. log.Printf("got Connection, getting Channel")
  45. channel, err := connection.Channel()
  46. failOnError(err, "Failed to open a channel")
  47. defer channel.Close()
  48. //创建一个exchange
  49. log.Printf("got Channel, declaring Exchange (%q)", exchange)
  50. err = channel.ExchangeDeclare(
  51. exchange, // name of the exchange
  52. exchangeType, // type
  53. true, // durable
  54. false, // delete when complete
  55. false, // internal
  56. false, // noWait
  57. nil, // arguments
  58. );
  59. failOnError(err, "Exchange Declare:")
  60. //创建一个queue
  61. q, err := channel.QueueDeclare(
  62. queueName, // name
  63. false, // durable
  64. false, // delete when unused
  65. true, // exclusive 当Consumer关闭连接时,这个queue要被deleted
  66. false, // no-wait
  67. nil, // arguments
  68. )
  69. failOnError(err, "Failed to declare a queue")
  70. //绑定到exchange
  71. err = channel.QueueBind(
  72. q.Name, // name of the queue
  73. key, // bindingKey
  74. exchange, // sourceExchange
  75. false, // noWait
  76. nil, // arguments
  77. );
  78. failOnError(err, "Failed to bind a queue")
  79. log.Printf("Queue bound to Exchange, starting Consume")
  80. //订阅消息
  81. msgs, err := channel.Consume(
  82. q.Name, // queue
  83. "", // consumer
  84. false, // auto-ack
  85. false, // exclusive
  86. false, // no-local
  87. false, // no-wait
  88. nil, // args
  89. )
  90. failOnError(err, "Failed to register a consumer")
  91. //创建一个channel
  92. forever := make(chan bool)
  93. //调用gorountine
  94. go func() {
  95. for d := range msgs {
  96. log.Printf(" [x] %s", d.Body)
  97. }
  98. }()
  99. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  100. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  101. <-forever
  102. }

  在AMQP客户端 ,当routing key为空的时候, 自动创建一个随机的queue,同时设置exclusive为true时,当这个Consumer关闭链接 时,会删除这个queue。

  当使用fanout类型的exchange和没有名字的queue,Cusomer并不知道消息发送到了哪个queue,这个时候我们就需要用到QueueBind方法,来绑定到exchange。

过程中可以使用rabbitmqctl list_bindings命令来查看绑定的列表

  查看结果


  Console1(Consumer),输出到文件:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_logs.go &> consumer_exchange_logs.log

  Console2(Consumer),打印到控制台:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_logs.go

  使用Producer来发送消息:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_exchange_logs.go
  2. 2016/07/24 02:21:49 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 02:21:49 got Connection, getting Channel
  4. 2016/07/24 02:21:49 got Channel, declaring "fanout" Exchange ("test-idoall-exchange-logs")
  5. 2016/07/24 02:21:49 declared queue, publishing 16B body ("hello idoall.org")
  6. 2016/07/24 02:21:49 published 16B OK

  这时可以使用rabbitmqctl list_bindings来查看我们的绑定信息,可以看到queueu的名字是随机的

  1. lion@node1:~/_code/_rabbitmq/_golang$ rabbitmqctl list_bindings
  2. Listing bindings ...
  3. exchange amq.gen-D2AnzGsLUMhJCPk7YxgUUw queue amq.gen-D2AnzGsLUMhJCPk7YxgUUw []
  4. exchange amq.gen-GC4VDS3mxsAOTEqii_WsWw queue amq.gen-GC4VDS3mxsAOTEqii_WsWw []
  5. test-idoall-exchange-logs exchange amq.gen-D2AnzGsLUMhJCPk7YxgUUw queue []
  6. test-idoall-exchange-logs exchange amq.gen-GC4VDS3mxsAOTEqii_WsWw queue []

  使用cat命令,查看consumer_exchange_logs.log文件,可以看到内容被输入到文件中

  1. lion@node1:~/_code/_rabbitmq/_golang$ cat consumer_exchange_logs.log
  2. 2016/07/24 02:25:17 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 02:25:17 got Connection, getting Channel
  4. 2016/07/24 02:25:17 got Channel, declaring Exchange ("test-idoall-exchange-logs")
  5. 2016/07/24 02:25:17 Queue bound to Exchange, starting Consume
  6. 2016/07/24 02:25:17 [*] Waiting for messages. To exit press CTRL+C
  7. signal: interrupt

5.7、Direct exchange

  RabbitMQ支持同一个binding key绑定到多个queue中。Direct exchange的算法就是通过binding key来做匹配的。

对于fanout的exchange来说,routing_key这个参数是被忽略的。

idoall.org

  producer_exchange_direct_logs.go(消息生产者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "strings"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange name
  13. exchangeName = "test-idoall-exchange-direct-logs"
  14. //Exchange type - direct|fanout|topic|x-custom
  15. exchangeType = "direct"
  16. )
  17. //如果存在错误,则输出
  18. func failOnError(err error, msg string) {
  19. if err != nil {
  20. log.Fatalf("%s: %s", msg, err)
  21. panic(fmt.Sprintf("%s: %s", msg, err))
  22. }
  23. }
  24. func main(){
  25. bodyMsg := bodyFrom(os.Args)
  26. //调用发布消息函数
  27. publish(uri, exchangeName, exchangeType, bodyMsg)
  28. log.Printf("published %dB OK", len(bodyMsg))
  29. }
  30. func bodyFrom(args []string) string {
  31. var s string
  32. if (len(args) < 3) || os.Args[2] == "" {
  33. s = "hello idoall.org"
  34. } else {
  35. s = strings.Join(args[2:], " ")
  36. }
  37. return s
  38. }
  39. func severityFrom(args []string) string {
  40. var s string
  41. if (len(args) < 2) || os.Args[1] == "" {
  42. s = "info"
  43. } else {
  44. s = os.Args[1]
  45. }
  46. return s
  47. }
  48. //发布者的方法
  49. //
  50. //@amqpURI, amqp的地址
  51. //@exchange, exchange的名称
  52. //@exchangeType, exchangeType的类型direct|fanout|topic
  53. //@body, 主体内容
  54. func publish(amqpURI string, exchange string, exchangeType string, body string){
  55. //建立连接
  56. log.Printf("dialing %q", amqpURI)
  57. connection, err := amqp.Dial(amqpURI)
  58. failOnError(err, "Failed to connect to RabbitMQ")
  59. defer connection.Close()
  60. //创建一个Channel
  61. log.Printf("got Connection, getting Channel")
  62. channel, err := connection.Channel()
  63. failOnError(err, "Failed to open a channel")
  64. defer channel.Close()
  65. //创建一个queue
  66. log.Printf("got Channel, declaring %q Exchange (%q)", exchangeType, exchange)
  67. err = channel.ExchangeDeclare(
  68. exchange, // name
  69. exchangeType, // type
  70. true, // durable
  71. false, // auto-deleted
  72. false, // internal
  73. false, // noWait
  74. nil, // arguments
  75. )
  76. failOnError(err, "Failed to declare a queue")
  77. // 发布消息
  78. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  79. err = channel.Publish(
  80. exchange, // exchange
  81. severityFrom(os.Args), // routing key
  82. false, // mandatory
  83. false, // immediate
  84. amqp.Publishing {
  85. Headers: amqp.Table{},
  86. ContentType: "text/plain",
  87. ContentEncoding: "",
  88. Body: []byte(body),
  89. })
  90. failOnError(err, "Failed to publish a message")
  91. }

  consumer_exchange_direct_logs.go(消息消费者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "github.com/streadway/amqp"
  7. )
  8. const (
  9. //AMQP URI
  10. uri = "amqp://guest:guest@localhost:5672/"
  11. //Durable AMQP exchange name
  12. exchangeName = "test-idoall-exchange-direct-logs"
  13. //Exchange type - direct|fanout|topic|x-custom
  14. exchangeType = "direct"
  15. //AMQP binding key
  16. bindingKey = ""
  17. //Durable AMQP queue name
  18. queueName = ""
  19. )
  20. //如果存在错误,则输出
  21. func failOnError(err error, msg string) {
  22. if err != nil {
  23. log.Fatalf("%s: %s", msg, err)
  24. panic(fmt.Sprintf("%s: %s", msg, err))
  25. }
  26. }
  27. func main(){
  28. //调用消息接收者
  29. consumer(uri, exchangeName, exchangeType, queueName, bindingKey)
  30. }
  31. //接收者方法
  32. //
  33. //@amqpURI, amqp的地址
  34. //@exchange, exchange的名称
  35. //@exchangeType, exchangeType的类型direct|fanout|topic
  36. //@queue, queue的名称
  37. //@key , 绑定的key名称
  38. func consumer(amqpURI string, exchange string, exchangeType string, queue string, key string){
  39. //建立连接
  40. log.Printf("dialing %q", amqpURI)
  41. connection, err := amqp.Dial(amqpURI)
  42. failOnError(err, "Failed to connect to RabbitMQ")
  43. defer connection.Close()
  44. //创建一个Channel
  45. log.Printf("got Connection, getting Channel")
  46. channel, err := connection.Channel()
  47. failOnError(err, "Failed to open a channel")
  48. defer channel.Close()
  49. //创建一个exchange
  50. log.Printf("got Channel, declaring Exchange (%q)", exchange)
  51. err = channel.ExchangeDeclare(
  52. exchange, // name of the exchange
  53. exchangeType, // type
  54. true, // durable
  55. false, // delete when complete
  56. false, // internal
  57. false, // noWait
  58. nil, // arguments
  59. );
  60. failOnError(err, "Exchange Declare:")
  61. //创建一个queue
  62. q, err := channel.QueueDeclare(
  63. queueName, // name
  64. false, // durable
  65. false, // delete when unused
  66. true, // exclusive 当Consumer关闭连接时,这个queue要被deleted
  67. false, // no-wait
  68. nil, // arguments
  69. )
  70. failOnError(err, "Failed to declare a queue")
  71. if len(os.Args) < 2 {
  72. log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
  73. os.Exit(0)
  74. }
  75. for _, s := range os.Args[1:] {
  76. log.Printf("Binding queue %s to exchange %s with routing key %s",
  77. q.Name, exchange, s)
  78. //绑定到exchange
  79. err = channel.QueueBind(
  80. q.Name, // name of the queue
  81. s, // bindingKey
  82. exchange, // sourceExchange
  83. false, // noWait
  84. nil, // arguments
  85. );
  86. failOnError(err, "Failed to bind a queue")
  87. }
  88. log.Printf("Queue bound to Exchange, starting Consume")
  89. //订阅消息
  90. msgs, err := channel.Consume(
  91. q.Name, // queue
  92. "", // consumer
  93. false, // auto-ack
  94. false, // exclusive
  95. false, // no-local
  96. false, // no-wait
  97. nil, // args
  98. )
  99. failOnError(err, "Failed to register a consumer")
  100. //创建一个channel
  101. forever := make(chan bool)
  102. //调用gorountine
  103. go func() {
  104. for d := range msgs {
  105. log.Printf(" [x] %s", d.Body)
  106. }
  107. }()
  108. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  109. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  110. <-forever
  111. }

  查看结果


  Console1(Consumer),输出到文件:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_direct_logs.go warning error &> consumer_exchange_direct_logs.log

  Console2(Consumer),打印到控制台:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_direct_logs.go info warning error
  2. 2016/07/24 08:48:17 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 08:48:17 got Connection, getting Channel
  4. 2016/07/24 08:48:17 got Channel, declaring Exchange ("test-idoall-exchange-direct-logs")
  5. 2016/07/24 08:48:17 Binding queue amq.gen-vE-62-Lwt4VQYjlBbMLTjQ to exchange test-idoall-exchange-direct-logs with routing key info
  6. 2016/07/24 08:48:17 Binding queue amq.gen-vE-62-Lwt4VQYjlBbMLTjQ to exchange test-idoall-exchange-direct-logs with routing key warning
  7. 2016/07/24 08:48:17 Binding queue amq.gen-vE-62-Lwt4VQYjlBbMLTjQ to exchange test-idoall-exchange-direct-logs with routing key error
  8. 2016/07/24 08:48:17 Queue bound to Exchange, starting Consume
  9. 2016/07/24 08:48:17 [*] Waiting for messages. To exit press CTRL+C

  使用Producer来发送消息:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_exchange_direct_logs.go error "Error. Error" && go run producer_exchange_direct_logs.go info "Info. Info" && go run producer_exchange_direct_logs.go warning "warning. warning"

  我们可以看到,在Console2控制台上能够看到error、info、waring的所有消息,而在文件中只能看到和error相关的消息。

5.7、Topic exchange

  对于Topic的exchange中Message的routing_key是有限制的,不能太随意。格式是以点号“.”分割的字符表。比如:”stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”。你可以放任意的key在routing_key中,不过长度不能超过255 bytes。

  idoall.org

  对于routing_key,有两个特殊字符(在正则表达式里叫元字符)

  • * (星号) 代表任意 一个单词
  • # (hash哈希) 0个或者多个单词

      Topic exchange和其他exchange的区别,由于有”*”和”#”, Topic exchange 非常强大并且可以转化为其他的exchange:

  • 如果binding_key 是 “#” - 它会接收所有的Message,不管routing_key是什么,就像是fanout exchange。

  • 如果 “*”和”#”没有被使用,那么topic exchange就变成了direct exchange。

      下面的代码中,我们将演示Topic的exchange使用”#”和”*”来匹配binding key。

      producer_exchange_topic_logs.go(消息生产者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "strings"
  7. "github.com/streadway/amqp"
  8. )
  9. const (
  10. //AMQP URI
  11. uri = "amqp://guest:guest@localhost:5672/"
  12. //Durable AMQP exchange name
  13. exchangeName = "test-idoall-exchange-direct-logs"
  14. //Exchange type - direct|fanout|topic|x-custom
  15. exchangeType = "fanout"
  16. //AMQP routing key
  17. routingKey = ""
  18. //Durable AMQP queue name
  19. queueName = "test-idoall-queues-direct"
  20. )
  21. //如果存在错误,则输出
  22. func failOnError(err error, msg string) {
  23. if err != nil {
  24. log.Fatalf("%s: %s", msg, err)
  25. panic(fmt.Sprintf("%s: %s", msg, err))
  26. }
  27. }
  28. func main(){
  29. bodyMsg := bodyFrom(os.Args)
  30. //调用发布消息函数
  31. publish(uri, exchangeName, exchangeType, routingKey, bodyMsg)
  32. log.Printf("published %dB OK", len(bodyMsg))
  33. }
  34. func bodyFrom(args []string) string {
  35. var s string
  36. if (len(args) < 2) || os.Args[1] == "" {
  37. s = "hello idoall.org"
  38. } else {
  39. s = strings.Join(args[1:], " ")
  40. }
  41. return s
  42. }
  43. //发布者的方法
  44. //
  45. //@amqpURI, amqp的地址
  46. //@exchange, exchange的名称
  47. //@exchangeType, exchangeType的类型direct|fanout|topic
  48. //@routingKey, routingKey的名称
  49. //@body, 主体内容
  50. func publish(amqpURI string, exchange string, exchangeType string, routingKey string, body string){
  51. //建立连接
  52. log.Printf("dialing %q", amqpURI)
  53. connection, err := amqp.Dial(amqpURI)
  54. failOnError(err, "Failed to connect to RabbitMQ")
  55. defer connection.Close()
  56. //创建一个Channel
  57. log.Printf("got Connection, getting Channel")
  58. channel, err := connection.Channel()
  59. failOnError(err, "Failed to open a channel")
  60. defer channel.Close()
  61. //创建一个queue
  62. log.Printf("got Channel, declaring %q Exchange (%q)", exchangeType, exchange)
  63. err = channel.ExchangeDeclare(
  64. exchange, // name
  65. exchangeType, // type
  66. true, // durable
  67. false, // auto-deleted
  68. false, // internal
  69. false, // noWait
  70. nil, // arguments
  71. )
  72. failOnError(err, "Failed to declare a queue")
  73. // 发布消息
  74. log.Printf("declared queue, publishing %dB body (%q)", len(body), body)
  75. err = channel.Publish(
  76. exchange, // exchange
  77. routingKey, // routing key
  78. false, // mandatory
  79. false, // immediate
  80. amqp.Publishing {
  81. Headers: amqp.Table{},
  82. ContentType: "text/plain",
  83. ContentEncoding: "",
  84. Body: []byte(body),
  85. })
  86. failOnError(err, "Failed to publish a message")
  87. }

  consumer_exchange_topic_logs.go(消息消费者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "github.com/streadway/amqp"
  6. )
  7. const (
  8. //AMQP URI
  9. uri = "amqp://guest:guest@localhost:5672/"
  10. //Durable AMQP exchange name
  11. exchangeName = "test-idoall-exchange-topic-logs"
  12. //Exchange type - direct|fanout|topic|x-custom
  13. exchangeType = "topic"
  14. //AMQP binding key
  15. bindingKey = ""
  16. //Durable AMQP queue name
  17. queueName = ""
  18. )
  19. //如果存在错误,则输出
  20. func failOnError(err error, msg string) {
  21. if err != nil {
  22. log.Fatalf("%s: %s", msg, err)
  23. panic(fmt.Sprintf("%s: %s", msg, err))
  24. }
  25. }
  26. func main(){
  27. //调用消息接收者
  28. consumer(uri, exchangeName, exchangeType, queueName, bindingKey)
  29. }
  30. //接收者方法
  31. //
  32. //@amqpURI, amqp的地址
  33. //@exchange, exchange的名称
  34. //@exchangeType, exchangeType的类型direct|fanout|topic
  35. //@queue, queue的名称
  36. //@key , 绑定的key名称
  37. func consumer(amqpURI string, exchange string, exchangeType string, queue string, key string){
  38. //建立连接
  39. log.Printf("dialing %q", amqpURI)
  40. connection, err := amqp.Dial(amqpURI)
  41. failOnError(err, "Failed to connect to RabbitMQ")
  42. defer connection.Close()
  43. //创建一个Channel
  44. log.Printf("got Connection, getting Channel")
  45. channel, err := connection.Channel()
  46. failOnError(err, "Failed to open a channel")
  47. defer channel.Close()
  48. //创建一个exchange
  49. log.Printf("got Channel, declaring Exchange (%q)", exchange)
  50. err = channel.ExchangeDeclare(
  51. exchange, // name of the exchange
  52. exchangeType, // type
  53. true, // durable
  54. false, // delete when complete
  55. false, // internal
  56. false, // noWait
  57. nil, // arguments
  58. );
  59. failOnError(err, "Exchange Declare:")
  60. //创建一个queue
  61. q, err := channel.QueueDeclare(
  62. queueName, // name
  63. false, // durable
  64. false, // delete when unused
  65. true, // exclusive 当Consumer关闭连接时,这个queue要被deleted
  66. false, // no-wait
  67. nil, // arguments
  68. )
  69. failOnError(err, "Failed to declare a queue")
  70. //绑定到exchange
  71. err = channel.QueueBind(
  72. q.Name, // name of the queue
  73. key, // bindingKey
  74. exchange, // sourceExchange
  75. false, // noWait
  76. nil, // arguments
  77. );
  78. failOnError(err, "Failed to bind a queue")
  79. log.Printf("Queue bound to Exchange, starting Consume")
  80. //订阅消息
  81. msgs, err := channel.Consume(
  82. q.Name, // queue
  83. "", // consumer
  84. false, // auto-ack
  85. false, // exclusive
  86. false, // no-local
  87. false, // no-wait
  88. nil, // args
  89. )
  90. failOnError(err, "Failed to register a consumer")
  91. //创建一个channel
  92. forever := make(chan bool)
  93. //调用gorountine
  94. go func() {
  95. for d := range msgs {
  96. log.Printf(" [x] %s", d.Body)
  97. }
  98. }()
  99. log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
  100. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  101. <-forever
  102. }

  查看结果


  Console1(Consumer),接收所有的日志:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_topic_logs.go "#"
  2. 2016/07/24 09:28:29 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 09:28:29 got Connection, getting Channel
  4. 2016/07/24 09:28:29 got Channel, declaring Exchange ("test-idoall-exchange-topic-logs")
  5. 2016/07/24 09:28:29 Binding queue amq.gen-jW2-PIBg4izXpt96CynyFw to exchange test-idoall-exchange-topic-logs with routing key #
  6. 2016/07/24 09:28:29 Queue bound to Exchange, starting Consume
  7. 2016/07/24 09:28:29 [*] Waiting for messages. To exit press CTRL+C

  Console2(Consumer),接收以”kern”开头的日志:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_topic_logs.go "kern.*"
  2. 2016/07/24 09:34:00 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 09:34:00 got Connection, getting Channel
  4. 2016/07/24 09:34:00 got Channel, declaring Exchange ("test-idoall-exchange-topic-logs")
  5. 2016/07/24 09:34:00 Binding queue amq.gen-8zYBz2uXYbWXcItJMZ3AQA to exchange test-idoall-exchange-topic-logs with routing key kern.*
  6. 2016/07/24 09:34:00 Queue bound to Exchange, starting Consume
  7. 2016/07/24 09:34:00 [*] Waiting for messages. To exit press CTRL+C

  Console3(Consumer),接收第二个单词以”critical”结尾的日志:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_topic_logs.go "*.critical"
  2. 2016/07/24 09:37:21 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 09:37:21 got Connection, getting Channel
  4. 2016/07/24 09:37:21 got Channel, declaring Exchange ("test-idoall-exchange-topic-logs")
  5. 2016/07/24 09:37:21 Binding queue amq.gen-tq9QsD1i1mCps-jrqDtTTA to exchange test-idoall-exchange-topic-logs with routing key *.critical
  6. 2016/07/24 09:37:21 Queue bound to Exchange, starting Consume
  7. 2016/07/24 09:37:21 [*] Waiting for messages. To exit press CTRL+C

  Console4(Consumer), 可以创建多个绑定关系:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run consumer_exchange_topic_logs.go "kern.critical" "A critical kernel error"
  2. 2016/07/24 09:39:35 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 09:39:35 got Connection, getting Channel
  4. 2016/07/24 09:39:35 got Channel, declaring Exchange ("test-idoall-exchange-topic-logs")
  5. 2016/07/24 09:39:35 Binding queue amq.gen-vcaHyCor5bbB2NX7YQhmzA to exchange test-idoall-exchange-topic-logs with routing key kern.critical
  6. 2016/07/24 09:39:35 Binding queue amq.gen-vcaHyCor5bbB2NX7YQhmzA to exchange test-idoall-exchange-topic-logs with routing key A critical kernel error
  7. 2016/07/24 09:39:35 Queue bound to Exchange, starting Consume
  8. 2016/07/24 09:39:35 [*] Waiting for messages. To exit press CTRL+C

  使用Producer来发送消息:

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run producer_exchange_topic_logs.go "kern.critical" "A critical kernel error"
  2. 2016/07/24 09:56:33 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 09:56:33 got Connection, getting Channel
  4. 2016/07/24 09:56:33 got Channel, declaring "topic" Exchange ("test-idoall-exchange-topic-logs")
  5. 2016/07/24 09:56:33 declared queue, publishing 23B body ("A critical kernel error")
  6. 2016/07/24 09:56:33 [x] Sent A critical kernel error
  7. 2016/07/24 09:56:33 published 23B OK

5.7、远程调用RPC

  之前的实例都是通过一个或多个Consumer来订阅消息,如果我们需要在远程机器上运行一个函数,来等待结果呢?这是一个不同的场景,例如做云计算。

  AMQP协议预定义了14个属性,大多数我们都很少用到,以下几个是比较常用的。

  • persistent:消息持久性
  • content_type:用来描述编码的MIME类型
  • reply_to:回调queue的名字
  • correlation_id:将远程RPC请求,进行关联的唯一标识

correlation_id

  如果为每个RPC的请求创建一个queue效率是非常低的,正常发送到queue的一个Message,它不知道是从哪里发过来的,而correlation_id属性的存在就是为每个请求设置一个唯一值,在回调接收消息的时候,也会带回这个属性进行匹配,如果不匹配,这个消息就不会被处理。

  接下来我们将使用RabbitMQ搭建一个RPC系统:一个客户端和一个可扩展的RPC服务器,RPC的工作流程如下:

  • 客户端启动时,创建一个匿名的exclusive callback queue
  • 客户端发送请求时,要带两个属性reply_to(设置回调的queue)和correlation_id(唯一标识)
  • 将请求发送到一个RPC queue
  • RPC的server端 ,一直在等待请求,当消息到达时会对过reply_to回复到指定的queue
  • 客户端在等queue从server的回调,检查 correlation_id是否一致,如果和请求时发送的一致,则做其他响应。

idoall.org

  rpc_server.go(服务端代码):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "strconv"
  6. "github.com/streadway/amqp"
  7. )
  8. const (
  9. //AMQP URI
  10. uri = "amqp://guest:guest@localhost:5672/"
  11. //Durable AMQP queue name
  12. queueName = "rpc-queue"
  13. )
  14. //如果存在错误,则输出
  15. func failOnError(err error, msg string) {
  16. if err != nil {
  17. log.Fatalf("%s: %s", msg, err)
  18. panic(fmt.Sprintf("%s: %s", msg, err))
  19. }
  20. }
  21. func main(){
  22. //调用发布消息函数
  23. publish(uri, queueName)
  24. }
  25. //发布者的方法
  26. //
  27. //@amqpURI, amqp的地址
  28. //@queue, queue的名称
  29. func publish(amqpURI string, queue string){
  30. //建立连接
  31. log.Printf("dialing %q", amqpURI)
  32. connection, err := amqp.Dial(amqpURI)
  33. failOnError(err, "Failed to connect to RabbitMQ")
  34. defer connection.Close()
  35. //创建一个Channel
  36. log.Printf("got Connection, getting Channel")
  37. channel, err := connection.Channel()
  38. failOnError(err, "Failed to open a channel")
  39. defer channel.Close()
  40. //创建一个queue
  41. log.Printf("got queue, declaring %q", queue)
  42. q,err := channel.QueueDeclare(
  43. queue, // name
  44. false, // durable
  45. false, // delete when usused
  46. false, // exclusive
  47. false, // no-wait
  48. nil, // arguments
  49. )
  50. failOnError(err, "Failed to declare a queue")
  51. //均衡处理,每次处理一条消息
  52. err = channel.Qos(
  53. 1, // prefetch count
  54. 0, // prefetch size
  55. false, // global
  56. )
  57. failOnError(err, "Failed to set QoS")
  58. //订阅一个消息
  59. //log.Printf("Queue bound to Exchange, starting Consume")
  60. msgs, err := channel.Consume(
  61. q.Name, // queue
  62. "", // consumer
  63. false, // auto-ack
  64. false, // exclusive
  65. false, // no-local
  66. false, // no-wait
  67. nil, // args
  68. )
  69. failOnError(err, "Failed to register a consumer")
  70. forever := make(chan bool)
  71. // 发布消息
  72. go func() {
  73. for d := range msgs {
  74. n, err := strconv.Atoi(string(d.Body))
  75. failOnError(err, "Failed to convert body to integer")
  76. log.Printf(" [.] server端接收到的数据是 (%d)", n)
  77. response := n*2
  78. err = channel.Publish(
  79. "", // exchange
  80. d.ReplyTo, // routing key
  81. false, // mandatory
  82. false, // immediate
  83. amqp.Publishing{
  84. ContentType: "text/plain",
  85. CorrelationId: d.CorrelationId,
  86. Body: []byte(strconv.Itoa(response)),
  87. })
  88. failOnError(err, "Failed to publish a message")
  89. d.Ack(false)
  90. }
  91. }()
  92. log.Printf(" [*] Awaiting RPC requests")
  93. //没有写入数据,一直等待读,阻塞当前线程,目的是让线程不退出
  94. <-forever
  95. }

  consumer_exchange_topic_logs.go(消息消费者):

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "math/rand"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/streadway/amqp"
  11. )
  12. const (
  13. //AMQP URI
  14. uri = "amqp://guest:guest@localhost:5672/"
  15. //Durable AMQP exchange name
  16. exchangeName = ""
  17. //Exchange type - direct|fanout|topic|x-custom
  18. queueName = "rpc-queue"
  19. )
  20. //如果存在错误,则输出
  21. func failOnError(err error, msg string) {
  22. if err != nil {
  23. log.Fatalf("%s: %s", msg, err)
  24. panic(fmt.Sprintf("%s: %s", msg, err))
  25. }
  26. }
  27. func randomString(l int) string {
  28. bytes := make([]byte, l)
  29. for i := 0; i < l; i++ {
  30. bytes[i] = byte(randInt(65, 90))
  31. }
  32. return string(bytes)
  33. }
  34. func randInt(min int, max int) int {
  35. return min + rand.Intn(max-min)
  36. }
  37. func bodyFrom(args []string) int {
  38. var s string
  39. if (len(args) < 2) || os.Args[1] == "" {
  40. s = "30"
  41. } else {
  42. s = strings.Join(args[1:], " ")
  43. }
  44. n, err := strconv.Atoi(s)
  45. failOnError(err, "Failed to convert arg to integer")
  46. return n
  47. }
  48. func main(){
  49. rand.Seed(time.Now().UTC().UnixNano())
  50. n := bodyFrom(os.Args)
  51. log.Printf(" [x] 请求的数据是(%d)", n)
  52. res, err := fibonacciRPC(n, uri, exchangeName, queueName)
  53. failOnError(err, "Failed to handle RPC request")
  54. log.Printf(" [.] 计算结果为 %d", res)
  55. }
  56. //RPC client调用方法
  57. //
  58. //@amqpURI, amqp的地址
  59. //@exchange, exchange的名称
  60. //@queue, queue的名称
  61. func fibonacciRPC(n int, amqpURI string, exchange string, queue string) (res int, err error){
  62. //建立连接
  63. log.Printf("dialing %q", amqpURI)
  64. connection, err := amqp.Dial(amqpURI)
  65. failOnError(err, "Failed to connect to RabbitMQ")
  66. defer connection.Close()
  67. //创建一个Channel
  68. log.Printf("got Connection, getting Channel")
  69. channel, err := connection.Channel()
  70. failOnError(err, "Failed to open a channel")
  71. defer channel.Close()
  72. //创建一个queue
  73. log.Printf("got queue, declaring %q", queue)
  74. q,err := channel.QueueDeclare(
  75. "", // name
  76. false, // durable
  77. false, // delete when usused
  78. true, // exclusive
  79. false, // no-wait
  80. nil, // arguments
  81. )
  82. failOnError(err, "Failed to declare a queue")
  83. log.Printf("Queue bound to Exchange, starting Consume")
  84. //订阅消息
  85. msgs, err := channel.Consume(
  86. q.Name, // queue
  87. "", // consumer
  88. true, // auto-ack
  89. false, // exclusive
  90. false, // no-local
  91. false, // no-wait
  92. nil, // args
  93. )
  94. failOnError(err, "Failed to register a consumer")
  95. corrId := randomString(32)
  96. err = channel.Publish(
  97. "", // exchange
  98. queue, // routing key
  99. false, // mandatory
  100. false, // immediate
  101. amqp.Publishing{
  102. ContentType: "text/plain",
  103. CorrelationId: corrId,
  104. ReplyTo: q.Name,
  105. Body: []byte(strconv.Itoa(n)),
  106. })
  107. failOnError(err, "Failed to publish a message")
  108. for d := range msgs {
  109. if corrId == d.CorrelationId {
  110. res, err = strconv.Atoi(string(d.Body))
  111. failOnError(err, "Failed to convert body to integer")
  112. break
  113. }
  114. }
  115. return
  116. }

  查看结果


  Console1(rpc server):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run rpc_server.go
  2. 2016/07/24 11:20:32 dialing "amqp://guest:guest@localhost:5672/"
  3. 2016/07/24 11:20:32 got Connection, getting Channel
  4. 2016/07/24 11:20:32 got queue, declaring "rpc-queue"
  5. 2016/07/24 11:20:32 [*] Awaiting RPC requests

  Console2(rpc client):

  1. lion@node1:~/_code/_rabbitmq/_golang$ go run rpc_client.go 69
  2. 2016/07/24 11:24:37 [x] 请求的数据是(69)
  3. 2016/07/24 11:24:37 dialing "amqp://guest:guest@localhost:5672/"
  4. 2016/07/24 11:24:37 got Connection, getting Channel
  5. 2016/07/24 11:24:37 got queue, declaring "rpc-queue"
  6. 2016/07/24 11:24:37 Queue bound to Exchange, starting Consume
  7. 2016/07/24 11:24:37 [.] 计算结果为 138

  以上只是简单实现了RPC的功能,如果你有复杂的需求,需要根据需求对Server和Client做调整。

6、写在后面

  业界对于消息传输有很多种方案,之前我们也介绍过Kafka,Kafka是Linkedin于2010年12月份开源的消息发布订阅系统,它主要用于处理活跃的流式数据,大数据量的数据处理上。RabbitMQ在吞吐量方面稍逊于kafka,他们的出发点不一样,RabbitMQ支持对消息的可靠的传递,支持事务,不支持批量的操作。

  RabbitMQ的消息应当尽可能的小,并且只用来处理实时且要高可靠性的消息。消费者和生产者的能力尽量对等,否则消息堆积会严重影响RabbitMQ的性能。

  本文实例代码在这里可以下载:点击下载

7、参考资料

http://www.rabbitmq.com/getstarted.html

https://github.com/streadway/amqp

8、FAQ

安装Erlang过程中出现提示configure: error: No curses library functions found

因为缺少缺少ncurses安装包,执行以下命令,即可解决:

  1. lion@node1:~/$ sudo apt-get install libncurses5-dev


0 0