├── .gitignore ├── LICENSE ├── README.md ├── book.json ├── docs ├── AMQP │ └── AMQP_0-9-1_Model_Explained.md ├── index.md ├── installation │ ├── Installing_on_Debian_Ubuntu.md │ └── Platforms_supported_by_RabbitMQ.md └── tutorials_with_python │ ├── [1]Hello_World.md │ ├── [2]Work_Queues.md │ ├── [3]Publish_Subscribe.md │ ├── [4]Routing.md │ ├── [5]Topics.md │ └── [6]RPC.md ├── mkdocs.yml ├── published ├── AMQP │ ├── AMQP_0-9-1_Model_Explained.md │ └── amqp-0-9-1-quickref.md ├── ClientDocumentation │ └── java-api-guide.md ├── README.md ├── SUMMARY.md ├── description.md ├── installation │ ├── Installing_on_Debian_Ubuntu.md │ └── Platforms_supported_by_RabbitMQ.md ├── tutorials_with_csharp │ ├── HelloWorld.md │ ├── Topics.md │ ├── WorkQueue.md │ ├── publish&subscribe.md │ ├── routing.md │ └── rpc.md ├── tutorials_with_golang │ ├── [1]Hello_World.md │ ├── [2]Work_Queues.md │ ├── [3]Publish_Subscribe.md │ ├── [4]Routing.md │ ├── [5]Topics.md │ └── [6]RPC.md └── tutorials_with_python │ ├── [1]Hello_World.md │ ├── [2]Work_Queues.md │ ├── [3]Publish_Subscribe.md │ ├── [4]Routing.md │ ├── [5]Topics.md │ └── [6]RPC.md ├── source ├── AMQP_0-9-1_Quick_Reference.md └── demo_file.md ├── translated ├── ClientDocumentation │ └── java-api-guide.md └── demo_file.md └── tutorials_with_golang ├── [1]Hello_World.html ├── [2]Work_Queues.html ├── [3]Publish_Subscribe.html ├── [4]Routing.html ├── [5]Topics.html └── [6]RPC.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ中文 2 | 3 | 此项目翻译RabbitMQ中文文档及相关中文译文 4 | 5 | ## 查看文档: 6 | 7 | [RabbitMQ中文 文档站](http://rabbitmq.mr-ping.com) 8 | 9 | ## 共同参与: 10 | 11 | 欢迎大家共同参与RabbitMQ官方文档的翻译。([rabbitmq.com](http://rabbitmq.com)) 12 | 13 | ### 翻译工作 14 | 15 | 请将翻译后的markdown文档放置在 `translated` 16 | 目录内,并在文章末尾附上原文连接,校对后会及时发布。 17 | 18 | 或 19 | 20 | 联系我代为发布。 21 | 22 | ### 校对工作 23 | 24 | 1. 请`fork`本库。 25 | 2. 在`translated`目录中领取需要校对的文章 26 | 3. 在文章内首行打上标示,如"校对中",并将打标后的修改提交PR。 27 | 4. 对文章进行校对,并在文章尾行添加校对者署名及链接。 28 | 5. 校对完成后,将标示修改为"校对完成",提交PR等待合并即可。 29 | 30 | 31 | 如果您有希望翻译成中文的文档或文章,可以在Issues中留言并附上连接。 32 | 33 | ## 项目结构 34 | 35 | "*source*" 暂存格式化(markdown)后的文档 36 | "*translated*" 暂存翻译完成及校对完成的文档 37 | "*published*" 存储已发布的文档 38 | 39 | ## 文档格式: 40 | 41 | - 文档的文件名请使用英文 42 | - 发布到此处的文档请统一使用 [Markdown](http://zh.wikipedia.org/wiki/Markdown) 格式。 43 | - Markdown语法介绍可以 [点击此处](http://wowubuntu.com/markdown/) 查看。 44 | - Markdown编辑器推荐: 45 | - 在线编辑器:[Markable](http://markable.in/) 46 | - 本地编辑器:[typora](https://typora.io) 47 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": "=3.0.0-pre.14", 3 | "author": "Ping ", 4 | "description": "RabbitMQ in Chinese", 5 | "root": "./published", 6 | "generator": "site", 7 | "links": { 8 | "sharing": { 9 | "all": null, 10 | "facebook": null, 11 | "google": null, 12 | "twitter": null, 13 | "weibo": null 14 | }, 15 | "sidebar": { 16 | "瓶先生": "http://mr-ping.com" 17 | } 18 | }, 19 | "plugins": [ 20 | "ga", 21 | "expandable-chapters-interactive", 22 | "-lunr", "-search", "search-plus", 23 | "page-toc" 24 | ], 25 | "pluginsConfig": { 26 | "ga": { 27 | "token": "UA-120619814-1" 28 | }, 29 | "page-toc": { 30 | "selector": ".markdown-section h1, .markdown-section h2, .markdown-section h3, .markdown-section h4", 31 | "position": "before-first", 32 | "showByDefault": true 33 | } 34 | }, 35 | "title": "RabbitMQ in Chinese", 36 | "variables": {} 37 | } 38 | -------------------------------------------------------------------------------- /docs/AMQP/AMQP_0-9-1_Model_Explained.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/installation/Installing_on_Debian_Ubuntu.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/installation/Platforms_supported_by_RabbitMQ.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/tutorials_with_python/[1]Hello_World.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/tutorials_with_python/[2]Work_Queues.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/tutorials_with_python/[3]Publish_Subscribe.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/tutorials_with_python/[4]Routing.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/tutorials_with_python/[5]Topics.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /docs/tutorials_with_python/[6]RPC.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 已迁移至新站点 2 | 3 | 此处不再提供服务,请异步 [rabbitmq.mr-ping.com][1] 进行浏览。 4 | 5 | [1]: http://rabbitmq.mr-ping.com 6 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: RabbitMQ 中文 2 | -------------------------------------------------------------------------------- /published/AMQP/AMQP_0-9-1_Model_Explained.md: -------------------------------------------------------------------------------- 1 | >原文:[AMQP 0-9-1 Model Explained](https://www.rabbitmq.com/tutorials/amqp-concepts.html) 2 | >翻译:[Ping](http://mr-ping.com) 3 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 4 | 5 | #AMQP 0-9-1 简介 6 | 7 | 关于本指南 8 | 9 | 本指南介绍了RabbitMQ所使用的 AMQP 0-9-1版本。[原始版本](http://bit.ly/amqp-model-explained)由[Michael Klishin](http://twitter.com/michaelklishin)贡献,[Chris Duncan](https://twitter.com/celldee)编辑。 10 | 11 | ## AMQP 0-9-1 和 AMQP 模型高阶概述 12 | 13 | ###AMQP是什么 14 | 15 | AMQP(高级消息队列协议)是一个网络协议。它支持符合要求的客户端应用(application)和消息中间件代理(messaging middleware broker)之间进行通信。 16 | 17 | ###消息代理和他们所扮演的角色 18 | 19 | 20 | 消息代理(message brokers)从发布者(publishers)亦称生产者(producers)那儿接收消息,并根据既定的路由规则把接收到的消息发送给处理消息的消费者(consumers)。 21 | 22 | 由于AMQP是一个网络协议,所以这个过程中的发布者,消费者,消息代理 可以存在于不同的设备上。 23 | ###AMQP 0-9-1 模型简介 24 | 25 | AMQP 0-9-1的工作过程如下图:消息(message)被发布者(publisher)发送给交换机(exchange),交换机常常被比喻成邮局或者邮箱。然后交换机将收到的消息根据路由规则分发给绑定的队列(queue)。最后AMQP代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。 26 | 27 | ![enter image description here](https://www.rabbitmq.com/img/tutorials/intro/hello-world-example-routing.png) 28 | 29 | 发布者(publisher)发布消息时可以给消息指定各种消息属性(message meta-data)。有些属性有可能会被消息代理(brokers)使用,然而其他的属性则是完全不透明的,它们只能被接收消息的应用所使用。 30 | 31 | 从安全角度考虑,网络是不可靠的,接收消息的应用也有可能在处理消息的时候失败。基于此原因,AMQP模块包含了一个消息确认(message acknowledgements)的概念:当一个消息从队列中投递给消费者后(consumer),消费者会通知一下消息代理(broker),这个可以是自动的也可以由处理消息的应用的开发者执行。当“消息确认”被启用的时候,消息代理不会完全将消息从队列中删除,直到它收到来自消费者的确认回执(acknowledgement)。 32 | 33 | 在某些情况下,例如当一个消息无法被成功路由时,消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了延期操作,消息会被放入一个所谓的死信队列中。此时,消息发布者可以选择某些参数来处理这些特殊情况。 34 | 35 | 队列,交换机和绑定统称为AMQP实体(AMQP entities)。 36 | 37 | ###AMQP是一个可编程的协议 38 | 39 | AMQP 0-9-1是一个可编程协议,某种意义上说AMQP的实体和路由规则是由应用本身定义的,而不是由消息代理定义。包括像声明队列和交换机,定义他们之间的绑定,订阅队列等等关于协议本身的操作。 40 | 41 | 这虽然能让开发人员自由发挥,但也需要他们注意潜在的定义冲突。当然这在实践中很少会发生,如果发生,会以配置错误(misconfiguration)的形式表现出来。 42 | 43 | 应用程序(Applications)声明AMQP实体,定义需要的路由方案,或者删除不再需要的AMQP实体。 44 | 45 | ##交换机和交换机类型 46 | 47 | 交换机是用来发送消息的AMQP实体。交换机拿到一个消息之后将它路由给一个或零个队列。它使用哪种路由算法是由交换机类型和被称作绑定(bindings)的规则所决定的。AMQP 0-9-1的代理提供了四种交换机 48 | 49 | | Name(交换机类型) | Default pre-declared names(预声明的默认名称) | 50 | |--------|--------| 51 | | Direct exchange(直连交换机) | (Empty string) and amq.direct | 52 | | Fanout exchange(扇型交换机) | amq.fanout | 53 | | Topic exchange(主题交换机) | amq.topic | 54 | | Headers exchange(头交换机) | amq.match (and amq.headers in RabbitMQ) | 55 | 56 | 57 | 除交换机类型外,在声明交换机时还可以附带许多其他的属性,其中最重要的几个分别是: 58 | 59 | - Name 60 | - Durability (消息代理重启后,交换机是否还存在) 61 | - Auto-delete (当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它) 62 | - Arguments(依赖代理本身) 63 | 64 | 65 | 交换机可以有两个状态:持久(durable)、暂存(transient)。持久化的交换机会在消息代理(broker)重启后依旧存在,而暂存的交换机则不会(它们需要在代理再次上线后重新被声明)。然而并不是所有的应用场景都需要持久化的交换机。 66 | 67 | ###默认交换机 68 | 69 | 默认交换机(default exchange)实际上是一个由消息代理预先声明好的没有名字(名字为空字符串)的直连交换机(direct exchange)。它有一个特殊的属性使得它对于简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。 70 | 71 | 举个栗子:当你声明了一个名为"search-indexing-online"的队列,AMQP代理会自动将其绑定到默认交换机上,绑定(binding)的路由键名称也是为"search-indexing-online"。因此,当携带着名为"search-indexing-online"的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为"search-indexing-online"的队列中。换句话说,默认交换机看起来貌似能够直接将消息投递给队列,尽管技术上并没有做相关的操作。 72 | 73 | ###直连交换机 74 | 75 | 76 | 直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应队列的。直连交换机用来处理消息的单播路由(unicast routing)(尽管它也可以处理多播路由)。下边介绍它是如何工作的: 77 | 78 | - 将一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key) 79 | - 当一个携带着路由键为`R`的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为`R`的队列。 80 | 81 | 直连交换机经常用来循环分发任务给多个工作者(workers)。当这样做的时候,我们需要明白一点,在AMQP 0-9-1中,消息的负载均衡是发生在消费者(consumer)之间的,而不是队列(queue)之间。 82 | 83 | 直连型交换机图例: 84 | ![enter image description here](https://www.rabbitmq.com/img/tutorials/intro/exchange-direct.png) 85 | 86 | ###扇型交换机 87 | 88 | 扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列,而不理会绑定的路由键。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的拷贝分别发送给这所有的N个队列。扇型用来交换机处理消息的广播路由(broadcast routing)。 89 | 90 | 因为扇型交换机投递消息的拷贝到所有绑定到它的队列,所以他的应用案例都极其相似: 91 | 92 | - 大规模多用户在线(MMO)游戏可以使用它来处理排行榜更新等全局事件 93 | - 体育新闻网站可以用它来近乎实时地将比分更新分发给移动客户端 94 | - 分发系统使用它来广播各种状态和配置更新 95 | - 在群聊的时候,它被用来分发消息给参与群聊的用户。(AMQP没有内置presence的概念,因此XMPP可能会是个更好的选择) 96 | 97 | 扇型交换机图例: 98 | ![enter image description here](https://www.rabbitmq.com/img/tutorials/intro/exchange-fanout.png) 99 | 100 | ###主题交换机 101 | 102 | 主题交换机(topic exchanges)通过对消息的路由键和队列到交换机的绑定模式之间的匹配,将消息路由给一个或多个队列。主题交换机经常用来实现各种分发/订阅模式及其变种。主题交换机通常用来实现消息的多播路由(multicast routing)。 103 | 104 | 主题交换机拥有非常广泛的用户案例。无论何时,当一个问题涉及到那些想要有针对性的选择需要接收消息的 多消费者/多应用(multiple consumers/applications) 的时候,主题交换机都可以被列入考虑范围。 105 | 106 | 使用案例: 107 | 108 | - 分发有关于特定地理位置的数据,例如销售点 109 | - 由多个工作者(workers)完成的后台任务,每个工作者负责处理某些特定的任务 110 | - 股票价格更新(以及其他类型的金融数据更新) 111 | - 涉及到分类或者标签的新闻更新(例如,针对特定的运动项目或者队伍) 112 | - 云端的不同种类服务的协调 113 | - 分布式架构/基于系统的软件封装,其中每个构建者仅能处理一个特定的架构或者系统。 114 | 115 | ###头交换机 116 | 117 | 有时消息的路由操作会涉及到多个属性,此时使用消息头就比用路由键更容易表达,头交换机(headers exchange)就是为此而生的。头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。 118 | 119 | 我们可以绑定一个队列到头交换机上,并给他们之间的绑定使用多个用于匹配的头(header)。这个案例中,消息代理得从应用开发者那儿取到更多一段信息,换句话说,它需要考虑某条消息(message)是需要部分匹配还是全部匹配。上边说的“更多一段消息”就是"x-match"参数。当"x-match"设置为“any”时,消息头的任意一个值被匹配就可以满足条件,而当"x-match"设置为“all”的时候,就需要消息头的所有值都匹配成功。 120 | 121 | 头交换机可以视为直连交换机的另一种表现形式。头交换机能够像直连交换机一样工作,不同之处在于头交换机的路由规则是建立在头属性值之上,而不是路由键。路由键必须是一个字符串,而头属性值则没有这个约束,它们甚至可以是整数或者哈希值(字典)等。 122 | 123 | ##队列 124 | 125 | AMQP中的队列(queue)跟其他消息队列或任务队列中的队列是很相似的:它们存储着即将被应用消费掉的消息。队列跟交换机共享某些属性,但是队列也有一些另外的属性。 126 | 127 | - Name 128 | - Durable(消息代理重启后,队列依旧存在) 129 | - Exclusive(只被一个连接(connection)使用,而且当连接关闭后队列即被删除) 130 | - Auto-delete(当最后一个消费者退订后即被删除) 131 | - Arguments(一些消息代理用他来完成类似与TTL的某些额外功能) 132 | 133 | 队列在声明(declare)后才能被使用。如果一个队列尚不存在,声明一个队列会创建它。如果声明的队列已经存在,并且属性完全相同,那么此次声明不会对原有队列产生任何影响。如果声明中的属性与已存在队列的属性有差异,那么一个错误代码为406的通道级异常就会被抛出。 134 | 135 | ###队列名称 136 | 137 | 队列的名字可以由应用(application)来取,也可以让消息代理(broker)直接生成一个。队列的名字可以是最多255字节的一个utf-8字符串。若希望AMQP消息代理生成队列名,需要给队列的name参数赋值一个空字符串:在同一个通道(channel)的后续的方法(method)中,我们可以使用空字符串来表示之前生成的队列名称。之所以之后的方法可以获取正确的队列名是因为通道可以默默地记住消息代理最后一次生成的队列名称。 138 | 139 | 以"amq."开始的队列名称被预留做消息代理内部使用。如果试图在队列声明时打破这一规则的话,一个通道级的403 (ACCESS_REFUSED)错误会被抛出。 140 | 141 | ###队列持久化 142 | 143 | 持久化队列(Durable queues)会被存储在磁盘上,当消息代理(broker)重启的时候,它依旧存在。没有被持久化的队列称作暂存队列(Transient queues)。并不是所有的场景和案例都需要将队列持久化。 144 | 145 | 持久化的队列并不会使得路由到它的消息也具有持久性。倘若消息代理挂掉了,重新启动,那么在重启的过程中持久化队列会被重新声明,无论怎样,只有经过持久化的消息才能被重新恢复。 146 | 147 | ##绑定 148 | 149 | 绑定(Binding)是交换机(exchange)将消息(message)路由给队列(queue)所需遵循的规则。如果要指示交换机“E”将消息路由给队列“Q”,那么“Q”就需要与“E”进行绑定。绑定操作需要定义一个可选的路由键(routing key)属性给某些类型的交换机。路由键的意义在于从发送给交换机的众多消息中选择出某些消息,将其路由给绑定的队列。 150 | 151 | 打个比方: 152 | 153 | - 队列(queue)是我们想要去的位于纽约的目的地 154 | - 交换机(exchange)是JFK机场 155 | - 绑定(binding)就是JFK机场到目的地的路线。能够到达目的地的路线可以是一条或者多条 156 | 157 | 拥有了交换机这个中间层,很多由发布者直接到队列难以实现的路由方案能够得以实现,并且避免了应用开发者的许多重复劳动。 158 | 159 | 如果AMQP的消息无法路由到队列(例如,发送到的交换机没有绑定队列),消息会被就地销毁或者返还给发布者。如何处理取决于发布者设置的消息属性。 160 | 161 | ##消费者 162 | 163 | 消息如果只是存储在队列里是没有任何用处的。被应用消费掉,消息的价值才能够体现。在AMQP 0-9-1 模型中,有两种途径可以达到此目的: 164 | 165 | - 将消息投递给应用 ("push API") 166 | - 应用根据需要主动获取消息 ("pull API") 167 | 168 | 使用push API,应用(application)需要明确表示出它在某个特定队列里所感兴趣的,想要消费的消息。如是,我们可以说应用注册了一个消费者,或者说订阅了一个队列。一个队列可以注册多个消费者,也可以注册一个独享的消费者(当独享消费者存在时,其他消费者即被排除在外)。 169 | 170 | 每个消费者(订阅者)都有一个叫做消费者标签的标识符。它可以被用来退订消息。消费者标签实际上是一个字符串。 171 | 172 | ###消息确认 173 | 174 | 消费者应用(Consumer applications) - 用来接受和处理消息的应用 - 在处理消息的时候偶尔会失败或者有时会直接崩溃掉。而且网络原因也有可能引起各种问题。这就给我们出了个难题,AMQP代理在什么时候删除消息才是正确的?AMQP 0-9-1 规范给我们两种建议: 175 | 176 | - 当消息代理(broker)将消息发送给应用后立即删除。(使用AMQP方法:basic.deliver或basic.get-ok) 177 | - 待应用(application)发送一个确认回执(acknowledgement)后再删除消息。(使用AMQP方法:basic.ack) 178 | 179 | 前者被称作自动确认模式(automatic acknowledgement model),后者被称作显式确认模式(explicit acknowledgement model)。在显式模式下,由消费者应用来选择什么时候发送确认回执(acknowledgement)。应用可以在收到消息后立即发送,或将未处理的消息存储后发送,或等到消息被处理完毕后再发送确认回执(例如,成功获取一个网页内容并将其存储之后)。 180 | 181 | 如果一个消费者在尚未发送确认回执的情况下挂掉了,那AMQP代理会将消息重新投递给另一个消费者。如果当时没有可用的消费者了,消息代理会死等下一个注册到此队列的消费者,然后再次尝试投递。 182 | 183 | ###拒绝消息 184 | 185 | 当一个消费者接收到某条消息后,处理过程有可能成功,有可能失败。应用可以向消息代理表明,本条消息由于“拒绝消息(Rejecting Messages)”的原因处理失败了(或者未能在此时完成)。当拒绝某条消息时,应用可以告诉消息代理如何处理这条消息——销毁它或者重新放入队列。当此队列只有一个消费者时,请确认不要由于拒绝消息并且选择了重新放入队列的行为而引起消息在同一个消费者身上无限循环的情况发生。 186 | 187 | ###Negative Acknowledgements 188 | 189 | 在AMQP中,basic.reject方法用来执行拒绝消息的操作。但basic.reject有个限制:你不能使用它决绝多个带有确认回执(acknowledgements)的消息。但是如果你使用的是RabbitMQ,那么你可以使用被称作negative acknowledgements(也叫nacks)的AMQP 0-9-1扩展来解决这个问题。更多的信息请参考[帮助页面](https://www.rabbitmq.com/nack.html) 190 | 191 | ###预取消息 192 | 193 | 在多个消费者共享一个队列的案例中,明确指定在收到下一个确认回执前每个消费者一次可以接受多少条消息是非常有用的。这可以在试图批量发布消息的时候起到简单的负载均衡和提高消息吞吐量的作用。For example, if a producing application sends messages every minute because of the nature of the work it is doing.(???例如,如果生产应用每分钟才发送一条消息,这说明处理工作尚在运行。) 194 | 195 | 注意,RabbitMQ只支持通道级的预取计数,而不是连接级的或者基于大小的预取。 196 | 197 | ##消息属性和有效载荷(消息主体) 198 | 199 | AMQP模型中的消息(Message)对象是带有属性(Attributes)的。有些属性及其常见,以至于AMQP 0-9-1 明确的定义了它们,并且应用开发者们无需费心思思考这些属性名字所代表的具体含义。例如: 200 | 201 | - Content type(内容类型) 202 | - Content encoding(内容编码) 203 | - Routing key(路由键) 204 | - Delivery mode (persistent or not) 205 | 投递模式(持久化 或 非持久化) 206 | - Message priority(消息优先权) 207 | - Message publishing timestamp(消息发布的时间戳) 208 | - Expiration period(消息有效期) 209 | - Publisher application id(发布应用的ID) 210 | 211 | 有些属性是被AMQP代理所使用的,但是大多数是开放给接收它们的应用解释器用的。有些属性是可选的也被称作消息头(headers)。他们跟HTTP协议的X-Headers很相似。消息属性需要在消息被发布的时候定义。 212 | 213 | AMQP的消息除属性外,也含有一个有效载荷 - Payload(消息实际携带的数据),它被AMQP代理当作不透明的字节数组来对待。消息代理不会检查或者修改有效载荷。消息可以只包含属性而不携带有效载荷。它通常会使用类似JSON这种序列化的格式数据,为了节省,协议缓冲器和MessagePack将结构化数据序列化,以便以消息的有效载荷的形式发布。AMQP及其同行者们通常使用"content-type" 和 "content-encoding" 这两个字段来与消息沟通进行有效载荷的辨识工作,但这仅仅是基于约定而已。 214 | 215 | 消息能够以持久化的方式发布,AMQP代理会将此消息存储在磁盘上。如果服务器重启,系统会确认收到的持久化消息未丢失。简单地将消息发送给一个持久化的交换机或者路由给一个持久化的队列,并不会使得此消息具有持久化性质:它完全取决与消息本身的持久模式(persistence mode)。将消息以持久化方式发布时,会对性能造成一定的影响(就像数据库操作一样,健壮性的存在必定造成一些性能牺牲)。 216 | 217 | ##消息确认 218 | 219 | 由于网络的不确定性和应用失败的可能性,处理确认回执(acknowledgement)就变的十分重要。有时我们确认消费者收到消息就可以了,有时确认回执意味着消息已被验证并且处理完毕,例如对某些数据已经验证完毕并且进行了数据存储或者索引操作。 220 | 221 | 这种情形很常见,所以 AMQP 0-9-1 内置了一个功能叫做 消息确认(message acknowledgements),消费者用它来确认消息已经被接收或者处理。如果一个应用崩溃掉(此时连接会断掉,所以AMQP代理亦会得知),而且消息的确认回执功能已经被开启,但是消息代理尚未获得确认回执,那么消息会被从新放入队列(并且在还有还有其他消费者存在于此队列的前提下,立即投递给另外一个消费者)。 222 | 223 | 协议内置的消息确认功能将帮助开发者建立强大的软件。 224 | 225 | ##AMQP 0-9-1 方法 226 | 227 | AMQP 0-9-1由许多方法(methods)构成。方法即是操作,这跟面向对象编程中的方法没半毛钱关系。AMQP的方法被分组在类(class)中。这里的类仅仅是对AMQP方法的逻辑分组而已。在 [AMQP 0-9-1参考](https://www.rabbitmq.com/amqp-0-9-1-reference.html) 中有对AMQP方法的详细介绍。 228 | 229 | 让我们来看看交换机类,有一组方法被关联到了交换机的操作上。这些方法如下所示: 230 | 231 | - exchange.declare 232 | - exchange.declare-ok 233 | - exchange.delete 234 | - exchange.delete-ok 235 | 236 | (请注意,RabbitMQ网站参考中包含了特用于RabbitMQ的交换机类的扩展,这里我们不对其进行讨论) 237 | 238 | 以上的操作来自逻辑上的配对:exchange.declare 和 exchange.declare-ok,exchange.delete 和 exchange.delete-ok. 这些操作分为“请求 - requests”(由客户端发送)和“响应 - responses”(由代理发送,用来回应之前提到的“请求”操作)。 239 | 240 | 如下的例子:客户端要求消息代理使用exchange.declare方法声明一个新的交换机: 241 | ![enter image description here](https://www.rabbitmq.com/img/tutorials/intro/exchange-declare.png) 242 | 243 | 如上图所示,exchange.declare方法携带了好几个参数。这些参数可以允许客户端指定交换机名称、类型、是否持久化等等。 244 | 245 | 操作成功后,消息代理使用exchange.declare-ok方法进行回应: 246 | ![enter image description here](https://www.rabbitmq.com/img/tutorials/intro/exchange-declare-ok.png) 247 | 248 | exchange.declare-ok方法除了通道号之外没有携带任何其他参数(通道-channel 会在本指南稍后章节进行介绍)。 249 | 250 | AMQP队列类的配对方法 - queue.declare方法 和 queue.declare-ok有着与其他配对方法非常相似的一系列事件: 251 | ![enter image description here](https://www.rabbitmq.com/img/tutorials/intro/queue-declare.png) 252 | 253 | ![enter image description here](https://www.rabbitmq.com/img/tutorials/intro/queue-declare-ok.png) 254 | 255 | 不是所有的AMQP方法都有与其配对的“另一半”。许多(basic.publish是最被广泛使用的)都没有相对应的“响应”方法,另外一些(如basic.get)有着一种以上与之对应的“响应”方法。 256 | 257 | ##连接 258 | 259 | AMQP连接通常是长连接。AMQP是一个使用TCP提供可靠投递的应用层协议。AMQP使用认证机制并且提供TLS(SSL)保护。当一个应用不再需要连接到AMQP代理的时候,需要优雅的释放掉AMQP连接,而不是直接将TCP连接关闭。 260 | 261 | ##通道 262 | 263 | 有些应用需要与AMQP代理建立多个连接。无论怎样,同时开启多个TCP连接都是不合适的,因为这样做会消耗掉过多的系统资源并且使得防火墙的配置更加困难。AMQP 0-9-1提供了通道(channels)来处理多连接,可以把通道理解成共享一个TCP连接的多个轻量化连接。 264 | 265 | 在涉及多线程/进程的应用中,为每个线程/进程开启一个通道(channel)是很常见的,并且这些通道不能被线程/进程共享。 266 | 267 | 一个特定通道上的通讯与其他通道上的通讯是完全隔离的,因此每个AMQP方法都需要携带一个通道号,这样客户端就可以指定此方法是为哪个通道准备的。 268 | 269 | ##虚拟主机 270 | 271 | 为了在一个单独的代理上实现多个隔离的环境(用户、用户组、交换机、队列 等),AMQP提供了一个虚拟主机(virtual hosts - vhosts)的概念。这跟Web servers虚拟主机概念非常相似,这为AMQP实体提供了完全隔离的环境。当连接被建立的时候,AMQP客户端来指定使用哪个虚拟主机。 272 | 273 | ##AMQP是可扩展的 274 | 275 | AMQP 0-9-1 拥有多个扩展点: 276 | 277 | - 定制化交换机类型 可以让开发者们实现一些开箱即用的交换机类型尚未很好覆盖的路由方案。例如 geodata-based routing。 278 | - 交换机和队列的声明中可以包含一些消息代理能够用到的额外属性。例如RabbitMQ中的per-queue message TTL即是使用该方式实现。 279 | - 特定消息代理的协议扩展。例如RabbitMQ所实现的扩展。 280 | - 新的 AMQP 0-9-1 方法类可被引入。 281 | - 消息代理可以被其他的插件扩展,例如RabbitMQ的管理前端 和 已经被插件化的HTTP API。 282 | 283 | 这些特性使得AMQP 0-9-1模型更加灵活,并且能够适用于解决更加宽泛的问题。 284 | 285 | ##AMQP 0-9-1 客户端生态系统 286 | 287 | AMQP 0-9-1 拥有众多的适用于各种流行语言和框架的客户端。其中一部分严格遵循AMQP规范,提供AMQP方法的实现。另一部分提供了额外的技术,方便使用的方法和抽象。有些客户端是异步的(非阻塞的),有些是同步的(阻塞的),有些将这两者同时实现。有些客户端支持“供应商的特定扩展”(例如RabbitMQ的特定扩展)。 288 | 289 | 因为AMQP的主要目标之一就是实现交互性,所以对于开发者来讲,了解协议的操作方法而不是只停留在弄懂特定客户端的库就显得十分重要。这样一来,开发者使用不同类型的库与协议进行沟通时就会容易的多。 290 | -------------------------------------------------------------------------------- /published/README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ 中文文档 2 | 3 | ![CC BY-SA](https://licensebuttons.net/l/by-sa/3.0/88x31.png) 4 | 5 | 欢迎大家在 [GitHub项目](https://github.com/mr-ping/RabbitMQ_into_Chinese) 中参与文档翻译。 6 | -------------------------------------------------------------------------------- /published/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [RabbitMQ中文文档](README.md) 4 | 5 | * RabbitMQ简介 6 | * [RabbitMQ能为你做些什么?](description.md) 7 | 8 | * 安装 9 | * [在Debian及Ubuntu系统上进行安装](installation/Installing_on_Debian_Ubuntu.md) 10 | * [RabbitMQ所支持的平台](installation/Platforms_supported_by_RabbitMQ.md) 11 | 12 | * AMQP协议 13 | * [AMQP 0.9.1 模型解析](AMQP/AMQP_0-9-1_Model_Explained.md) 14 | * [AMQP 0.9.1 快速参考指南](AMQP/amqp-0-9-1-quickref.md) 15 | 16 | * 客户端文档 17 | * [Java客户端指南](ClientDocumentation/java-api-guide.md) 18 | 19 | * 应用教程 20 | - Python版 21 | - [Hello World](tutorials_with_python/[1]Hello_World.md) 22 | - [工作队列](tutorials_with_python/[2]Work_Queues.md) 23 | - [发布/订阅](tutorials_with_python/[3]Publish_Subscribe.md) 24 | - [路由](tutorials_with_python/[4]Routing.md) 25 | - [主题交换机](tutorials_with_python/[5]Topics.md) 26 | - [远程过程调用](tutorials_with_python/[6]RPC.md) 27 | - C#版 28 | - [Hello World](tutorials_with_csharp/HelloWorld.md) 29 | - [工作队列](tutorials_with_csharp/WorkQueue.md) 30 | - [发布/订阅](tutorials_with_csharp/publish&subscribe.md) 31 | - [路由](tutorials_with_csharp/routing.md) 32 | - [主题交换机](tutorials_with_csharp/Topics.md) 33 | - [远程过程调用](tutorials_with_csharp/rpc.md) 34 | - golang版 35 | - [Hello World](tutorials_with_golang/[1]Hello_World.md) 36 | - [工作队列](tutorials_with_golang/[2]Work_Queues.md) 37 | - [发布/订阅](tutorials_with_golang/[3]Publish_Subscribe.md) 38 | - [路由](tutorials_with_golang/[4]Routing.md) 39 | - [主题交换机](tutorials_with_golang/[5]Topics.md) 40 | - [远程过程调用](tutorials_with_golang/[6]RPC.md) 41 | -------------------------------------------------------------------------------- /published/description.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ能为你做些什么? 2 | 3 | **消息系统允许软件、应用相互连接和扩展.这些应用可以相互链接起来组成一个更大的应用,或者将用户设备和数据进行连接.消息系统通过将消息的发送和接收分离来实现应用程序的异步和解偶.** 4 | 5 | 或许你正在考虑进行数据投递,非阻塞操作或推送通知。或许你想要实现发布/订阅,异步处理,或者工作队列。所有这些都可以通过消息系统实现。 6 | 7 | RabbitMQ是一个消息代理 - 一个消息系统的媒介。它可以为你的应用提供一个通用的消息发送和接收平台,并且保证消息在传输过程中的安全。 8 | 9 | ## 技术亮点 10 | 11 | ### 可靠性 12 | 13 | RabbitMQ提供了多种技术可以让你在性能和可靠性之间进行权衡。这些技术包括持久性机制、投递确认、发布者证实和高可用性机制。 14 | 15 | ### 灵活的路由 16 | 17 | 消息在到达队列前是通过交换机进行路由的。RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做RabbitMQ的插件来使用。 18 | 19 | ### 集群 20 | 在相同局域网中的多个RabbitMQ服务器可以聚合在一起,作为一个独立的逻辑代理来使用。 21 | 22 | ### 联合 23 | 对于服务器来说,它比集群需要更多的松散和非可靠链接。为此RabbitMQ提供了联合模型。 24 | 25 | ### 高可用的队列 26 | 在同一个集群里,队列可以被镜像到多个机器中,以确保当其中某些硬件出现故障后,你的消息仍然安全。 27 | 28 | ### 多协议 29 | RabbitMQ 支持多种消息协议的消息传递。 30 | 31 | ### 广泛的客户端 32 | 只要是你能想到的编程语言几乎都有与其相适配的RabbitMQ客户端。 33 | 34 | ### 可视化管理工具 35 | RabbitMQ附带了一个易于使用的可视化管理工具,它可以帮助你监控消息代理的每一个环节。 36 | 37 | ### 追踪 38 | 如果你的消息系统有异常行为,RabbitMQ还提供了追踪的支持,让你能够发现问题所在。 39 | 40 | ### 插件系统 41 | RabbitMQ附带了各种各样的插件来对自己进行扩展。你甚至也可以写自己的插件来使用。 42 | 43 | ## 还有什么呢... 44 | 45 | ### 商业支持 46 | 可以提供商业支持,包括培训和咨询。 47 | 48 | ### 大型社区 49 | 围绕着RabbitMQ有一个大型的社区,那儿有着各种各样的客户端、插件、指南等等。快加入我们的邮件列表参与其中吧! 50 | -------------------------------------------------------------------------------- /published/installation/Installing_on_Debian_Ubuntu.md: -------------------------------------------------------------------------------- 1 | >原文:[Installing on Debian / Ubuntu](http://www.rabbitmq.com/install-debian.html) 2 | >状态:校对完成 3 | >翻译:[Ping](http://weibo.com/370321376) 4 | >校对:[Ping](http://weibo.com/370321376) 5 | 6 | ##安装到 Debian / Ubuntu 系统中 7 | 8 | ###下载服服务器 9 | 10 | | Description | Download | | 11 | | ------------------------ | ------------------------------ | --- | 12 | | Packaged as .deb for Debian-based Linux | [rabbitmq-server_3.4.3-1_all.deb][0] |([Signature][5])| 13 | 14 | 自Debian since 6.0 (squeeze) 和 Ubuntu 9.04 之后,rabbitmq-server就已经被内置其中了。然而这些被包含在内的版本往往过低。所以从我们网站上下载 .deb 文件来安装可以达到更好的效果。查看 [Debian安装包][1] 和 [Ubuntu安装包][2] 来确认适用于指定发行版的可用版本。 15 | 16 | 你可以使用`dpkg`来安装从上边下载来的安装包,也可以使用我们的APT库(下边介绍)。 17 | 18 | 所有的依赖都会被自动安装。 19 | 20 | ###运行RabbitMQ服务器 21 | 22 | ####自定义RabbitMQ环境变量 23 | 24 | 服务器会以默认方式启动。你可以[自定义RabbitMQ的环境][3]。也可以查看[如何配置组件][4]。 25 | 26 | ####开启服务器 27 | 28 | 当RabbitMQ安装完毕的时候服务器就会像后台程序一般运行起来了。作为一个管理员,可以像平常一样在Debian中使用以下命令启动和关闭服务 29 | `invoke-rc.d rabbitmq-server stop/start/etc.` 30 | 31 | 注意:服务器是使用`rabbitmq`这个系统用户来运行的。如果你改变了Mnesia数据库或者日志的位置,那么你必须确认这些文件属于此用户(同时更新系统变量)。 32 | 33 | ###我们的APT库 34 | 35 | 使用我们的APT库: 36 | 37 | - 将以下的行添加到你的 /etc/apt/sources.list 文件中: 38 | 39 | deb http://www.rabbitmq.com/debian/ testing main 40 | 41 | (请注意上边行中的 testing 指的是RabbitMQ发行状态,而不是指特定的Debian发行版。你可以将它使用在Debain的稳定版、测试版、非稳定版本中。对Ubuntu来说也是如此。我们之所以将版本描述为 testing 这个词是为了强调我们会频繁发布一些新的东西。) 42 | 43 | - (可选的)为了避免未签名的错误信息,请使用apt-key(8)命令将我们的[公钥](http://www.rabbitmq.com/rabbitmq-signing-key-public.asc)添加到你的可信任密钥列表中: 44 | 45 | wget http://www.rabbitmq.com/rabbitmq-signing-key-public.asc 46 | sudo apt-key add rabbitmq-signing-key-public.asc 47 | 48 | - 运行 49 | 50 | apt-get update` 51 | 52 | - 像平常一样安装软件包即可;例如 53 | 54 | sudo apt-get install rabbitmq-server 55 | 56 | ###控制系统限制 57 | 58 | 如果要调整系统限制(尤其是打开文件的句柄的数量)的话,可以通过编辑 /etc/default/rabbitmq-server 文件让服务启动的时候调用ulimit,例如: 59 | 60 | ulimit -n 1024 61 | 62 | 这将会设置此服务打开文件句柄的最大数量为1024个(这也是默认设置)。 63 | 64 | ##安全和端口 65 | 66 | SELinux和类似机制或许会通过绑定端口的方式阻止RabbitMQ。当这种情况发生时,RabbitMQ会启动失败。请确认以下的端口是可以被打开的: 67 | 68 | - 4369 (epmd), 25672 (Erlang distribution) 69 | - 5672, 5671 (启用了 或者 未启用 TLS 的 AMQP 0-9-1) 70 | - 15672 (如果管理插件被启用) 71 | - 61613, 61614 (如果 STOMP 被启用) 72 | - 1883, 8883 (如果 MQTT 被启用) 73 | 74 | ##默认用户访问 75 | 76 | 代理会建立一个用户名为“guest”密码为“guest”的用户。未经配置的客户端通常会使用这个凭据。默认情况下,这些凭据只能在链接到本机上的代理时使用,所以在链接到其他设备的代理之前,你需要做一些事情。 77 | 78 | 查看[访问控制](http://www.rabbitmq.com/access-control.html),了解如何新建更多的用户,删除“guest”用户或者给“guest”用户赋予远程访问权限。 79 | 80 | ##管理代理 81 | 82 | 如果想要停止或者查看服务器状态等,你可以调用`rabbitmqctl`(在管理员权限下)。如果没有任何代理在运行,所有的rabbitmqctl命令都会给出“结点未找到”的报告。 83 | 84 | - 调用`rabbitmqctl stop`来关闭服务器。 85 | - 调用`rabbitmqctl status`来查看代理是否运行。 86 | 87 | 更多信息请查看 [rabbitmqctl 信息](http://www.rabbitmq.com/man/rabbitmqctl.1.man.html) 88 | 89 | ###日志 90 | 91 | 服务器的输出被发送到 RABBITMQ_LOG_BASE 目录的 RABBITMQ_NODENAME.log 文件中。一些额外的信息会被写入到 RABBITMQ_NODENAME-sasl.log 文件中。 92 | 93 | 代理总是会把新的信息添加到日志文件尾部,所以完整的日志历史可以被保存下来。 94 | 95 | 你可以使用 logrotate 程序来执行必要的循环和压缩工作,并且你还可以更改它。默认情况下,这个位于 /var/log/rabbitmq 文件中的脚本会每周执行一次。你可以查看 /etc/logrotate.d/rabbitmq-server 来对 logrotate 进行配置。 96 | 97 | 98 | [0]:http://www.rabbitmq.com/releases/rabbitmq-server/v3.4.3/rabbitmq-server_3.4.3-1_all.deb 99 | [1]:http://packages.qa.debian.org/r/rabbitmq-server.html 100 | [2]:https://launchpad.net/ubuntu/+source/rabbitmq-server 101 | [3]:http://www.rabbitmq.com/configure.html#customise-general-unix-environment 102 | [4]:http://www.rabbitmq.com/configure.html#configuration-file 103 | [5]:http://www.rabbitmq.com/releases/rabbitmq-server/v3.4.3/rabbitmq-server_3.4.3-1_all.deb.asc 104 | -------------------------------------------------------------------------------- /published/installation/Platforms_supported_by_RabbitMQ.md: -------------------------------------------------------------------------------- 1 | >原文:[Supported Platforms](http://www.rabbitmq.com/platforms.html) 2 | >状态:校对完成 3 | >翻译:[Ping](http://weibo.com/370321376) 4 | >校对:[Ping](http://weibo.com/370321376) 5 | 6 | ##支持的平台 7 | 8 | 我们的目标是让RabbitMQ运行在尽可能广泛的平台之上。RabbitMQ有着运行在所有Erlang所支持的平台之上的潜力,从嵌入式系统到多核心集群还有基于云端的服务器。 9 | 10 | 以下的平台是Erlang语言所支持的,因此RabbitMQ可以运行其上: 11 | 12 | - Solaris 13 | - BSD 14 | - Linux 15 | - MacOSX 16 | - TRU64 17 | - Windows NT/2000/XP/Vista/Windows 7/Windows 8 18 | - Windows Server 2003/2008/2012 19 | - Windows 95, 98 20 | - VxWorks 21 | 22 | RabbitMQ的开源版本通常被部署在以下的平台上: 23 | 24 | - Ubuntu和其他基于Debian的Linux发行版 25 | - Fedora和其他基于RPM包管理方式的Linux发行版 26 | - openSUSE和衍生的发行版(包括SLES和SLERT) 27 | - Mac OS X 28 | - Windows XP 和 后续版本 29 | 30 | ###Windows 31 | 32 | RabbitMQ会运行在Windows XP及其之后的版本之上(Server 2003, Vista, Windows 7, Windows 8, Server 2008 and Server 2012)。尽管没有经过测试,但它应该也可以在Windows NT 以及 Windows 2000上良好的运行。 33 | 34 | Windows Erlang 虚拟机能够以32位(所有可用版本)和64位(R15B往后)方式使用。将32位虚拟机运行在64位系统上的时候会有一些限制(如地址空间)存在。 35 | 36 | 37 | ###常见的 UNIX 38 | 39 | 尽管没有官方支持,但Erlang和RabbitMQ还是可以运行在大多数系统的POSIX层上,包括Solaris, FreeBSD, NetBSD, OpenBSD等等。 40 | 41 | ###虚拟平台 42 | 43 | RabbitMQ可以运行在物理的或模拟的硬件中。这个特性同样允许将不支持的平台模拟成一个支持的平台来运行RabbitMQ。 44 | 45 | 如果要将RabbitMQ运行在EC2上,点击 [EC2 guide](http://www.rabbitmq.com/ec2.html) 查看更多细节。 46 | 47 | ##商业平台支持 48 | 49 | [RabbitMQ commercial documentation](https://www.vmware.com/support/pubs/vfabric-rabbitmq.html)上有一系列你可以付费购买的RabbitMQ商业支持平台。 50 | 51 | ##不支持的平台 52 | 53 | 一些平台是不被支持的,而且很可能永远不会: 54 | 55 | - z/OS 和大多数的大型机 56 | - 有内存限制的机器(小于16Mb) 57 | 58 | 如果你的平台不在此列或者你需要其他的帮助,请[联系我们](http://www.rabbitmq.com/contact.html) -------------------------------------------------------------------------------- /published/tutorials_with_csharp/HelloWorld.md: -------------------------------------------------------------------------------- 1 | >原文:[Hello World](https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html) 2 | >翻译:[mr-ping](http://rabbitmq.mr-ping.com) 3 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 4 | 5 | > ### 前置条件 6 | 7 | > 本教程假设RabbitMQ已经[安装](http://www.rabbitmq.com/download.html)在你本机的 (5672)端口。如果你使用了不同的主机、端口或者凭证,连接设置就需要作出一些对应的调整。 8 | 9 | > ### 如何获得帮助 10 | 11 | > 如果你在使用本教程的过程中遇到了麻烦,你可以通过邮件列表来[联系我们](https://groups.google.com/forum/#!forum/rabbitmq-users)。 12 | 13 | 14 | ## 介绍 15 | 16 | RabbitMQ 是一个消息代理:它用来接收消息,并将其进行转发。 你可以把它想象成一个邮局:当你把想要邮寄的邮件放到邮箱里后,邮递员就会把邮件最终送达到目的地。 在这个比喻中,RabbitMQ既代表了邮箱,也同时扮演着邮局和邮递员的角色. 17 | 18 | RabbitMQ和邮局主要区别在于,RabbitMQ不处理纸质信件,取而代之,它接收、存储和转发的是被称为*消息*的二进制数据块。 19 | 20 | 下面介绍下通常情况下会用到的一些RabbitMQ和messaging术语: 21 | 22 | - *生产*就是指的发送。一个用来发送消息的*生产者*程序: 23 | 24 | ![img](http://www.rabbitmq.com/img/tutorials/producer.png) 25 | 26 | - *队列*指的是存在于RabbitMQ当中的邮箱。虽然消息是在RabbbitMQ和你的应用程序之间流转,但他们是存储在*队列*中的。*队列*只收到主机内存和磁盘的限制,它实质上是存在于主机内存和硬盘中的消息缓冲区。多个*生产者*可以发送消息到同一个*队列*中,多个*消费者*也可以从同一个*队列*中接收消息。我们这样来描述一个队列: 27 | 28 | ![img](http://www.rabbitmq.com/img/tutorials/queue.png) 29 | 30 | - *消费*跟接收基本是一个意思。一个*消费者*基本上就是一个用来等待接收消息的程序: 31 | 32 | ![img](http://www.rabbitmq.com/img/tutorials/consumer.png) 33 | 34 | 需要注意的是,生产者、消费者和代理不需要存在于同一个主机上; 实际上,大多数应用中也确实如此。另外,一个应用程序也可以同时充当生产者和消费者两个角色。 35 | 36 | ## "Hello World" 37 | 38 | ### (使用 .NET/C# 客户端) 39 | 40 | 在教程的这个部分,我们会使用C#语言编写两个程序;一个生产者负责发送单条信息,一个消费者负责接收这条信息并将其打印出来。我们会有意忽略.NET 客户端接口的一些细节,专注于利用这个及其简单的例子来打开局面。这个例子就是传送"Hello World"消息。 41 | 42 | 下方的图例中,“P”是我们的生产者,“C”是我们的消费者。中间的盒子代表RabbitMQ中用来为消费者保持消息的缓冲区——队列。 43 | 44 | ![(P) -> [|||] -> (C)](http://www.rabbitmq.com/img/tutorials/python-one.png) 45 | 46 | > #### .NET客户端库 47 | > 48 | > RabbitMQ有多种协议可用。本教程用的是AMQP 0-9-1这个用于消息传输的,开放的通用协议。RabbitMQ有许多针对 [不同语言的客户端](http://rabbitmq.com/devtools.html)。这里我们使用RabbitMQ出品的.NET客户端。 49 | > 50 | > 此客户端支持 [.NET Core](https://www.microsoft.com/net/core) 以及 .NET Framework 4.5.1+。本教程会使用RabbitMQ .NET client 5.0 和 .NET Core,所以你需要确保已经安装了他们,并且配置到了PATH当中。 51 | > 52 | > 当然你也可以使用.NET Framework来完成这个教程,但是安装配置过程会有所不同。 53 | > 54 | > RabbitMQ .NET 客户端 5.0 是通过[nuget](https://www.nuget.org/packages/RabbitMQ.Client)来分发的。 55 | > 56 | > 本教程假设你使用的是Windows中的powershell。在MacOS 和 Linux 中几乎所有的shell都可以正常完成我们的工作。 57 | 58 | ### 安装 59 | 60 | 首先,让我们验证一下你的`PATH`中是否有.NET Core工具连。 61 | 62 | ```powershell 63 | dotnet --help 64 | ``` 65 | 66 | 这时应该会生成一个帮助信息。 67 | 68 | 现在我们来生成两个项目,一个作为发布者(译者注:指的就是生产者),一个作为消费者。 69 | 70 | ```powershell 71 | dotnet new console --name Send 72 | mv Send/Program.cs Send/Send.cs 73 | dotnet new console --name Receive 74 | mv Receive/Program.cs Receive/Receive.cs 75 | ``` 76 | 77 | 这样就会分别创建两个名为`Send`和`Receive`的目录。 78 | 79 | 然后我们来添加客户端依赖。 80 | 81 | ```powershell 82 | cd Send 83 | dotnet add package RabbitMQ.Client 84 | dotnet restore 85 | cd ../Receive 86 | dotnet add package RabbitMQ.Client 87 | dotnet restore 88 | ``` 89 | 90 | 这样.NET项目就配置成功,我们可以着手写代码了。 91 | 92 | ### 发送 93 | 94 | ![(P) -> [|||]](http://www.rabbitmq.com/img/tutorials/sending.png) 95 | 96 | 我们将消息发布者(发送者)命名为Send.cs,将消息消费者(接收者)命名为Receive.cs。发布者将会连接到RabbitMQ,发送一条消息,然后退出。 97 | 98 | 在 [Send.cs](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/Send/Send.cs) 中, 我们需要使用一些命名空间: 99 | 100 | ```csharp 101 | using System; 102 | using RabbitMQ.Client; 103 | using System.Text; 104 | ``` 105 | 106 | 配置类: 107 | 108 | ```csharp 109 | class Send 110 | { 111 | public static void Main() 112 | { 113 | ... 114 | } 115 | } 116 | ``` 117 | 118 | 然后创建到服务器的连接: 119 | 120 | ```csharp 121 | class Send 122 | { 123 | public static void Main() 124 | { 125 | var factory = new ConnectionFactory() { HostName = "localhost" }; 126 | using (var connection = factory.CreateConnection()) 127 | { 128 | using (var channel = connection.CreateModel()) 129 | { 130 | ... 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | 此连接为我们抽象了套接字的链接,协议版本的协商以及验证。此处我们连接的是本地机器的代理,所以写的是*localhost*。如果我们打算链接其他机器上的代理,这里得写上那台机器的机器名或者IP地址。 138 | 139 | 接下来我们来创建一个信道,大部分API的工作都在此信道的基础上完成。 140 | 141 | 为了发送消息,我们必须声明一个用于送达消息的队列,然后我们会将消息发布到此队列中: 142 | 143 | ```csharp 144 | using System; 145 | using RabbitMQ.Client; 146 | using System.Text; 147 | 148 | class Send 149 | { 150 | public static void Main() 151 | { 152 | var factory = new ConnectionFactory() { HostName = "localhost" }; 153 | using(var connection = factory.CreateConnection()) 154 | using(var channel = connection.CreateModel()) 155 | { 156 | channel.QueueDeclare(queue: "hello", 157 | durable: false, 158 | exclusive: false, 159 | autoDelete: false, 160 | arguments: null); 161 | 162 | string message = "Hello World!"; 163 | var body = Encoding.UTF8.GetBytes(message); 164 | 165 | channel.BasicPublish(exchange: "", 166 | routingKey: "hello", 167 | basicProperties: null, 168 | body: body); 169 | Console.WriteLine(" [x] Sent {0}", message); 170 | } 171 | 172 | Console.WriteLine(" Press [enter] to exit."); 173 | Console.ReadLine(); 174 | } 175 | } 176 | ``` 177 | 178 | 队列的声明是幂等的,只有当这个队列不存在的时候才会被创建。消息的内容是一个比特数组,所以你可以按照自己的喜好对其进行编码。 179 | 180 | 当上方的代码运行完成之后,信道和连接会被销毁掉。以上就是我们的发布者。 181 | 182 | [这是完整的Send.cs类代码](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/Send/Send.cs). 183 | 184 | > #### 没有发送成功! 185 | > 186 | > 如果这是你第一次使用RabbitMQ,而且没有成功看到"Sent"信息的输出,估计你会抓耳挠腮,不得其解。有可能只是因为你的硬盘没有足够的空间了(默认需要50MB空闲空间),所以才会把消息拒绝掉。你可以查看代理日志文件来进行确认,并且降低此限制条件。[配置文件文档](http://www.rabbitmq.com/configure.html#config-items) 会告诉你如何设置`disk_free_limit`. 187 | 188 | ### 接收 189 | 190 | 消费者会监听来自与RabbitMQ的消息。所以不同于发布者只发送一条消息,我们会让消费者保持持续运行来监听消息,并将消息打印出来。 191 | 192 | ![[|||] -> (C)](http://www.rabbitmq.com/img/tutorials/receiving.png) 193 | 194 | 此代码(在 [Receive.cs](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/Receive/Receive.cs)中) 跟`Send`代码非常相似: 195 | 196 | ```csharp 197 | using RabbitMQ.Client; 198 | using RabbitMQ.Client.Events; 199 | using System; 200 | using System.Text; 201 | ``` 202 | 203 | 配置工作跟发布者是一样的;我们打开一个连接和一个信道,声明一个我们想要从其中获取消息的队列。注意这个队列需要与`Send`发布到信息的那个队列相匹配。 204 | 205 | ```csharp 206 | class Receive 207 | { 208 | public static void Main() 209 | { 210 | var factory = new ConnectionFactory() { HostName = "localhost" }; 211 | using (var connection = factory.CreateConnection()) 212 | { 213 | using (var channel = connection.CreateModel()) 214 | { 215 | channel.QueueDeclare(queue: "hello", 216 | durable: false, 217 | exclusive: false, 218 | autoDelete: false, 219 | arguments: null); 220 | ... 221 | } 222 | } 223 | } 224 | } 225 | ``` 226 | 227 | 你可能注意到了,这里我们又把队列声明了一次。这是因为,我们可能会先把消费者启动起来,而不是发布者。我们希望确保用于消费的队列是确实存在的。 228 | 229 | 接下来我们通知服务器可以将消息从队列里发送过来了。由于服务器会异步地将消息推送给我们,所以我们这里提供一个回调方法。这就是`EventingBasicConsumer.Receivedevent`所做的工作。 230 | 231 | ```csharp 232 | using RabbitMQ.Client; 233 | using RabbitMQ.Client.Events; 234 | using System; 235 | using System.Text; 236 | 237 | class Receive 238 | { 239 | public static void Main() 240 | { 241 | var factory = new ConnectionFactory() { HostName = "localhost" }; 242 | using(var connection = factory.CreateConnection()) 243 | using(var channel = connection.CreateModel()) 244 | { 245 | channel.QueueDeclare(queue: "hello", 246 | durable: false, 247 | exclusive: false, 248 | autoDelete: false, 249 | arguments: null); 250 | 251 | var consumer = new EventingBasicConsumer(channel); 252 | consumer.Received += (model, ea) => 253 | { 254 | var body = ea.Body; 255 | var message = Encoding.UTF8.GetString(body); 256 | Console.WriteLine(" [x] Received {0}", message); 257 | }; 258 | channel.BasicConsume(queue: "hello", 259 | autoAck: true, 260 | consumer: consumer); 261 | 262 | Console.WriteLine(" Press [enter] to exit."); 263 | Console.ReadLine(); 264 | } 265 | } 266 | } 267 | ``` 268 | 269 | [这是 Receive.cs 类的完整代码](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/Receive/Receive.cs). 270 | 271 | ### 将它们整合到一起 272 | 273 | 打开两个终端。 274 | 275 | 运行消费者: 276 | 277 | ```powershell 278 | cd Receive 279 | dotnet run 280 | ``` 281 | 282 | 然后运行生产者: 283 | 284 | ```powershell 285 | cd Send 286 | dotnet run 287 | ``` 288 | 289 | 消费者会接收到发布者通过RabbitMQ发送的消息,并将其打印出来。消费者会一直保持运行状态来等待接受消息(可以使用Ctrl-C 来将其停止),接下来我们可以试着在另一个终端里运行发布者代码来尝试发送消息了。 290 | ß 291 | 接下来,我们可以移步[第二部分](http://www.rabbitmq.com/tutorials/tutorial-two-dotnet.html) ,创建一个简单的*工作队列*。 292 | -------------------------------------------------------------------------------- /published/tutorials_with_csharp/Topics.md: -------------------------------------------------------------------------------- 1 | >原文:[Topics](https://www.rabbitmq.com/tutorials/tutorial-five-dotnet.html) 2 | >翻译:[mr-ping](http://rabbitmq.mr-ping.com) 3 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 4 | 5 | > ### 前置条件 6 | 7 | > 本教程假设RabbitMQ已经[安装](http://www.rabbitmq.com/download.html)在你本机的 (5672)端口。如果你使用了不同的主机、端口或者凭证,连接设置就需要作出一些对应的调整。 8 | 9 | > ### 如何获得帮助 10 | 11 | > 如果你在使用本教程的过程中遇到了麻烦,你可以通过邮件列表来[联系我们](https://groups.google.com/forum/#!forum/rabbitmq-users)。 12 | 13 | ## 主题 14 | 15 | ### (使用.NET客户端) 16 | 17 | 18 | 19 | [上个教程](https://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html)里,我们对日志系统进行了改进。我们用直连交换机取代了只会无脑广播的扇形交换机,并且具备了选择性接收日志的能力。 20 | 21 | 尽管使用直连交换机来改进了我们的系统,但是仍有一点缺陷——仍然不能基于多个条件进行路由。 22 | 23 | 在我们的日志系统中,我们除了想要根据严重性来订阅消息外,还想根据发送日志的来源进行订阅。你之前有可能通过一个名叫 [`syslog`](http://en.wikipedia.org/wiki/Syslog)的Unix工具了解过这种情形,它是通过严重性(info/warn/crit...)和设备(auth/cron/kern...)来对日志进行路由的。 24 | 25 | 通过多种条件进行路由会给我们带来很大的灵活性——比如可能我们想要监听的是来自'cron'的关键(`critical`)错误和来自 'kern'的所有日志。 26 | 27 | 想要在日志系统中实现以上的功能,我们需要学一下更复杂的主题交换机。 28 | 29 | ## 主题交换机 30 | 31 | 发送到主题交换机的消息所携带的路由键(`routing_key`)不能随意命名——它必须是一个用点号分隔的词列表。当中的词可以是任何单词,不过一般都会指定一些跟消息有关的特征作为这些单词。列举几个有效的路由键的例子:"`stock.usd.nyse`", "`nyse.vmw`", "`quick.orange.rabbit`"。只要不超过255个字节,词的长度由你来定。 32 | 33 | 绑定键(`binding key`)也得使用相同的格式。主题交换机背后的逻辑跟直连交换机比较相似——一条携带特定路由键(`routing key`)的消息会被投送给所有绑定键(`binding key`)与之相匹配的队列。尽管如此,仍然有两条与绑定键相关的特殊情况: 34 | 35 | - \`*` (星号) 能够替代一个单词。 36 | - \`#` (井号) 能够替代零个或多个单词。 37 | 38 | 39 | 用一个例子可以很容易地解释: 40 | 41 | ![img](https://www.rabbitmq.com/img/tutorials/python-five.png) 42 | 43 | 此例中,我们将会发送用来描述动物的多条消息。发送的消息包含带有三个单词(两个点号)的路由键(`routing key`)。路由键中第一个单词描述速度,第二个单词是颜色,第三个是品种: "`<速度>.<颜色>.<品种>`"。 44 | 45 | 我们创建三个绑定:Q1通过"`*.orange.*`"绑定键进行绑定,Q2使用"`*.*.rabbit`" 和 "`lazy.#`"。 46 | 47 | 这些绑定可以总结为: 48 | 49 | - Q1针对所有的橘色`orange`动物。 50 | - Q2针对每一个有关兔子`rabbits`和慵懒`lazy`的动物的消息。 51 | 52 | 一个带有"`quick.orange.rabbit`"绑定键的消息会给两个队列都进行投送。消息"`lazy.orange.elephant`"也会投送给这两个队列。另外一方面,"`quick.orange.fox`" 只会给第一个队列。"`lazy.pink.rabbit`"虽然与两个绑定键都匹配,但只会给第二个队列投送一遍。"`quick.brown.fox`" 没有匹配到任何绑定,因此会被丢弃掉。 53 | 54 | 如果我们破坏规则,发送的消息只带有一个或者四个单词,例如 "`orange`" 或者 "`quick.orange.male.rabbit`"会发生什么呢?结果是这些消息不会匹配到任何绑定,将会被丢弃。 55 | 56 | 另一方面,“`lazy.orange.male.rabbit`”即使有四个单词,也会与最后一个绑定匹配,并 被投送到第二个队列。 57 | 58 | > #### 主题交换机 59 | > 60 | > 主题交换机非常强大,并且可以表现的跟其他交换机相似。 61 | > 62 | > 当一个队列使用"`#`"(井号)绑定键进行绑定。它会表现的像扇形交换机一样,不理会路由键,接收所有消息。 63 | > 64 | > 当绑定当中不包含任何一个 "*" (星号) 和 "#" (井号)特殊字符的时候,主题交换机会表现的跟直连交换机一毛一样。 65 | 66 | ## 整合到一起 67 | 68 | 我们将在日志系统中使用主题交换机。我们现从一个可行的假设开始,假设日志的路由键包含两个单词: "`<设施>.<严重性>`". 69 | 70 | 代码跟[上个教程](https://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html)非常相似: 71 | 72 | `EmitLogTopic.cs`的代码: 73 | 74 | ```csharp 75 | using System; 76 | using System.Linq; 77 | using RabbitMQ.Client; 78 | using System.Text; 79 | 80 | class EmitLogTopic 81 | { 82 | public static void Main(string[] args) 83 | { 84 | var factory = new ConnectionFactory() { HostName = "localhost" }; 85 | using(var connection = factory.CreateConnection()) 86 | using(var channel = connection.CreateModel()) 87 | { 88 | channel.ExchangeDeclare(exchange: "topic_logs", 89 | type: "topic"); 90 | 91 | var routingKey = (args.Length > 0) ? args[0] : "anonymous.info"; 92 | var message = (args.Length > 1) 93 | ? string.Join(" ", args.Skip( 1 ).ToArray()) 94 | : "Hello World!"; 95 | var body = Encoding.UTF8.GetBytes(message); 96 | channel.BasicPublish(exchange: "topic_logs", 97 | routingKey: routingKey, 98 | basicProperties: null, 99 | body: body); 100 | Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message); 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | `ReceiveLogsTopic.cs`的代码: 107 | 108 | ```csharp 109 | using System; 110 | using RabbitMQ.Client; 111 | using RabbitMQ.Client.Events; 112 | using System.Text; 113 | 114 | class ReceiveLogsTopic 115 | { 116 | public static void Main(string[] args) 117 | { 118 | var factory = new ConnectionFactory() { HostName = "localhost" }; 119 | using(var connection = factory.CreateConnection()) 120 | using(var channel = connection.CreateModel()) 121 | { 122 | channel.ExchangeDeclare(exchange: "topic_logs", type: "topic"); 123 | var queueName = channel.QueueDeclare().QueueName; 124 | 125 | if(args.Length < 1) 126 | { 127 | Console.Error.WriteLine("Usage: {0} [binding_key...]", 128 | Environment.GetCommandLineArgs()[0]); 129 | Console.WriteLine(" Press [enter] to exit."); 130 | Console.ReadLine(); 131 | Environment.ExitCode = 1; 132 | return; 133 | } 134 | 135 | foreach(var bindingKey in args) 136 | { 137 | channel.QueueBind(queue: queueName, 138 | exchange: "topic_logs", 139 | routingKey: bindingKey); 140 | } 141 | 142 | Console.WriteLine(" [*] Waiting for messages. To exit press CTRL+C"); 143 | 144 | var consumer = new EventingBasicConsumer(channel); 145 | consumer.Received += (model, ea) => 146 | { 147 | var body = ea.Body; 148 | var message = Encoding.UTF8.GetString(body); 149 | var routingKey = ea.RoutingKey; 150 | Console.WriteLine(" [x] Received '{0}':'{1}'", 151 | routingKey, 152 | message); 153 | }; 154 | channel.BasicConsume(queue: queueName, 155 | autoAck: true, 156 | consumer: consumer); 157 | 158 | Console.WriteLine(" Press [enter] to exit."); 159 | Console.ReadLine(); 160 | } 161 | } 162 | } 163 | ``` 164 | 165 | 运行一下例子: 166 | 167 | 接收所有日志: 168 | 169 | ```bash 170 | cd ReceiveLogsTopic 171 | dotnet run "#" 172 | ``` 173 | 174 | 接收来自于"`kern`"设施的所有日志: 175 | 176 | ```bash 177 | cd ReceiveLogsTopic 178 | dotnet run "kern.*" 179 | ``` 180 | 181 | 或者如果你只想接收跟”严重“(”`critical`“)程度有关的日志: 182 | 183 | ```bash 184 | cd ReceiveLogsTopic 185 | dotnet run "*.critical" 186 | ``` 187 | 188 | 你可以创建多个绑定: 189 | 190 | ```bash 191 | cd ReceiveLogsTopic 192 | dotnet run "kern.*" "*.critical" 193 | ``` 194 | 195 | 然后发送一个路由键为"`kern.critical`"的日志: 196 | 197 | ```bash 198 | cd EmitLogTopic 199 | dotnet run "kern.critical" "A critical kernel error" 200 | ``` 201 | 202 | 有意思吧?需要注意的是代码并没有对路由键或者绑定键做任何假定,你仍然可以用多于两个路由参数。 203 | 204 | ([EmitLogTopic.cs的完整代码](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/EmitLogTopic/EmitLogTopic.cs) 和 [ReceiveLogsTopic.cs](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/ReceiveLogsTopic/ReceiveLogsTopic.cs)) 205 | 206 | 接下来,可以[教程 6](https://www.rabbitmq.com/tutorials/tutorial-six-dotnet.html)会介绍到如何像远程过程调用一样操作一个往返的消息。 207 | -------------------------------------------------------------------------------- /published/tutorials_with_csharp/WorkQueue.md: -------------------------------------------------------------------------------- 1 | >原文:[Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-dotnet.html) 2 | >翻译:[mr-ping](http://rabbitmq.mr-ping.com) 3 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 4 | 5 | > ### 前置条件 6 | 7 | > 本教程假设RabbitMQ已经[安装](http://www.rabbitmq.com/download.html)在你本机的 (5672)端口。如果你使用了不同的主机、端口或者凭证,连接设置就需要作出一些对应的调整。 8 | 9 | > ### 如何获得帮助 10 | 11 | > 如果你在使用本教程的过程中遇到了麻烦,你可以通过邮件列表来[联系我们](https://groups.google.com/forum/#!forum/rabbitmq-users)。 12 | > 13 | > 14 | 15 | ## 工作队列 16 | 17 | ### (使用 .NET 客户端) 18 | 19 | 20 | ![img](http://www.rabbitmq.com/img/tutorials/python-two.png) 21 | 22 | 在[第一个教程](http://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html)中,我们写了一个用于向命名过的队列发送消息并且从其中进行接收的程序。本教程中,我们会创建一个用于在多个工作者(worker)当中分发耗时任务的*工作队列(work queue)*。 23 | 24 | 工作队列 (亦称 *任务队列*) 的主要目的在于避免资源密集型任务被立即执行,执行者需要一直等到它完成为止。相反,我们会安排任务稍后完成。我们将*任务*封装成一条消息,并将其发送给队列。运行于后台的工作者程序会丢弃掉这条消息(译者注:首先从工作队列中接收到消息),并最终将消息所描述的任务执行完。当你有多个工作者的情况下,任务会在多个工作者中共享。 25 | 26 | 由于web应用无法在一个短暂的HTTP请求过程中执行比较复杂的任务,所以这种概念在web应用当中特别有用。 27 | 28 | ## 准备 29 | 30 | 上个教程中,我们发送了一条"Hellow World!"消息。这次,我们会发送一个用于表示复杂任务的字符串。由于我们当下没有一个类似于图片缩放,渲染pdf文件这种真实的任务需要执行,因此我们会使用`Thread.Sleep()`函数来伪造繁忙的状态(要使用有关线程的接口,需要在文件顶部添加`using System.Threading;`的引用)。我们会在字符串中使用英文句号来表示任务的复杂程度。每一个点代表一秒钟的工作者执行的耗时。举个例子,如果一个伪造的任务用`Hello...`来表示,那就意味着它会耗时三秒钟。 31 | 32 | 我们会稍微改造下上个教程中的*Send*程序,以便于可以通过命令行发送任意一条消息。这个程序会用来为我们的工作队列规划任务,让我们称它为`NewTask`: 33 | 34 | 跟 [教程一](http://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html) 一样,我们需要生成两个项目: 35 | 36 | ```powershell 37 | dotnet new console --name NewTask 38 | mv NewTask/Program.cs NewTask/NewTask.cs 39 | dotnet new console --name Worker 40 | mv Worker/Program.cs Worker/Worker.cs 41 | cd NewTask 42 | dotnet add package RabbitMQ.Client 43 | dotnet restore 44 | cd ../Worker 45 | dotnet add package RabbitMQ.Client 46 | dotnet restore 47 | var message = GetMessage(args); 48 | var body = Encoding.UTF8.GetBytes(message); 49 | 50 | var properties = channel.CreateBasicProperties(); 51 | properties.Persistent = true; 52 | 53 | channel.BasicPublish(exchange: "", 54 | routingKey: "task_queue", 55 | basicProperties: properties, 56 | body: body); 57 | ``` 58 | 59 | 为从命令行参数获取消息内容提供一些帮助: 60 | 61 | ```csharp 62 | private static string GetMessage(string[] args) 63 | { 64 | return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!"); 65 | } 66 | ``` 67 | 68 | 上一版 *Receive.cs* 脚本也需要做一些修改:它需要为消息体中的每一个英文句号伪造一秒钟的工作耗时,所以我们将它拷贝到`worker`项目中,并做如下修改: 69 | 70 | ```csharp 71 | var consumer = new EventingBasicConsumer(channel); 72 | consumer.Received += (model, ea) => 73 | { 74 | var body = ea.Body; 75 | var message = Encoding.UTF8.GetString(body); 76 | Console.WriteLine(" [x] Received {0}", message); 77 | 78 | int dots = message.Split('.').Length - 1; 79 | Thread.Sleep(dots * 1000); 80 | 81 | Console.WriteLine(" [x] Done"); 82 | }; 83 | channel.BasicConsume(queue: "task_queue", autoAck: true, consumer: consumer); 84 | ``` 85 | 86 | 伪造任务来模拟执行时间: 87 | 88 | ```csharp 89 | int dots = message.Split('.').Length - 1; 90 | Thread.Sleep(dots * 1000); 91 | ``` 92 | 93 | ## 循环调度 94 | 95 | 使用任务队列所带来的一个高级特性是可以用简单的方式让工作具有并行执行的能力。如果我们建立的是一个阻塞的任务,那只需要通过添加更多的工作者就可以简便的进行扩展。 96 | 97 | 首先,让我们同事运行两个`Worker`实例。它们都可以从队列中获取消息。下面我们看看它们是如何工作的。 98 | 99 | 你需要打开三个控制台。两个用于运行`Worker`程序。它们分别代表两个消费者,命名为C1和C2。 100 | 101 | ```bash 102 | # shell 1 103 | cd Worker 104 | dotnet run 105 | # => [*] Waiting for messages. To exit press CTRL+C 106 | # shell 2 107 | cd Worker 108 | dotnet run 109 | # => [*] Waiting for messages. To exit press CTRL+C 110 | ``` 111 | 112 | 第三个控制台用来发布新任务。开启了消费者之后,你就可以发布新消息了: 113 | 114 | ```bash 115 | # shell 3 116 | cd NewTask 117 | dotnet run "First message." 118 | dotnet run "Second message.." 119 | dotnet run "Third message..." 120 | dotnet run "Fourth message...." 121 | dotnet run "Fifth message....." 122 | ``` 123 | 124 | 让我们看看投送给工作者的是什么内容: 125 | 126 | ```bash 127 | # shell 1 128 | # => [*] Waiting for messages. To exit press CTRL+C 129 | # => [x] Received 'First message.' 130 | # => [x] Received 'Third message...' 131 | # => [x] Received 'Fifth message.....' 132 | # shell 2 133 | # => [*] Waiting for messages. To exit press CTRL+C 134 | # => [x] Received 'Second message..' 135 | # => [x] Received 'Fourth message....' 136 | ``` 137 | 138 | 默认情况下,RabbitMQ会将消息依次发送给下一个消费者。平均每个消费者会获得同样数量的消息。这种消息分发方法被称为循环法(round-robin)。你可以发布三条以上的消息来尝试一下。 139 | 140 | ## 消息确认 141 | 142 | 执行一个任务可能会耗时好几秒钟。你也许会好奇如果一个消费者在执行一个耗时任务时只完成了部分工作就挂掉的情况下会发生什么。在我们当前代码下,一旦RabbitMQ将消息投送给消费者后,它会立即将消息标示为删除状态。这个案例中,如果你将工作者杀掉的话,我们会丢失它正在处理的消息。如果有其他已经调度给这个工作者的消息没有完成,也会一起丢失。 143 | 144 | 但是我们不想丢失任何任务。如果一个工作者挂掉了,我们希望任务会投送给其他的工作者。 145 | 146 | 为了确保消息永不丢失,RabbitMQ支持 [消息 *确认*](http://www.rabbitmq.com/confirms.html)。消费者回送一个确认信号——ack(nowledgement)给RabbitMQ,告诉它一条指定的消息已经接收到并且处理完毕,可以选择将消息删除掉了。 147 | 148 | 如果一个消费者在没有回送确认信号的情况下挂掉了(消费者的信道关闭,连接关闭或者TCP连接已经丢失),RabbitMQ会理解为此条消息没有被处理完成,并且重新将其放入队列。如果恰时有其他消费者在线,这条消息会立即被投送给其他的消费者。通过这种方式,你可以确定即使有工作者由于事故挂掉,也不会发生消息丢失的情况。 149 | 150 | RabbitMQ不会有任何消息超时的机制,消费者挂掉之后RabbitMQ才会将此消息投送给其他消费者。所以即使消息处理需要话费超长的是时间也没有问题。 151 | 152 | [手动进行消息确认](http://www.rabbitmq.com/confirms.html) 默认为开启状态。上个例子中,我们明确地通过将autoAck ("自动确认模式")设置为true将其关闭掉了。这次我们移除掉这个标志,一旦任务完成,手动从工作者当中发送合适的确认标志。 153 | 154 | ```csharp 155 | var consumer = new EventingBasicConsumer(channel); 156 | consumer.Received += (model, ea) => 157 | { 158 | var body = ea.Body; 159 | var message = Encoding.UTF8.GetString(body); 160 | Console.WriteLine(" [x] Received {0}", message); 161 | 162 | int dots = message.Split('.').Length - 1; 163 | Thread.Sleep(dots * 1000); 164 | 165 | Console.WriteLine(" [x] Done"); 166 | 167 | channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); 168 | }; 169 | channel.BasicConsume(queue: "task_queue", autoAck: false, consumer: consumer); 170 | ``` 171 | 172 | 使用本代码,即使你在它运行时使用 CTRL+C杀掉工作者,也不会有任何东西丢失。稍后,挂掉的工作者当中未进行确认的消息会被重新投送。 173 | 174 | 确认信号必须在收到投送的同一个信道上发送。尝试在不同的信道上发送确认信号会引发信道级别的协议异常。 [确认行为的文档指南](http://www.rabbitmq.com/confirms.html) 里有更多介绍。 175 | 176 | > #### 忘记进行确认 177 | > 178 | > 忘记使用`BasicAck`是一个常见的错误。虽然是个简单的错误,但是后果严重。消息会在客户端退出后重新投送(就像是随机进行的重新投送),但是由于RabbitMQ无法释放任何未经确认的消息,内存占用会越来越严重。 179 | > 180 | > 想要对这种错误进行调试,你可以使用`rabbitmqctl`将“未经确认的消息”(`messages_unacknowledged`)字段打印出来 181 | > 182 | > 183 | > 184 | > ```bash 185 | > sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged 186 | > ``` 187 | > 188 | > 189 | > 190 | > On Windows, drop the sudo: 191 | > 192 | > 在Windows中,不需要sudo: 193 | > 194 | > ```bash 195 | > rabbitmqctl.bat list_queues name messages_ready messages_unacknowledged 196 | > ``` 197 | 198 | ## 消息持久化 199 | 200 | 我们已经学过了如何让任务在消费者即使挂掉的情况也不会丢失。但我们的任务仍有可能在RabbitMQ服务器停机的时候丢失掉。 201 | 202 | 当RabbitMQ退出或崩溃的时候会忘记掉所有的队列,除非你告诉他不要这么做。如果想要确保消息不会丢失,我们需要做两件事,将队列和消息都标示成持久化。 203 | 204 | 首先,我们需要确保RabbitMQ永远不会将队列丢失。为了达到此目的,我们需要使用*durable*来将其持久化。 205 | 206 | ```csharp 207 | channel.QueueDeclare(queue: "hello", 208 | durable: true, 209 | exclusive: false, 210 | autoDelete: false, 211 | arguments: null); 212 | ``` 213 | 214 | 虽然这个命令本身是正确的,但是它在我们当前的配置中并不起作用。原因是我们已经定义了一个名为`hello`的非持久化队列。RabbitMQ不允许用不同的参数去重新定义一个已经存在的队列,如果有程序尝试这样做的话,会收到一个错误的返回值。 但是有一个快捷的解决方案——我们可以定义一个不重名的队列,例如`task_queue`: 215 | 216 | 217 | ```csharp 218 | channel.QueueDeclare(queue: "task_queue", 219 | durable: true, 220 | exclusive: false, 221 | autoDelete: false, 222 | arguments: null); 223 | ``` 224 | 225 | 此`QueueDeclare`的改变需要应用到生产者和消费者两份代码当中。 226 | 227 | 当下,我们可以确认即使RabbitMQ重启,我们的`task_queue`队列也不会丢失。接下来,我们需要将`IBasicProperties.SetPersistent`设置为`true`,用来将我们的消息标示成持久化的。 228 | 229 | 230 | ```csharp 231 | var properties = channel.CreateBasicProperties(); 232 | properties.Persistent = true; 233 | ``` 234 | 235 | > #### 消息持久化的注释 236 | > 237 | > 将消息标示为持久化并不能完全保证消息不会丢失。尽管它会告诉RabbitMQ将消息存储到硬盘上,但是在RabbitMQ接收到消息并将其进行存储两个行为之间仍旧会有一个窗口期。同样的,RabbitMQ也不会对每一条消息执行`fsync(2)`,所以消息获取只是存到了缓存之中,而不是硬盘上。虽然持久化的保证不强,但是应对我们简单的任务队列已经足够了。如果你需要更强的保证,可以使用[publisher confirms](https://www.rabbitmq.com/confirms.html). 238 | 239 | ## 公平调度 240 | 241 | 你可能注意到了,调度依照我们希望的方式运行。例如在有两个工作者的情况下,当所有的奇数任务都很繁重而所有的偶数任务都很轻松的时候,其中一个工作者会一直处于忙碌之中而另一个几乎无事可做。RabbitMQ并不会对此有任何察觉,仍旧会平均分配消息。 242 | 243 | 这种情况发生的原因是由于当有消息进入队列时,RabbitMQ只负责将消息调度的工作,而不会检查某个消费者有多少未经确认的消息。它只是盲目的将第n个消息发送给第n个消费者而已。 244 | 245 | ![img](http://www.rabbitmq.com/img/tutorials/prefetch-count.png) 246 | 247 | 要改变这种行为的话,我们可以在`BasicQos`方法中设置`prefetchCount = 1`。这样会告诉RabbitMQ一次不要给同一个worker提供多于一条的信息。话句话说,在一个工作者还没有处理完消息,并且返回确认标志之前,不要再给它调度新的消息。取而代之,它会将消息调度给下一个不再繁忙的工作者。 248 | 249 | ```csharp 250 | channel.BasicQos(0, 1, false); 251 | ``` 252 | 253 | > If all the workers are busy, your queue can fill up. You will want to keep an eye on that, and maybe add more workers, or have some other strategy. 254 | 255 | ## 整合到一起 256 | 257 | 最终的`NewTask.cs` 类代码: 258 | 259 | ```csharp 260 | using System; 261 | using RabbitMQ.Client; 262 | using System.Text; 263 | 264 | class NewTask 265 | { 266 | public static void Main(string[] args) 267 | { 268 | var factory = new ConnectionFactory() { HostName = "localhost" }; 269 | using(var connection = factory.CreateConnection()) 270 | using(var channel = connection.CreateModel()) 271 | { 272 | channel.QueueDeclare(queue: "task_queue", 273 | durable: true, 274 | exclusive: false, 275 | autoDelete: false, 276 | arguments: null); 277 | 278 | var message = GetMessage(args); 279 | var body = Encoding.UTF8.GetBytes(message); 280 | 281 | var properties = channel.CreateBasicProperties(); 282 | properties.Persistent = true; 283 | 284 | channel.BasicPublish(exchange: "", 285 | routingKey: "task_queue", 286 | basicProperties: properties, 287 | body: body); 288 | Console.WriteLine(" [x] Sent {0}", message); 289 | } 290 | 291 | Console.WriteLine(" Press [enter] to exit."); 292 | Console.ReadLine(); 293 | } 294 | 295 | private static string GetMessage(string[] args) 296 | { 297 | return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!"); 298 | } 299 | } 300 | ``` 301 | 302 | [(NewTask.cs 源代码)](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/NewTask/NewTask.cs) 303 | 304 | 我们的 `Worker.cs`: 305 | 306 | ```csharp 307 | using System; 308 | using RabbitMQ.Client; 309 | using RabbitMQ.Client.Events; 310 | using System.Text; 311 | using System.Threading; 312 | 313 | class Worker 314 | { 315 | public static void Main() 316 | { 317 | var factory = new ConnectionFactory() { HostName = "localhost" }; 318 | using(var connection = factory.CreateConnection()) 319 | using(var channel = connection.CreateModel()) 320 | { 321 | channel.QueueDeclare(queue: "task_queue", 322 | durable: true, 323 | exclusive: false, 324 | autoDelete: false, 325 | arguments: null); 326 | 327 | channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); 328 | 329 | Console.WriteLine(" [*] Waiting for messages."); 330 | 331 | var consumer = new EventingBasicConsumer(channel); 332 | consumer.Received += (model, ea) => 333 | { 334 | var body = ea.Body; 335 | var message = Encoding.UTF8.GetString(body); 336 | Console.WriteLine(" [x] Received {0}", message); 337 | 338 | int dots = message.Split('.').Length - 1; 339 | Thread.Sleep(dots * 1000); 340 | 341 | Console.WriteLine(" [x] Done"); 342 | 343 | channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); 344 | }; 345 | channel.BasicConsume(queue: "task_queue", 346 | autoAck: false, 347 | consumer: consumer); 348 | 349 | Console.WriteLine(" Press [enter] to exit."); 350 | Console.ReadLine(); 351 | } 352 | } 353 | } 354 | ``` 355 | 356 | [(Worker.cs 源代码)](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/Worker/Worker.cs) 357 | 358 | 使用消息确认和`BasicQos`,你可以设置一个工作队列。持久化选项可以使得任务即使在RabbitMQ重启后也不会丢失。 359 | 360 | 有关`IModel`方法和`IBasicProperties`的更多信息,请浏览[RabbitMQ .NET client API reference online](http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v3.6.10/rabbitmq-dotnet-client-3.6.10-client-htmldoc/html/index.html). 361 | 362 | 现在我们可以异步[教程 3](http://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html),学习将消息投送给多个消费者。 363 | -------------------------------------------------------------------------------- /published/tutorials_with_csharp/publish&subscribe.md: -------------------------------------------------------------------------------- 1 | >原文:[Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html) 2 | >翻译:[mr-ping](http://rabbitmq.mr-ping.com) 3 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 4 | 5 | > ### 前置条件 6 | > 7 | > 本教程假设RabbitMQ已经[安装](http://www.rabbitmq.com/download.html)在你本机的 (5672)端口。如果你使用了不同的主机、端口或者凭证,连接设置就需要作出一些对应的调整。 8 | > 9 | > ### 如何获得帮助 10 | > 11 | > 如果你在使用本教程的过程中遇到了麻烦,你可以通过邮件列表来[联系我们](https://groups.google.com/forum/#!forum/rabbitmq-users)。 12 | 13 | 14 | 15 | ## 发布/订阅 16 | 17 | ### (使用 .NET 客户端) 18 | 19 | 20 | 21 | 在[上个教程](https://www.rabbitmq.com/tutorials/tutorial-two-dotnet.html)中,我们创建了一个工作队列。工作队列假设每个任务只会被推送给一个工作者。这部分,我们会做一些完全不同的事情——我们会将消息投送给多个消费者。这种模式被称为“发布/订阅”。 22 | 23 | 为了解释此种模式,我们将会建立一个简单的日志系统。它由两个程序组成——第一个会发送日志消息,第二个接收、并将其打印出来。 24 | 25 | 在我们的日志系统中,每一个接收程序的拷贝都会获取到消息。通过这种方式,我们可以做到让其中一个接收者将日志直接存储到硬盘上,同时运行的另一个接收者将日志输出到屏幕上用于查看。 26 | 27 | 实质上,发布的日志消息会广播给所有的接收者。 28 | 29 | ## 交换机 30 | 31 | 教程的上个部分里,我们通过一个队列来发送和接收消息。现在,是时候把完整的Rabbit消息模型模型介绍一下了。 32 | 33 | 让我们快速过一下上个教程中所涉及的内容。 34 | 35 | - 一个“生产者”就是一个发送消息的用户应用程序。 36 | - 一个“队列”就是存储消息的缓存。 37 | - 一个“消费者”就是一个接收消息的用户应用程序。 38 | 39 | RabbitMQ的消息模型中的核心思想就是生产者永远不会将任何消息直接发送给队列。实际上,通常情况下,生产者根本不知道它是否会将消息投送给任何一个队列。 40 | 41 | 真正的情况是,生产者只能将消息发送给一个*交换机*。交换机是个很简单概念。它做左手收生产者发送的消息,右手就将消息推送给队列。交换机必须明确的知道需要对接收到的消息做些什么。消息是需要追加到一个特定的队列中?是需要追加到多个队列中?还是需要被丢弃掉。*交换机类型(exchange type)*就是用来定义这种规则的。 42 | 43 | ![img](https://www.rabbitmq.com/img/tutorials/exchanges.png) 44 | 45 | 这里有几个可用的交换机类型:直连交换机(`direct`), 主题交换机(`topic`), 头交换机(`headers`) 和扇形交换机(`fanout`)。我们会把关注点放在最后一个上。让我们来创建一个此种类型的交换机,将其命名为`logs`: 46 | 47 | ```csharp 48 | channel.ExchangeDeclare("logs", "fanout"); 49 | ``` 50 | 51 | 52 | 扇形交换机非常简单。从名字就可猜出来,它只是负责将消息广播给所有它所知道的队列。这正是我们的日志系统所需要的。 53 | 54 | > 55 | > 56 | > #### 交换机的监听 57 | > 58 | > 想要列出服务器上的交换机,可以运行`rabbitmqctl`这个非常有用的程序: 59 | > 60 | > 61 | > 62 | > ```bash 63 | > sudo rabbitmqctl list_exchanges 64 | > ``` 65 | > 66 | > 67 | > 68 | > 在此列表中,会出现一些类似于`amq.*`的交换机以及默认(未命名)交换机。这些交换机是以默认方式创建的,但此刻并不需要用到它们。 69 | > 70 | > #### 默认交换机 71 | > 72 | > 教程的上一部分中,我们对交换机还一无所知,但是依然能将消息发送给队列。原因是我们使用了用空字符串(`""`)来标示的默认交换机。 73 | > 74 | > 回想一下之前我们是如何来发布消息的: 75 | > 76 | > 77 | > 78 | > ```csharp 79 | > var message = GetMessage(args); 80 | > var body = Encoding.UTF8.GetBytes(message); 81 | > channel.BasicPublish(exchange: "", 82 | > routingKey: "hello", 83 | > basicProperties: null, 84 | > body: body); 85 | > ``` 86 | > 87 | > 88 | > 89 | > 第一个参数就是交换机的名字。空字符串用来表示默认或者*无名*交换机:如果队列存在的话,消息会依据路由键(`routingKey`)所指定的名称路由到队列中。 90 | 91 | 现在我们可以对命名过的交换机执行发布操作了: 92 | 93 | ```csharp 94 | var message = GetMessage(args); 95 | var body = Encoding.UTF8.GetBytes(message); 96 | channel.BasicPublish(exchange: "logs", 97 | routingKey: "", 98 | basicProperties: null, 99 | body: body); 100 | ``` 101 | 102 | ## 临时队列 103 | 104 | 你可能还记得我们上次使用的是命名过的队列(还记得`hello`和`task_queue`吗?)。可以对队列进行命名对我们来说是至关重要的——我们需要将工作者指向同一个队列。当你想在多个生产者和消费者之间共享一个队列时,给队列起个名字是很重要的。 105 | 106 | 但是我们的日志系统不需要如此。我们希望了解所有的消息,而不是其中的一个子集。而且我们只对当前正在流动的消息感兴趣,而不是那些老的消息。所以我们需要做两件事情来解决这个问题。 107 | 108 | 首先,如论我们何时连接到Rabbit,我们需要的是一个新鲜的空队列。想要做到这点,我们可以创建一个随机命名的队列,或者更简单一点——让服务器为我们选择一个随机队列名称。 109 | 110 | 其次,一旦消费者断开连接,队列需要被自动删除。 111 | 112 | 在.NET客户端中,当我们不给`QueueDeclare()`提供参数的情况下,就可以创建一个非持久化、独享的、可自动删除的拥有生成名称的队列。 113 | 114 | ```csharp 115 | var queueName = channel.QueueDeclare().QueueName; 116 | ``` 117 | 118 | 你可以在[guide on queues](https://www.rabbitmq.com/queues.html)中学习到更多关于独享(`exclusive`)标识以及其他队列属性的相关信息。 119 | 120 | 此时,`queueName`包含的是一个随机的队列名称。看起来可能会类似于`amq.gen-JzTY20BRgKO-HjmUJj0wLg`这样。 121 | 122 | ## 绑定 123 | 124 | ![img](https://www.rabbitmq.com/img/tutorials/bindings.png) 125 | 126 | 我们已经创建了一个扇形交换机和一个队列。现在我们需要通知交换机将消息发送给我们的队列。交换机和队列之间的这种关系称为*绑定(`binding`)*。 127 | 128 | ```csharp 129 | channel.QueueBind(queue: queueName, 130 | exchange: "logs", 131 | routingKey: ""); 132 | ``` 133 | 134 | 现在开始,`logs`交换机会将消息追加到我们的队列当中。 135 | 136 | > #### 绑定的监听 137 | > 138 | > 你可以通过以下命令列出所有正在使用的绑定, 139 | > 140 | > ```bash 141 | > rabbitmqctl list_bindings 142 | > ``` 143 | 144 | ## 组合到一起 145 | 146 | ![img](https://www.rabbitmq.com/img/tutorials/python-three-overall.png) 147 | 148 | 用来发送日志消息的生产者程序看起来跟上个教程中的没多大区别。最重大的改变是,现在我们希望将消息发布到`logs`交换机而不是未命名的那个。发送的时候我们需要提供一个`routingKey,`但是它的值会被扇形交换机忽略掉。下边是`EmitLog.cs`文件: 149 | 150 | ```csharp 151 | using System; 152 | using RabbitMQ.Client; 153 | using System.Text; 154 | 155 | class EmitLog 156 | { 157 | public static void Main(string[] args) 158 | { 159 | var factory = new ConnectionFactory() { HostName = "localhost" }; 160 | using(var connection = factory.CreateConnection()) 161 | using(var channel = connection.CreateModel()) 162 | { 163 | channel.ExchangeDeclare(exchange: "logs", type: "fanout"); 164 | 165 | var message = GetMessage(args); 166 | var body = Encoding.UTF8.GetBytes(message); 167 | channel.BasicPublish(exchange: "logs", 168 | routingKey: "", 169 | basicProperties: null, 170 | body: body); 171 | Console.WriteLine(" [x] Sent {0}", message); 172 | } 173 | 174 | Console.WriteLine(" Press [enter] to exit."); 175 | Console.ReadLine(); 176 | } 177 | 178 | private static string GetMessage(string[] args) 179 | { 180 | return ((args.Length > 0) 181 | ? string.Join(" ", args) 182 | : "info: Hello World!"); 183 | } 184 | } 185 | ``` 186 | 187 | [(EmitLog.cs 源文件)](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/EmitLog/EmitLog.cs) 188 | 189 | 如你所见,建立连接之后,我们对交换机进行了声明。这一步是必需的,因为不允许发布消息到一个不存在的交换机。 190 | 191 | 如果尚未有队列绑定到交换机,消息会丢失掉,但是对我们来说无所谓;如果还没有消费者进行监听,我们可以安全的将消息丢弃掉。 192 | 193 | `ReceiveLogs.cs`的代码: 194 | 195 | ```csharp 196 | using System; 197 | using RabbitMQ.Client; 198 | using RabbitMQ.Client.Events; 199 | using System.Text; 200 | 201 | class ReceiveLogs 202 | { 203 | public static void Main() 204 | { 205 | var factory = new ConnectionFactory() { HostName = "localhost" }; 206 | using(var connection = factory.CreateConnection()) 207 | using(var channel = connection.CreateModel()) 208 | { 209 | channel.ExchangeDeclare(exchange: "logs", type: "fanout"); 210 | 211 | var queueName = channel.QueueDeclare().QueueName; 212 | channel.QueueBind(queue: queueName, 213 | exchange: "logs", 214 | routingKey: ""); 215 | 216 | Console.WriteLine(" [*] Waiting for logs."); 217 | 218 | var consumer = new EventingBasicConsumer(channel); 219 | consumer.Received += (model, ea) => 220 | { 221 | var body = ea.Body; 222 | var message = Encoding.UTF8.GetString(body); 223 | Console.WriteLine(" [x] {0}", message); 224 | }; 225 | channel.BasicConsume(queue: queueName, 226 | autoAck: true, 227 | consumer: consumer); 228 | 229 | Console.WriteLine(" Press [enter] to exit."); 230 | Console.ReadLine(); 231 | } 232 | } 233 | } 234 | ``` 235 | 236 | [(ReceiveLogs.cs 源代码)](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/ReceiveLogs/ReceiveLogs.cs) 237 | 238 | 根据 [教程一](https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html) 所介绍的步骤生成 `EmitLogs` and `ReceiveLogs`项目。 239 | 240 | 如果你想要将日志保存到一个文件中,只需要在控制台中输入: 241 | 242 | ```bash 243 | cd ReceiveLogs 244 | dotnet run > logs_from_rabbit.log 245 | ``` 246 | 247 | 如果你希望在屏幕上看到日志记录,打开一个新的终端并运行: 248 | 249 | ```bash 250 | cd ReceiveLogs 251 | dotnet run 252 | ``` 253 | 254 | 当然,还需要通过以下方式发送日志: 255 | 256 | ```bash 257 | cd EmitLog 258 | dotnet run 259 | ``` 260 | 261 | 使用`rabbitmqctl list_bindings`命令可以验证绑定和队列是否按照我们期望的方式正确运行。当有两个`ReceiveLogs.cs`程序运行的时候,你应该可以看到类似于这样的信息: 262 | 263 | 264 | 265 | ```bash 266 | sudo rabbitmqctl list_bindings 267 | # => Listing bindings ... 268 | # => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue [] 269 | # => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue [] 270 | # => ...done. 271 | ``` 272 | 273 | 简单地对结果进行下解释:跟我们期待的一样,数据从`logs`交换机传输到两个由服务器命名的队列当中。 274 | 275 | 接下来,我们可以移步[教程4](https://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html)来了解如何监听消息的子集。 276 | -------------------------------------------------------------------------------- /published/tutorials_with_csharp/routing.md: -------------------------------------------------------------------------------- 1 | >原文:[Routing](https://www.rabbitmq.com/tutorials/tutorial-four-dotnet.html) 2 | >翻译:[mr-ping](http://rabbitmq.mr-ping.com) 3 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 4 | 5 | > ### 前置条件 6 | 7 | > 本教程假设RabbitMQ已经[安装](http://www.rabbitmq.com/download.html)在你本机的 (5672)端口。如果你使用了不同的主机、端口或者凭证,连接设置就需要作出一些对应的调整。 8 | 9 | > ### 如何获得帮助 10 | 11 | > 如果你在使用本教程的过程中遇到了麻烦,你可以通过邮件列表来[联系我们](https://groups.google.com/forum/#!forum/rabbitmq-users)。 12 | 13 | ## 路由 14 | 15 | ### (使用.NET客户端) 16 | 17 | 上个教程中,我们建立了一个简单的日志系统。我们可以将日志消息广播给多个接收者。 18 | 19 | 这个教程中我们会添加一个新功能——让它可以从日志消息中只订阅一个子集。例如,我们只将关键的错误消息定向到日志文件中(从而节省磁盘空间),同时仍旧可以将所有的日志消息打印到控制台上。 20 | 21 | ## 绑定 22 | 23 | 上个例子中,我们已经创建了绑定。代码如下: 24 | 25 | ```csharp 26 | channel.QueueBind(queue: queueName, 27 | exchange: "logs", 28 | routingKey: ""); 29 | ``` 30 | 31 | 一个绑定就是交换机和队列之间的一个关系。可以解读为:目标队列对此交换机的消息感兴趣。 32 | 33 | 绑定可以使用一个格外的`routingKey`参数。为了避免跟`BasicPublish`参数混淆,我们称其为绑定键(`binding key`)。以下是如何创建一个绑定键: 34 | 35 | ```csharp 36 | channel.QueueBind(queue: queueName, 37 | exchange: "direct_logs", 38 | routingKey: "black"); 39 | ``` 40 | 41 | 绑定键的实际意义依赖于交换机的类型。对于我们之前使用的扇形交换机来说,会简单的将其值忽略掉。 42 | 43 | ## 直连型交换机 44 | 45 | 上个教程中,我们的日志系统将所有的消息广播给所有的消费者。我们打算对其进行扩展以根据它们的严重性来进行过滤。例如,我们想要将日志写到磁盘上的脚本只接收关键性的错误,而不在警告信息和普通日志消息上浪费磁盘空间。 46 | 47 | 我们之前使用的扇形交换机不能提供足够的灵活性——它只能进行无意识的广播。 48 | 49 | 下面我们使用直连型交换机进行替代。直连型交换机背后的路由算法很简单——消息会传送给绑定键与消息的路由键完全匹配的那个队列。 50 | 51 | 为了说明这点,可以考虑如下设置: 52 | 53 | ![img](https://www.rabbitmq.com/img/tutorials/direct-exchange.png) 54 | 55 | 这种配置下,我们可以看到有两个队列绑定到了直连交换机`X`上。第一个队列用的是橘色(`orange`)绑定键,第二个有两个绑定键,其中一个绑定键是黑色(`black`),另一个绑定键是绿色(`green`)。 56 | 57 | 在此设置中,发布到交换机的带有橘色(`orange`)路由键的消息会被路由给队列`Q1`。带有黑色(`black`)或绿色(`green`)路由键的消息会被路由给`Q2`。其他的消息则会被丢弃。 58 | 59 | ## 多个绑定 60 | 61 | ![img](https://www.rabbitmq.com/img/tutorials/direct-exchange-multiple.png) 62 | 63 | 使用相同的绑定键来绑定多个队列是完全合法的。在我们的例子中,我们可以使用黑色(`black`)绑定键来绑定`X`和`Q1`。那种情况下,直连型交换机的行为就会跟扇形交换机类似,会将消息广播给所有匹配的队列。一个拥有黑色(`black`)路由键的消息会被头送给`Q1`和`Q2`两个队列。 64 | 65 | 使用相同的绑定键来绑定多个队列是完全合法的。在我们的例子中,我们可以使用黑色(`black`)绑定键来绑定`X`和`Q1`。那种情况下,直连型交换机(`direct`)的行为就会跟扇形交换机(`fanout`)类似,会将消息广播给所有匹配的队列。一个拥有黑色(`black`)路由键的消息会被头送给`Q1`和`Q2`两个队列。 66 | 67 | ## 发送日志 68 | 69 | 我们将会在我们日志系统中采用这种模式,将消息发送给直连交换机来替代扇形交换机。我们会提供日志的严重等级来作为路由键的值。通过这种方式脚本就可以选择其需要的严重等级来进行接收。首先让我们将关注点放到发送日志上: 70 | 71 | 像往常一样,首先我们需要创建一个交换机: 72 | 73 | ```csharp 74 | channel.ExchangeDeclare(exchange: "direct_logs", type: "direct"); 75 | ``` 76 | 77 | 然后,做好发送消息的准备: 78 | 79 | ```csharp 80 | var body = Encoding.UTF8.GetBytes(message); 81 | channel.BasicPublish(exchange: "direct_logs", 82 | routingKey: severity, 83 | basicProperties: null, 84 | body: body); 85 | ``` 86 | 87 | 为了保持简洁,我们假设严重等级只可以是'info', 'warning', 'error'其中一种。 88 | 89 | ## 订阅 90 | 91 | 除了我们会为每个我们感兴趣的严重等级创建一个新的绑定键之外,接收消息的工作方式跟前一个教程中几乎一样。 92 | 93 | ```csharp 94 | var queueName = channel.QueueDeclare().QueueName; 95 | 96 | foreach(var severity in args) 97 | { 98 | channel.QueueBind(queue: queueName, 99 | exchange: "direct_logs", 100 | routingKey: severity); 101 | } 102 | ``` 103 | 104 | ## 整合到一起 105 | 106 | ![img](https://www.rabbitmq.com/img/tutorials/python-four.png) 107 | 108 | `EmitLogDirect.cs`类的代码: 109 | 110 | 111 | ```csharp 112 | using System; 113 | using System.Linq; 114 | using RabbitMQ.Client; 115 | using System.Text; 116 | 117 | class EmitLogDirect 118 | { 119 | public static void Main(string[] args) 120 | { 121 | var factory = new ConnectionFactory() { HostName = "localhost" }; 122 | using(var connection = factory.CreateConnection()) 123 | using(var channel = connection.CreateModel()) 124 | { 125 | channel.ExchangeDeclare(exchange: "direct_logs", 126 | type: "direct"); 127 | 128 | var severity = (args.Length > 0) ? args[0] : "info"; 129 | var message = (args.Length > 1) 130 | ? string.Join(" ", args.Skip( 1 ).ToArray()) 131 | : "Hello World!"; 132 | var body = Encoding.UTF8.GetBytes(message); 133 | channel.BasicPublish(exchange: "direct_logs", 134 | routingKey: severity, 135 | basicProperties: null, 136 | body: body); 137 | Console.WriteLine(" [x] Sent '{0}':'{1}'", severity, message); 138 | } 139 | 140 | Console.WriteLine(" Press [enter] to exit."); 141 | Console.ReadLine(); 142 | } 143 | } 144 | ``` 145 | 146 | `ReceiveLogsDirect.cs`的代码: 147 | 148 | ```csharp 149 | using System; 150 | using RabbitMQ.Client; 151 | using RabbitMQ.Client.Events; 152 | using System.Text; 153 | 154 | class ReceiveLogsDirect 155 | { 156 | public static void Main(string[] args) 157 | { 158 | var factory = new ConnectionFactory() { HostName = "localhost" }; 159 | using(var connection = factory.CreateConnection()) 160 | using(var channel = connection.CreateModel()) 161 | { 162 | channel.ExchangeDeclare(exchange: "direct_logs", 163 | type: "direct"); 164 | var queueName = channel.QueueDeclare().QueueName; 165 | 166 | if(args.Length < 1) 167 | { 168 | Console.Error.WriteLine("Usage: {0} [info] [warning] [error]", 169 | Environment.GetCommandLineArgs()[0]); 170 | Console.WriteLine(" Press [enter] to exit."); 171 | Console.ReadLine(); 172 | Environment.ExitCode = 1; 173 | return; 174 | } 175 | 176 | foreach(var severity in args) 177 | { 178 | channel.QueueBind(queue: queueName, 179 | exchange: "direct_logs", 180 | routingKey: severity); 181 | } 182 | 183 | Console.WriteLine(" [*] Waiting for messages."); 184 | 185 | var consumer = new EventingBasicConsumer(channel); 186 | consumer.Received += (model, ea) => 187 | { 188 | var body = ea.Body; 189 | var message = Encoding.UTF8.GetString(body); 190 | var routingKey = ea.RoutingKey; 191 | Console.WriteLine(" [x] Received '{0}':'{1}'", 192 | routingKey, message); 193 | }; 194 | channel.BasicConsume(queue: queueName, 195 | autoAck: true, 196 | consumer: consumer); 197 | 198 | Console.WriteLine(" Press [enter] to exit."); 199 | Console.ReadLine(); 200 | } 201 | } 202 | } 203 | ``` 204 | 205 | 跟往常一样创建项目(参见 [教程一](https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html) ) 206 | 207 | 如果你只希望将'warning' 和 'error' (不包括 'info') 的日志信息保存到文件中,只需要打开一个控制台,输入: 208 | 209 | ```bash 210 | cd ReceiveLogsDirect 211 | dotnet run warning error > logs_from_rabbit.log 212 | ``` 213 | 214 | 如果你希望将所有的日志信息显示在屏幕上,新开一个终端,做如下操作: 215 | 216 | ```bash 217 | cd ReceiveLogsDirect 218 | dotnet run info warning error 219 | # => [*] Waiting for logs. To exit press CTRL+C 220 | ``` 221 | 222 | 例如,如果你想发送一条`error`的日志信息,只需要输入: 223 | 224 | ```bash 225 | cd EmitLogDirect 226 | dotnet run error "Run. Run. Or it will explode." 227 | # => [x] Sent 'error':'Run. Run. Or it will explode.' 228 | ``` 229 | 230 | (完整的 [(EmitLogDirect.cs 源代码)](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/EmitLogDirect/EmitLogDirect.cs) 和 [(ReceiveLogsDirect.cs 源代码)](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/ReceiveLogsDirect/ReceiveLogsDirect.cs)) 231 | 232 | 想要了解如何基于一种模式来监听消息,可以移步至 [教程 5](https://www.rabbitmq.com/tutorials/tutorial-five-dotnet.html) 。 233 | -------------------------------------------------------------------------------- /published/tutorials_with_csharp/rpc.md: -------------------------------------------------------------------------------- 1 | >原文:[Remote procedure call RPC](https://www.rabbitmq.com/tutorials/tutorial-six-dotnet.html) 2 | >翻译:[mr-ping](http://rabbitmq.mr-ping.com) 3 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 4 | 5 | > ### 前置条件 6 | 7 | > 本教程假设RabbitMQ已经[安装](http://www.rabbitmq.com/download.html)在你本机的 (5672)端口。如果你使用了不同的主机、端口或者凭证,连接设置就需要作出一些对应的调整。 8 | 9 | > ### 如何获得帮助 10 | 11 | > 如果你在使用本教程的过程中遇到了麻烦,你可以通过邮件列表来[联系我们](https://groups.google.com/forum/#!forum/rabbitmq-users)。 12 | 13 | ## 远程过程调用(RPC) 14 | 15 | ### (使用 .NET 客户端) 16 | 17 | 18 | 在[第二个教程](https://www.rabbitmq.com/tutorials/tutorial-two-dotnet.html)中,我们学习了如何使用*工作队列* 在多个工作者之间分配耗时任务。 19 | 20 | 不过如果我们需要在一个远程电脑上运行函数并且等待结果的时候会怎样呢。这又是另一个故事了。这种模式通常被称为*远程过程调用*或者*RPC*。 21 | 22 | 这个教程里,我们用RabbitMQ来建立一个RPC系统:一个客户端和一个可扩展的RPC服务器。因为没有任何耗时任务用于分发,我们会创建一个伪造的用来返回斐波那契数列的RPC服务。 23 | 24 | ### 客户端接口 25 | 26 | 为了说明如何使用一个RPC服务,我们创建一个简单的客户端类。它会暴露一个名为`Call`的方法,此方法发送一个RPC请求,然后阻塞到收到回答为止。 27 | 28 | ```csharp 29 | var rpcClient = new RPCClient(); 30 | 31 | Console.WriteLine(" [x] Requesting fib(30)"); 32 | var response = rpcClient.Call("30"); 33 | Console.WriteLine(" [.] Got '{0}'", response); 34 | 35 | rpcClient.Close(); 36 | ``` 37 | 38 | > #### 有关RPC的说明 39 | > 40 | > 虽然RPC在运算中是个非常常见的模式,但是也常常被批评。问题出在程序员察觉不到函数调用是发生在本地还是发生在一个很慢的RPC当中。这种困惑导致了系统的不可预测性并且为调试增加了不必要的复杂度。跟简单的软件相比,RPC会导致不可维护的面条式代码(译者注:[面条式代码](https://bkso.baidu.com/item/%E9%9D%A2%E6%9D%A1%E5%BC%8F%E4%BB%A3%E7%A0%81)在软件工程中是一种典型的反面模式)。 41 | > 42 | > 考虑到这点,请斟酌以下建议: 43 | > 44 | > - 确保一眼就能看出来哪个方法是本地执行的,哪个方法是远程执行的。 45 | > - 给你的系统写好文档。让组件之间的依赖清晰可查。 46 | > - 处理错误用例。如果RPC宕掉的时间过长,客户端该如何反应。 47 | > 48 | > 当有疑虑的时候,请避免实用RPC。有可能的话,应该使用异步管道来替代RPC式的阻塞,从而将结果异步地推送给下一个计算阶段。 49 | 50 | ### 回调队列 51 | 52 | 通常情况下,通过RabbitMQ来使用RPC非常简单。一个客户端发送请求消息,一个服务器返回消息来进行响应。为了能够收到响应,我们需要发送一个带有回调队列地址的请求: 53 | 54 | ```csharp 55 | var props = channel.CreateBasicProperties(); 56 | props.ReplyTo = replyQueueName; 57 | 58 | var messageBytes = Encoding.UTF8.GetBytes(message); 59 | channel.BasicPublish(exchange: "", 60 | routingKey: "rpc_queue", 61 | basicProperties: props, 62 | body: messageBytes); 63 | 64 | // ... then code to read a response message from the callback_queue ... 65 | // ... 然后代码从回调队列中读取返回的消息 ... 66 | ``` 67 | 68 | > #### 消息属性 69 | > 70 | > AMQP 0-9-1 协议与定义了14个消息的属性。大部分属性很少会用到,不过以下几个例外: 71 | > 72 | > - `Persistent`: 标示此信息为持久的(用值为2来表示)还是暂时的(2之外的其他任何值)。具体可以去 [第二个教程](https://www.rabbitmq.com/tutorials/tutorial-two-dotnet.html) 一探究竟。 73 | > - `DeliveryMode`:那些熟悉协议的人可能会选择使用这个属性,而不是`Persistent`。他们办的是一个事情。 74 | > - `ContentType`:用来描述编码的mime-type。例如对于经常用到的JSON编码来说,将此属性设置为application/json是一个很好的做法。 75 | > - `ReplyTo`: 通常用来对一个回调队列进行命名。 76 | > - `CorrelationId`: 用于将RPC响应和请求进行关联。 77 | 78 | ### 关联id 79 | 80 | 上面介绍的方法中,我们建议为每一个RPC请求创建一个回调队列。这样做效率很低,幸好我们有更好的解决办法,让我们为每个客户端创建一个单独的回调队列。 81 | 82 | 这样又有一个新问题,当我们从此队列里收到一个响应的时候并不清楚它是属于哪个请求的。这就是`CorrelationId`属性的用途所在了。我们为每一个请求将其设置为一个唯一值。稍后,当我们从回调队列里收到一条消息的时候,就可以通过这个属性来对响应和请求进行匹配。如果我们收到的消息的`CorrelationId`值是未知的,那就可以安心的把它丢弃掉,因为它并不属于我们的请求。 83 | 84 | 或许你会问,我们为什么不把回调队列里的未知消息当成错误的失败来处理,而是要把它忽略掉?这是由于服务器端存在竞争条件的可能。虽然可能性不大,但是RPC服务器是有可能在发送给我们回应之后挂掉的,可此时它并没有完成为请求发回确认(acknowledgment)的动作。一旦这种情况发生,RPC服务器会再次将那条请求处理一遍。这就是为什么我们需要在客户端里优雅的处理重复的响应,并且在理想情况下,RPC应该是幂等的。 85 | 86 | ### 总结 87 | 88 | ![img](https://www.rabbitmq.com/img/tutorials/python-six.png) 89 | 90 | 我们的RPC看起来是这样的: 91 | 92 | - 当客户端启动时,会创建一个匿名的独占回调队列。 93 | - 客户端发送一条带有`ReplyTo`和`CorrelationId`两个属性的消息作为一个RPC请求,`ReplyTo`用于设置回调队列,`CorrelationId`用于为每一个请求设置一个独一无二的值。 94 | - 请求被发送到一个`rpc_queue`队列。 95 | - RPC工作者(也称为服务器)等待从那个队列中接受请求。当一个请求出现的时候,他会执行任务并且通过`ReplyTo` 属性所提及的队列来将带有执行结果的消息发回给客户端。 96 | - 客户端从回调队列那儿等待数据。当消息出现的时候,它会检查`CorrelationId`属性。如果属性值跟请求相匹配,就将响应返回给应用。 97 | 98 | ## 将代码整合到一起 99 | 100 | 斐波那契任务: 101 | 102 | ```csharp 103 | private static int fib(int n) 104 | { 105 | if (n == 0 || n == 1) return n; 106 | return fib(n - 1) + fib(n - 2); 107 | } 108 | ``` 109 | 110 | 我们定义了斐波那契函数。函数假设输入值是合法的正整数。(不要期望这个函数能作用于很大的数字,它可能是最慢的递归实现了)。 111 | 112 | RPC服务器代码:[RPCServer.cs](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/RPCServer/RPCServer.cs) 看起来像这样: 113 | 114 | ```csharp 115 | using System; 116 | using RabbitMQ.Client; 117 | using RabbitMQ.Client.Events; 118 | using System.Text; 119 | 120 | class RPCServer 121 | { 122 | public static void Main() 123 | { 124 | var factory = new ConnectionFactory() { HostName = "localhost" }; 125 | using (var connection = factory.CreateConnection()) 126 | using (var channel = connection.CreateModel()) 127 | { 128 | channel.QueueDeclare(queue: "rpc_queue", durable: false, 129 | exclusive: false, autoDelete: false, arguments: null); 130 | channel.BasicQos(0, 1, false); 131 | var consumer = new EventingBasicConsumer(channel); 132 | channel.BasicConsume(queue: "rpc_queue", 133 | autoAck: false, consumer: consumer); 134 | Console.WriteLine(" [x] Awaiting RPC requests"); 135 | 136 | consumer.Received += (model, ea) => 137 | { 138 | string response = null; 139 | 140 | var body = ea.Body; 141 | var props = ea.BasicProperties; 142 | var replyProps = channel.CreateBasicProperties(); 143 | replyProps.CorrelationId = props.CorrelationId; 144 | 145 | try 146 | { 147 | var message = Encoding.UTF8.GetString(body); 148 | int n = int.Parse(message); 149 | Console.WriteLine(" [.] fib({0})", message); 150 | response = fib(n).ToString(); 151 | } 152 | catch (Exception e) 153 | { 154 | Console.WriteLine(" [.] " + e.Message); 155 | response = ""; 156 | } 157 | finally 158 | { 159 | var responseBytes = Encoding.UTF8.GetBytes(response); 160 | channel.BasicPublish(exchange: "", routingKey: props.ReplyTo, 161 | basicProperties: replyProps, body: responseBytes); 162 | channel.BasicAck(deliveryTag: ea.DeliveryTag, 163 | multiple: false); 164 | } 165 | }; 166 | 167 | Console.WriteLine(" Press [enter] to exit."); 168 | Console.ReadLine(); 169 | } 170 | } 171 | 172 | /// 173 | /// Assumes only valid positive integer input. 174 | /// Don't expect this one to work for big numbers, and it's 175 | /// probably the slowest recursive implementation possible. 176 | /// 177 | /// 假设输入值只能是合法的正整数。 178 | /// 不要期望它能服务于很大的数字,它可能是最慢的递归实现了。 179 | /// 180 | private static int fib(int n) 181 | { 182 | if (n == 0 || n == 1) 183 | { 184 | return n; 185 | } 186 | 187 | return fib(n - 1) + fib(n - 2); 188 | } 189 | } 190 | ``` 191 | 192 | 服务器代码很简单: 193 | 194 | - 跟之前一样,一开始我们建立连接、信道,并且声明队列。 195 | - 可能我们会希望运行多个服务器进程。为了在多个服务器间均分负载,我们需要在`channel.BasicQos`中设置`prefetchCount`。 196 | - 我们使用`BasicConsume`去访问队列。然后我们注册一个投递处理程序,我们在这个处理程序中完成工作并发回响应。 197 | 198 | RPC客户端代码 [RPCClient.cs](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/RPCClient/RPCClient.cs): 199 | 200 | ```csharp 201 | using System; 202 | using System.Collections.Concurrent; 203 | using System.Text; 204 | using RabbitMQ.Client; 205 | using RabbitMQ.Client.Events; 206 | 207 | public class RpcClient 208 | { 209 | private readonly IConnection connection; 210 | private readonly IModel channel; 211 | private readonly string replyQueueName; 212 | private readonly EventingBasicConsumer consumer; 213 | private readonly BlockingCollection respQueue = new BlockingCollection(); 214 | private readonly IBasicProperties props; 215 | 216 | public RpcClient() 217 | { 218 | var factory = new ConnectionFactory() { HostName = "localhost" }; 219 | 220 | connection = factory.CreateConnection(); 221 | channel = connection.CreateModel(); 222 | replyQueueName = channel.QueueDeclare().QueueName; 223 | consumer = new EventingBasicConsumer(channel); 224 | 225 | props = channel.CreateBasicProperties(); 226 | var correlationId = Guid.NewGuid().ToString(); 227 | props.CorrelationId = correlationId; 228 | props.ReplyTo = replyQueueName; 229 | 230 | consumer.Received += (model, ea) => 231 | { 232 | var body = ea.Body; 233 | var response = Encoding.UTF8.GetString(body); 234 | if (ea.BasicProperties.CorrelationId == correlationId) 235 | { 236 | respQueue.Add(response); 237 | } 238 | }; 239 | } 240 | 241 | public string Call(string message) 242 | { 243 | var messageBytes = Encoding.UTF8.GetBytes(message); 244 | channel.BasicPublish( 245 | exchange: "", 246 | routingKey: "rpc_queue", 247 | basicProperties: props, 248 | body: messageBytes); 249 | 250 | channel.BasicConsume( 251 | consumer: consumer, 252 | queue: replyQueueName, 253 | autoAck: true); 254 | 255 | return respQueue.Take(); ; 256 | } 257 | 258 | public void Close() 259 | { 260 | connection.Close(); 261 | } 262 | } 263 | 264 | public class Rpc 265 | { 266 | public static void Main() 267 | { 268 | var rpcClient = new RpcClient(); 269 | 270 | Console.WriteLine(" [x] Requesting fib(30)"); 271 | var response = rpcClient.Call("30"); 272 | 273 | Console.WriteLine(" [.] Got '{0}'", response); 274 | rpcClient.Close(); 275 | } 276 | } 277 | ``` 278 | 279 | 客户端代码稍显复杂: 280 | 281 | - 我们创建一个连接和信道,并且声明一个独享的`callback`队列用于回复。 282 | - 我们订阅`callback`队列,这样就可以接收到RPC的响应。 283 | - 我们的`Call`方法生成实际的RPC请求。 284 | - 这里,我们首先生成一个唯一的`CorrelationId`数字并且保存起来——整个循环都会使用这个值来获取相对应的响应。 285 | - 接下来,我们发送带有`ReplyTo` 和 `CorrelationId`属性的请求信息。 286 | - 此刻,我们可以坐等匹配的回应到来。 287 | - 整个循环所做的工作很简单,就是检查每个响应消息,看它们是不是我们需要的那个。如果是的话就将响应保存起来。 288 | - 最后我们将响应返回给用户。 289 | 290 | 生成客户端请求: 291 | 292 | ```csharp 293 | var rpcClient = new RPCClient(); 294 | 295 | Console.WriteLine(" [x] Requesting fib(30)"); 296 | var response = rpcClient.Call("30"); 297 | Console.WriteLine(" [.] Got '{0}'", response); 298 | 299 | rpcClient.Close(); 300 | ``` 301 | 302 | 现在我们可以看看 [RPCClient.cs](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/RPCClient/RPCClient.cs) 和 [RPCServer.cs](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/dotnet/RPCServer/RPCServer.cs) 的完整的样例代码了(代码里包含了简单的异常处理)。 303 | 304 | 像往常一样进行设置(见 [tutorial one](https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html)): 305 | 306 | RPC服务已经就绪,让我们启动它: 307 | 308 | ```bash 309 | cd RPCServer 310 | dotnet run 311 | # => [x] Awaiting RPC requests 312 | ``` 313 | 314 | 运行客户端来请求一个斐波那契数: 315 | 316 | ```bash 317 | cd RPCClient 318 | dotnet run 319 | # => [x] Requesting fib(30) 320 | ``` 321 | 322 | 这里所呈现的设计方式并不是实现RPC服务的唯一方法,但是它具备一些重要的优势: 323 | 324 | - 如果RPC服务器过慢,你可以通过再运行一个服务器来进行扩展。试试在一个新的控制台中运行第二个`RPCServer`吧。 325 | - 在客户端这边,RPC只需要发送和接收一条消息。不需要类似`QueueDeclare`这样的同步调用。因此RPC客户端在一次RPC请求中,只需要进行一次网络的往返。 326 | 327 | 我们的代码依旧很简洁,并且只去尝试解决重要的而不是更加复杂的问题,比如: 328 | 329 | - 如果没有服务器运行,客户端是不是要做出反应? 330 | - 客户端是不是需要有针对RPC的某种超时设置? 331 | - 如果服务器发生故障,抛出异常,是不是需要转发给客户端? 332 | - 在进行处理之前防止无效的消息传入(比如检查边界、类型)。 333 | 334 | > 如果你想进行一下实验。会发现[管理界面](https://www.rabbitmq.com/management.html) 对于浏览队列来说用处很大。 335 | -------------------------------------------------------------------------------- /published/tutorials_with_golang/[1]Hello_World.md: -------------------------------------------------------------------------------- 1 | >原文:[Hello_World](https://www.rabbitmq.com/tutorials/tutorial-one-go.html) 2 | >状态:待校对 3 | >翻译:[Bingjian-Zhu](https://bingjian-zhu.github.io/) 4 | >校对: 5 | 6 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 7 | 8 | ## 前提条件 9 | > 本教程假设RabbitMQ已经安装在你本机的 (5672)端口。如果你使用了不同的主机、端口或者凭证,连接设置就需要作出一些对应的调整。 10 | 11 | 12 | 13 | ## 介绍 14 | RabbitMQ是一个消息代理。它的工作就是接收和转发消息。你可以把它想像成一个邮局:你把信件放入邮箱,邮递员就会把信件投递到你的收件人处。在这个比喻中,RabbitMQ就扮演着邮箱、邮局以及邮递员的角色。 15 | 16 | RabbitMQ和邮局的主要区别在于,它处理纸张,而是接收、存储和发送消息(message)这种二进制数据。 17 | 18 | 下面是RabbitMQ和消息所涉及到的一些术语。 19 | 20 | * 生产(Producing)的意思就是发送。发送消息的程序就是一个生产者(producer)。我们一般用"P"来表示: 21 | ![img](http://www.rabbitmq.com/img/tutorials/producer.png) 22 | * 队列(queue)就是存在于RabbitMQ中邮箱的名称。虽然消息的传输经过了RabbitMQ和你的应用程序,但是它只能被存储于队列当中。实质上队列就是个巨大的消息缓冲区,它的大小只受主机内存和硬盘限制。多个生产者(producers)可以把消息发送给同一个队列,同样,多个消费者(consumers)也能够从同一个队列(queue)中获取数据。队列可以绘制成这样(图上是队列的名称): 23 | ![img](http://www.rabbitmq.com/img/tutorials/queue.png) 24 | * 在这里,消费(Consuming)和接收(receiving)是同一个意思。一个消费者(consumer)就是一个等待获取消息的程序。我们把它绘制为"C": 25 | ![img](http://www.rabbitmq.com/img/tutorials/consumer.png) 26 | 27 | 28 | ## Hello World! 29 | 30 | **(使用Go客户端)** 31 | 32 | 在本教程的这一部分中,我们将在Go中编写两个小程序:发送单个消息的生产者,以及接收消息并将其打印出来的消费者。我们将忽略Go RabbitMQ API中的一些细节,这里传递“Hello World”消息。 33 | 在下图中,“P”是生产者,“C”是消费者。中间的框是一个队列(保存消息的地方)。 34 | ![(P) -> [|||] -> (C)](http://www.rabbitmq.com/img/tutorials/python-one.png) 35 | 36 | 37 | >**Go RabbitMQ客户端库** 38 | > RabbitMQ使用的是AMQP 0.9.1协议。这是一个用于消息传递的开放、通用的协议。针对不同编程语言有大量的RabbitMQ客户端可用。在本教程中,我们将使用Go amqp客户端。 39 | 40 | >首先,使用`go get`安装amqp:`go get github.com/streadway/amqp` 41 | 42 | ### 发送 43 | ![(P) -> [|||]](http://www.rabbitmq.com/img/tutorials/sending.png) 44 | 45 | 我们将编写消息生产者(发送者)`send.go`和我们的消息消费者(接收者)`receive.go`。 46 | 发布者将连接到RabbitMQ,发送单个消息,然后退出。 47 | 在`send.go`中,我们需要先导入包: 48 | ```go 49 | package main 50 | 51 | import ( 52 | "log" 53 | 54 | "github.com/streadway/amqp" 55 | ) 56 | ``` 57 | 我们还需要一个辅助函数来检查每个amqp调用的返回值 58 | ```go 59 | func failOnError(err error, msg string) { 60 | if err != nil { 61 | log.Fatalf("%s: %s", msg, err) 62 | } 63 | } 64 | ``` 65 | 然后连接到RabbitMQ服务器 66 | ```go 67 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 68 | failOnError(err, "Failed to connect to RabbitMQ") 69 | defer conn.Close() 70 | ``` 71 | 配置连接套接字,它主要定义连接的协议和身份验证等。接下来,我们创建一个channel来传递消息: 72 | ```go 73 | ch, err := conn.Channel() 74 | failOnError(err, "Failed to open a channel") 75 | defer ch.Close() 76 | ``` 77 | 发送前,我们必须声明一个队列供我们发送,然后才能向队列发送消息: 78 | ```go 79 | q, err := ch.QueueDeclare( 80 | "hello", // name 81 | false, // durable 82 | false, // delete when unused 83 | false, // exclusive 84 | false, // no-wait 85 | nil, // arguments 86 | ) 87 | failOnError(err, "Failed to declare a queue") 88 | 89 | body := "Hello World!" 90 | err = ch.Publish( 91 | "", // exchange 92 | q.Name, // routing key 93 | false, // mandatory 94 | false, // immediate 95 | amqp.Publishing { 96 | ContentType: "text/plain", 97 | Body: []byte(body), 98 | }) 99 | failOnError(err, "Failed to publish a message") 100 | ``` 101 | 声明队列是幂等的——只有在它不存在的情况下才会创建它。消息内容是一个字节数组,因此你可以编写任何内容。 102 | ![[|||] -> (C)](http://www.rabbitmq.com/img/tutorials/receiving.png) 103 | 104 | ### 接收 105 | 以上是消息生产者。我们的消费者需要监听来自RabbitMQ的消息,因此与生产者不同,它需要持续运行以监听消息并将其打印出来 106 | ![](http://pwzr2sh3s.bkt.clouddn.com/RabbitMQ/Hello%20World/6.png) 107 | 代码`receive.go`具有与send相同的导入包和辅助函数: 108 | ```go 109 | package main 110 | 111 | import ( 112 | "log" 113 | 114 | "github.com/streadway/amqp" 115 | ) 116 | 117 | func failOnError(err error, msg string) { 118 | if err != nil { 119 | log.Fatalf("%s: %s", msg, err) 120 | } 121 | } 122 | ``` 123 | 设置与生产者相同,首先打开一个连接和一个Channel,并声明我们要消费的队列。请注意,这与发送的队列相匹配 124 | ```go 125 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 126 | failOnError(err, "Failed to connect to RabbitMQ") 127 | defer conn.Close() 128 | 129 | ch, err := conn.Channel() 130 | failOnError(err, "Failed to open a channel") 131 | defer ch.Close() 132 | 133 | q, err := ch.QueueDeclare( 134 | "hello", // name 135 | false, // durable 136 | false, // delete when usused 137 | false, // exclusive 138 | false, // no-wait 139 | nil, // arguments 140 | ) 141 | failOnError(err, "Failed to declare a queue") 142 | ``` 143 | 注意,在这里也做了队列声明。因为消费者可能在生产者启动前就运行了,所以要确保使用消息之前队列已经存在。 144 | 我们即将告诉服务器从队列中传递消息。因为它会异步地向我们发送消息,所以我们将在goroutine中读取来自channel (由`amqp :: Consume`返回)的消息。 145 | ```go 146 | msgs, err := ch.Consume( 147 | q.Name, // queue 148 | "", // consumer 149 | true, // auto-ack 150 | false, // exclusive 151 | false, // no-local 152 | false, // no-wait 153 | nil, // args 154 | ) 155 | failOnError(err, "Failed to register a consumer") 156 | 157 | forever := make(chan bool) 158 | 159 | go func() { 160 | for d := range msgs { 161 | log.Printf("Received a message: %s", d.Body) 162 | } 163 | }() 164 | 165 | log.Printf(" [*] Waiting for messages. To exit press CTRL+C") 166 | <-forever 167 | ``` 168 | [receive.go](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/receive.go) 169 | 170 | ### 代码整合 171 | 运行生产者:`go run send.go` 172 | 运行消费者:`go run receive.go` 173 | 消费者将通过RabbitMQ打印从生产者处获得的消息。消费者将继续运行,等待消息(使用Ctrl-C停止消息)。可以尝试从另一个终端再次运行生产者来发送消息。 174 | 175 | 我们已经学会如何发送消息到一个已知队列中并接收消息。是时候移步到第二部分了,我们将会建立一个简单的工作队列(work queue)。 176 | -------------------------------------------------------------------------------- /published/tutorials_with_golang/[2]Work_Queues.md: -------------------------------------------------------------------------------- 1 | >原文:[Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-go.html) 2 | >状态:待校对 3 | >翻译:[Bingjian-Zhu](https://bingjian-zhu.github.io/) 4 | >校对: 5 | 6 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 7 | 8 | ## 工作队列 9 | 10 | **(使用Go客户端)** 11 | 12 | 13 | 14 | ![](http://www.rabbitmq.com/img/tutorials/python-two.png) 15 | 16 | 在[第一篇教程](https://bingjian-zhu.github.io/2019/09/01/RabbitMQ%E6%95%99%E7%A8%8B%EF%BC%88%E8%AF%91%EF%BC%89-Hello-World/)中,我们已经写了一个从已知队列中发送和获取消息的程序。在这篇教程中,我们将创建一个工作队列(Work Queue),它会发送一些耗时的任务给多个工作者(Worker)。 17 | 18 | 工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息发送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在它们之间共享。 19 | 20 | 这个概念在网络应用中是非常有用的,它可以在短暂的HTTP请求中处理一些复杂的任务。 21 | 22 | ## 准备 23 | 24 | 之前的教程中,我们发送了一个包含“Hello World!”的字符串消息。现在,我们将发送一些字符串,把这些字符串当作复杂的任务。我们没有真实的例子,例如图片缩放、pdf文件转换。所以使用time.sleep()函数来模拟这种情况。我们在字符串中加上点号(.)来表示任务的复杂程度,一个点(.)将会耗时1秒钟。比如"Hello..."就会耗时3秒钟。 25 | 26 | 我们将稍微修改前面示例中的`send.go`代码,以允许从命令行发送任意消息。该程序将发送任务到我们的工作队列,所以我们将其命名为`new_task.go`: 27 | 28 | ```go 29 | body := bodyFrom(os.Args) 30 | err = ch.Publish( 31 | "", // exchange 32 | q.Name, // routing key 33 | false, // mandatory 34 | false, 35 | amqp.Publishing { 36 | DeliveryMode: amqp.Persistent, 37 | ContentType: "text/plain", 38 | Body: []byte(body), 39 | }) 40 | failOnError(err, "Failed to publish a message") 41 | log.Printf(" [x] Sent %s", body) 42 | ``` 43 | 44 | 我们旧的`receive.go`也需要进行一些更改:它需要为消息体中每一个点号(.)模拟1秒钟的操作。它会从队列中获取消息并执行,我们把它命名为`worker.go`: 45 | 46 | ```go 47 | msgs, err := ch.Consume( 48 | q.Name, // queue 49 | "", // consumer 50 | true, // auto-ack 51 | false, // exclusive 52 | false, // no-local 53 | false, // no-wait 54 | nil, // args 55 | ) 56 | failOnError(err, "Failed to register a consumer") 57 | 58 | forever := make(chan bool) 59 | 60 | go func() { 61 | for d := range msgs { 62 | log.Printf("Received a message: %s", d.Body) 63 | dot_count := bytes.Count(d.Body, []byte(".")) 64 | t := time.Duration(dot_count) 65 | time.Sleep(t * time.Second) 66 | log.Printf("Done") 67 | } 68 | }() 69 | 70 | log.Printf(" [*] Waiting for messages. To exit press CTRL+C") 71 | <-forever 72 | ``` 73 | 请注意,我们的假任务模拟执行时间。 74 | 像在教程一中那样运行它们 75 | ``` shell 76 | # shell 1 77 | go run worker.go 78 | ``` 79 | ``` shell 80 | # shell 2 81 | go run new_task.go 82 | ``` 83 | 84 | ## 循环调度: 85 | 86 | 使用工作队列的一个好处就是它能够并行的处理队列。如果堆积了很多任务,我们只需要添加更多的工作者(workers)就可以了,扩展很简单。 87 | 88 | 首先,我们先同时运行两个`worker.go`,它们都会从队列中获取消息,到底是不是这样呢?我们看看。 89 | 90 | 你需要打开三个终端,两个用来运行`worker.go`,这两个终端就是我们的两个消费者(consumers)—— C1 和 C2。 91 | 92 | ```shell 93 | # shell 1 94 | go run worker.go 95 | # => [*] Waiting for messages. To exit press CTRL+C 96 | ``` 97 | 98 | ```shell 99 | # shell 2 100 | go run worker.go 101 | # => [*] Waiting for messages. To exit press CTRL+C 102 | ``` 103 | 第三个终端,我们用来发布新任务。你可以发送一些消息给消费者(consumers): 104 | 105 | ```shell 106 | # shell 3 107 | go run new_task.go First message. 108 | go run new_task.go Second message.. 109 | go run new_task.go Third message... 110 | go run new_task.go Fourth message.... 111 | go run new_task.go Fifth message..... 112 | ``` 113 | 看看到底发送了什么给我们的工作者(workers): 114 | 115 | ```go 116 | # shell 1 117 | go run worker.go 118 | # => [*] Waiting for messages. To exit press CTRL+C 119 | # => [x] Received 'First message.' 120 | # => [x] Received 'Third message...' 121 | # => [x] Received 'Fifth message.....' 122 | ``` 123 | 124 | ```go 125 | # shell 2 126 | go run worker.go 127 | # => [*] Waiting for messages. To exit press CTRL+C 128 | # => [x] Received 'Second message..' 129 | # => [x] Received 'Fourth message....' 130 | ``` 131 | 默认来说,RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)。试着添加三个或更多得工作者(workers)。 132 | 133 | ## 消息确认 134 | 135 | 当处理一个比较耗时得任务的时候,你也许想知道消费者(consumers)是否运行到一半就挂掉。当前的代码中,当消息被RabbitMQ发送给消费者(consumers)之后,马上就会在内存中移除。这种情况,你只要把一个工作者(worker)停止,正在处理的消息就会丢失。同时,所有发送到这个工作者的还没有处理的消息都会丢失。 136 | 137 | 我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望任务会重新发送给其他的工作者(worker)。 138 | 139 | 为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息。 140 | 141 | 如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,即使工作者(workers)偶尔的挂掉,也不会丢失消息。 142 | 143 | 消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。 144 | 145 | 在本教程中,我们将使用手动消息确认,通过为`auto-ack`参数传递false,一旦有任务完成,使用`d.Ack(false)`向RabbitMQ服务器发送消费完成的确认(这个确认消息是单次传递的)。 146 | 147 | ```go 148 | msgs, err := ch.Consume( 149 | q.Name, // queue 150 | "", // consumer 151 | false, // auto-ack 152 | false, // exclusive 153 | false, // no-local 154 | false, // no-wait 155 | nil, // args 156 | ) 157 | failOnError(err, "Failed to register a consumer") 158 | 159 | forever := make(chan bool) 160 | 161 | go func() { 162 | for d := range msgs { 163 | log.Printf("Received a message: %s", d.Body) 164 | dot_count := bytes.Count(d.Body, []byte(".")) 165 | t := time.Duration(dot_count) 166 | time.Sleep(t * time.Second) 167 | log.Printf("Done") 168 | d.Ack(false) 169 | } 170 | }() 171 | 172 | log.Printf(" [*] Waiting for messages. To exit press CTRL+C") 173 | <-forever 174 | ``` 175 | 运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。当工作者(worker)挂掉这后,所有没有响应的消息都会重新发送。 176 | 177 | > #### 忘记确认 178 | > 忘记ack是一个常见的错误。这是一个简单的错误,但后果是严重的。当客户端退出时,消息将被重新传递(这可能看起来像随机重新传递),但是RabbitMQ将会占用越来越多的内存,因为它无法释放任何未经消息的消息 179 | > 为了排除这种错误,你可以使用`rabbitmqctl`命令,输出`messages_unacknowledged`字段: 180 | 181 | > sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged 182 | 183 | Windows上执行: 184 | 185 | > rabbitmqctl.bat list_queues name messages_ready messages_unacknowledged 186 | 187 | 188 | ## 消息持久化 189 | 190 | 如果你没有特意告诉RabbitMQ,那么在它退出或者崩溃的时候,将会丢失所有队列和消息。为了确保信息不会丢失,有两个事情是需要注意的:我们必须把“队列”和“消息”设为持久化。 191 | 192 | 首先,为了不让队列消失,需要把队列声明为持久化(durable): 193 | ```go 194 | q, err := ch.QueueDeclare( 195 | "hello", // name 196 | true, // durable 197 | false, // delete when unused 198 | false, // exclusive 199 | false, // no-wait 200 | nil, // arguments 201 | ) 202 | failOnError(err, "Failed to declare a queue") 203 | ``` 204 | 205 | 尽管这行代码本身是正确的,但是达不到我们预期的结果。因为我们已经定义过一个叫`hello`的非持久化队列。RabbitMq不允许你使用不同的参数重新定义一个队列,它会返回一个错误。但我们现在使用一个快捷的解决方法——用不同的名字,例如`task_queue`。 206 | 207 | ```go 208 | q, err := ch.QueueDeclare( 209 | "task_queue", // name 210 | true, // durable 211 | false, // delete when unused 212 | false, // exclusive 213 | false, // no-wait 214 | nil, // arguments 215 | ) 216 | failOnError(err, "Failed to declare a queue") 217 | ``` 218 | 219 | 这个`durable`必须在生产者(producer)和消费者(consumer)对应的代码中修改。 220 | 221 | 此时,已经确保即使RabbitMQ重新启动,`task_queue`队列也不会丢失。现在我们需要将消息标记为持久性 - 通过设置`amqp.Publishing`的`amqp.Persistent`属性完成。 222 | 223 | ```go 224 | err = ch.Publish( 225 | "", // exchange 226 | q.Name, // routing key 227 | false, // mandatory 228 | false, 229 | amqp.Publishing { 230 | DeliveryMode: amqp.Persistent, 231 | ContentType: "text/plain", 232 | Body: []byte(body), 233 | }) 234 | ``` 235 | 236 | > #### 注意:消息持久化 237 | > 将消息设为持久化并不能完全保证不会丢失。以上代码只是告诉了RabbitMq要把消息存到硬盘,但从RabbitMq收到消息到保存之间还是有一个很小的间隔时间。因为RabbitMq并不是所有的消息都使用fsync(2)——它有可能只是保存到缓存中,并不一定会写到硬盘中。并不能保证真正的持久化,但已经足够应付我们的简单工作队列。如果您需要更强的保证,那么您可以使用[publisher confirms.](https://www.rabbitmq.com/confirms.html)。 238 | 239 | ## 公平调度 240 | 241 | 你应该已经发现,它仍旧没有按照我们期望的那样进行分发。比如有两个工作者(workers),处理奇数消息的比较繁忙,处理偶数消息的比较轻松。然而RabbitMQ并不知道这些,它仍然一如既往的派发消息。 242 | 243 | 这时因为RabbitMQ只管分发进入队列的消息,不会关心有多少消费者(consumer)没有作出响应。它盲目的把第n-th条消息发给第n-th个消费者。 244 | 245 | ![](http://www.rabbitmq.com/img/tutorials/prefetch-count.png) 246 | 247 | 我们可以设置预取计数值为1。告诉RabbitMQ一次只向一个worker发送一条消息。换句话说,在处理并确认前一个消息之前,不要向工作人员发送新消息。 248 | ```go 249 | err = ch.Qos( 250 | 1, // prefetch count 251 | 0, // prefetch size 252 | false, // global 253 | ) 254 | failOnError(err, "Failed to set QoS") 255 | ``` 256 | 257 | > #### 关于队列大小 258 | > 如果所有的工作者都处理繁忙状态,你的队列就会被填满。你需要留意这个问题,要么添加更多的工作者(workers),要么使用其他策略。 259 | 260 | ## 整合代码 261 | 262 | `new_task.go`的完整代码: 263 | 264 | ```go 265 | package main 266 | 267 | import ( 268 | "log" 269 | "os" 270 | "strings" 271 | 272 | "github.com/streadway/amqp" 273 | ) 274 | 275 | func failOnError(err error, msg string) { 276 | if err != nil { 277 | log.Fatalf("%s: %s", msg, err) 278 | } 279 | } 280 | 281 | func main() { 282 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 283 | failOnError(err, "Failed to connect to RabbitMQ") 284 | defer conn.Close() 285 | 286 | ch, err := conn.Channel() 287 | failOnError(err, "Failed to open a channel") 288 | defer ch.Close() 289 | 290 | q, err := ch.QueueDeclare( 291 | "task_queue", // name 292 | true, // durable 293 | false, // delete when unused 294 | false, // exclusive 295 | false, // no-wait 296 | nil, // arguments 297 | ) 298 | failOnError(err, "Failed to declare a queue") 299 | 300 | body := bodyFrom(os.Args) 301 | err = ch.Publish( 302 | "", // exchange 303 | q.Name, // routing key 304 | false, // mandatory 305 | false, 306 | amqp.Publishing{ 307 | DeliveryMode: amqp.Persistent, 308 | ContentType: "text/plain", 309 | Body: []byte(body), 310 | }) 311 | failOnError(err, "Failed to publish a message") 312 | log.Printf(" [x] Sent %s", body) 313 | } 314 | 315 | func bodyFrom(args []string) string { 316 | var s string 317 | if (len(args) < 2) || os.Args[1] == "" { 318 | s = "hello" 319 | } else { 320 | s = strings.Join(args[1:], " ") 321 | } 322 | return s 323 | } 324 | ``` 325 | 326 | ([new_task.go](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/new_task.go)源码) 327 | 328 | `worker.go`: 329 | 330 | ```go 331 | package main 332 | 333 | import ( 334 | "bytes" 335 | "github.com/streadway/amqp" 336 | "log" 337 | "time" 338 | ) 339 | 340 | func failOnError(err error, msg string) { 341 | if err != nil { 342 | log.Fatalf("%s: %s", msg, err) 343 | } 344 | } 345 | 346 | func main() { 347 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 348 | failOnError(err, "Failed to connect to RabbitMQ") 349 | defer conn.Close() 350 | 351 | ch, err := conn.Channel() 352 | failOnError(err, "Failed to open a channel") 353 | defer ch.Close() 354 | 355 | q, err := ch.QueueDeclare( 356 | "task_queue", // name 357 | true, // durable 358 | false, // delete when unused 359 | false, // exclusive 360 | false, // no-wait 361 | nil, // arguments 362 | ) 363 | failOnError(err, "Failed to declare a queue") 364 | 365 | err = ch.Qos( 366 | 1, // prefetch count 367 | 0, // prefetch size 368 | false, // global 369 | ) 370 | failOnError(err, "Failed to set QoS") 371 | 372 | msgs, err := ch.Consume( 373 | q.Name, // queue 374 | "", // consumer 375 | false, // auto-ack 376 | false, // exclusive 377 | false, // no-local 378 | false, // no-wait 379 | nil, // args 380 | ) 381 | failOnError(err, "Failed to register a consumer") 382 | 383 | forever := make(chan bool) 384 | 385 | go func() { 386 | for d := range msgs { 387 | log.Printf("Received a message: %s", d.Body) 388 | dot_count := bytes.Count(d.Body, []byte(".")) 389 | t := time.Duration(dot_count) 390 | time.Sleep(t * time.Second) 391 | log.Printf("Done") 392 | d.Ack(false) 393 | } 394 | }() 395 | 396 | log.Printf(" [*] Waiting for messages. To exit press CTRL+C") 397 | <-forever 398 | } 399 | ``` 400 | 401 | ([worker.go](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/worker.go) 源码) 402 | 403 | 使用消息响应和prefetch_count你就可以搭建起一个工作队列了。这些持久化的选项使得在RabbitMQ重启之后仍然能够恢复。 404 | 405 | 现在我们可以移步教程3学习如何发送相同的消息给多个消费者(consumers)。 406 | -------------------------------------------------------------------------------- /published/tutorials_with_golang/[3]Publish_Subscribe.md: -------------------------------------------------------------------------------- 1 | >原文:[Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-go.html) 2 | >状态:待校对 3 | >翻译:[Bingjian-Zhu](https://bingjian-zhu.github.io/) 4 | >校对: 5 | 6 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 7 | 8 | ## 发布/订阅 9 | 10 | **(使用Go客户端)** 11 | 12 | 13 | 14 | 在[上篇教程](https://bingjian-zhu.github.io/2019/09/01/RabbitMQ%E6%95%99%E7%A8%8B%EF%BC%88%E8%AF%91%EF%BC%89-Work-queues/)中,我们搭建了一个工作队列,每个任务只分发给一个工作者(worker)。在本篇教程中,我们要做的跟之前完全不一样 —— 分发一个消息给多个消费者(consumers)。这种模式被称为“发布/订阅”。 15 | 16 | 为了描述这种模式,我们将会构建一个简单的日志系统。它包括两个程序——第一个程序负责发送日志消息,第二个程序负责获取消息并输出内容。 17 | 18 | 在我们的这个日志系统中,所有正在运行的接收方程序都会接受消息。我们用其中一个接收者(receiver)把日志写入硬盘中,另外一个接受者(receiver)把日志输出到屏幕上。 19 | 20 | 最终,日志消息被广播给所有的接受者(receivers)。 21 | 22 | ## 交换机(Exchanges) 23 | 24 | 前面的教程中,我们发送消息到队列并从中取出消息。现在是时候介绍RabbitMQ中完整的消息模型了。 25 | 26 | 让我们简单的概括一下之前的教程: 27 | 28 | * 发布者(producer)是发布消息的应用程序。 29 | * 队列(queue)用于消息存储的缓冲。 30 | * 消费者(consumer)是接收消息的应用程序。 31 | 32 | RabbitMQ消息模型的核心理念是:发布者(producer)不会直接发送任何消息给队列。事实上,发布者(producer)甚至不知道消息是否已经被投递到队列。 33 | 34 | 发布者(producer)只需要把消息发送给一个交换机(exchange)。交换机非常简单,它一边从发布者方接收消息,一边把消息推送到队列。交换机必须知道如何处理它接收到的消息,是应该推送到指定的队列还是是多个队列,或者是直接忽略消息。这些规则是通过交换机类型(exchange type)来定义的。 35 | 36 | ![](http://www.rabbitmq.com/img/tutorials/exchanges.png) 37 | 38 | 有几个可供选择的交换机类型:`direct`, `topic`, `headers`和`fanout`。我们在这里主要说明最后一个 —— `fanout`。先创建一个`fanout`类型的交换机,命名为logs: 39 | 40 | ```go 41 | err = ch.ExchangeDeclare( 42 | "logs", // name 43 | "fanout", // type 44 | true, // durable 45 | false, // auto-deleted 46 | false, // internal 47 | false, // no-wait 48 | nil, // arguments 49 | ) 50 | ``` 51 | 52 | `fanout`交换机很简单,你可能从名字上就能猜测出来,它把消息发送给它所知道的所有队列。这正是我们的日志系统所需要的。 53 | 54 | > #### 交换器列表 55 | > `rabbitmqctl`能够列出服务器上所有的交换器:`sudo rabbitmqctl list_exchanges` 56 | > 这个列表中有一些叫做`amq.*`的匿名交换器。这些都是默认创建的,不过这时候你还不需要使用他们。 57 | 58 | > #### 匿名的交换器 59 | > 前面的教程中我们对交换机一无所知,但仍然能够发送消息到队列中。因为我们使用了命名为空字符串("")的匿名交换机。 60 | > 回想我们之前是如何发布一则消息: 61 | > 62 | err = ch.Publish( 63 | "", // exchange 64 | q.Name, // routing key 65 | false, // mandatory 66 | false, // immediate 67 | amqp.Publishing{ 68 | ContentType: "text/plain", 69 | Body: []byte(body), 70 | }) 71 | > exchange参数就是交换机的名称。空字符串代表默认或者匿名交换机,消息将会根据指定的`routing_key`分发到指定的队列。 72 | 73 | 现在,我们就可以发送消息到一个具名交换机了: 74 | 75 | ```go 76 | err = ch.ExchangeDeclare( 77 | "logs", // name 78 | "fanout", // type 79 | true, // durable 80 | false, // auto-deleted 81 | false, // internal 82 | false, // no-wait 83 | nil, // arguments 84 | ) 85 | failOnError(err, "Failed to declare an exchange") 86 | 87 | body := bodyFrom(os.Args) 88 | err = ch.Publish( 89 | "logs", // exchange 90 | "", // routing key 91 | false, // mandatory 92 | false, // immediate 93 | amqp.Publishing{ 94 | ContentType: "text/plain", 95 | Body: []byte(body), 96 | }) 97 | ``` 98 | 99 | ## 临时队列 100 | 101 | 你还记得之前我们使用的队列名吗( hello和task_queue)?给一个队列命名是很重要的——我们需要把工作者(workers)指向正确的队列。如果你打算在发布者(producers)和消费者(consumers)之间共享同队列的话,给队列命名是十分重要的。 102 | 103 | 但是这并不适用于我们的日志系统。我们打算接收所有的日志消息,而不仅仅是一小部分。我们关心的是最新的消息而不是旧的。为了解决这个问题,我们需要做两件事情。 104 | 105 | 首先,当我们连接上RabbitMQ的时候,我们需要一个全新的、空的队列。我们可以手动创建一个随机的队列名,或者让服务器为我们选择一个随机的队列名(推荐)。 106 | 107 | 其次,当与消费者(consumer)断开连接的时候,这个队列应当被立即删除。 108 | 在`amqp`客户端中,当我们将队列名称作为空字符串提供时,我们创建一个具有生成名称的非持久队列: 109 | 110 | ```go 111 | q, err := ch.QueueDeclare( 112 | "", // name 113 | false, // durable 114 | false, // delete when usused 115 | true, // exclusive 116 | false, // no-wait 117 | nil, // arguments 118 | ) 119 | ``` 120 | 该方法返回时,队列实例包含RabbitMQ生成的随机队列名称。例如,它可能看起来像`amq.gen-JzTY20BRgKO-HjmUJj0wLg`。 121 | 当声明它的连接关闭时,队列将被删除,因为它被声明为`exclusive`。 122 | 您可以在[guide on queues](https://www.rabbitmq.com/queues.html)了解有关`exclusive`和其他队列属性的更多信息 123 | 124 | ## 绑定(Bindings) 125 | 126 | ![](http://www.rabbitmq.com/img/tutorials/bindings.png) 127 | 128 | 我们已经创建了一个`fanout`交换机和一个队列。现在我们需要告诉交换机如何发送消息给我们的队列。交换器和队列之间的联系我们称之为绑定(binding)。 129 | 130 | ```go 131 | err = ch.QueueBind( 132 | q.Name, // queue name 133 | "", // routing key 134 | "logs", // exchange 135 | false, 136 | nil, 137 | ) 138 | ``` 139 | 140 | 现在,logs交换机将会把消息添加到我们的队列中。 141 | 142 | > #### 绑定(binding)列表 143 | > 你可以使用`rabbitmqctl list_bindings` 列出所有现存的绑定。 144 | 145 | ## 代码整合 146 | 147 | ![](http://www.rabbitmq.com/img/tutorials/python-three-overall.png) 148 | 149 | 发布日志消息的程序看起来和之前的没有太大区别。最重要的改变就是我们把消息发送给logs交换机而不是匿名交换机。在发送的时候我们需要提供`routing_key`参数,但可以忽略它的值。以下是`emit_log.go`: 150 | 151 | ```go 152 | package main 153 | 154 | import ( 155 | "log" 156 | "os" 157 | "strings" 158 | 159 | "github.com/streadway/amqp" 160 | ) 161 | 162 | func failOnError(err error, msg string) { 163 | if err != nil { 164 | log.Fatalf("%s: %s", msg, err) 165 | } 166 | } 167 | 168 | func main() { 169 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 170 | failOnError(err, "Failed to connect to RabbitMQ") 171 | defer conn.Close() 172 | 173 | ch, err := conn.Channel() 174 | failOnError(err, "Failed to open a channel") 175 | defer ch.Close() 176 | 177 | err = ch.ExchangeDeclare( 178 | "logs", // name 179 | "fanout", // type 180 | true, // durable 181 | false, // auto-deleted 182 | false, // internal 183 | false, // no-wait 184 | nil, // arguments 185 | ) 186 | failOnError(err, "Failed to declare an exchange") 187 | 188 | body := bodyFrom(os.Args) 189 | err = ch.Publish( 190 | "logs", // exchange 191 | "", // routing key 192 | false, // mandatory 193 | false, // immediate 194 | amqp.Publishing{ 195 | ContentType: "text/plain", 196 | Body: []byte(body), 197 | }) 198 | failOnError(err, "Failed to publish a message") 199 | 200 | log.Printf(" [x] Sent %s", body) 201 | } 202 | 203 | func bodyFrom(args []string) string { 204 | var s string 205 | if (len(args) < 2) || os.Args[1] == "" { 206 | s = "hello" 207 | } else { 208 | s = strings.Join(args[1:], " ") 209 | } 210 | return s 211 | } 212 | ``` 213 | 214 | ([emit_log.go](http://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/emit_log.go) 源码) 215 | 216 | 正如你看到的那样,在连接成功之后,我们声明了一个交换器,这一个是很重要的,因为不允许发布消息到不存在的交换器。 217 | 218 | 如果没有绑定队列到交换器,消息将会丢失。但这个没有所谓,如果没有消费者监听,那么消息就会被忽略。 219 | 220 | `receive_logs.go`:的代码: 221 | 222 | ```go 223 | package main 224 | 225 | import ( 226 | "log" 227 | 228 | "github.com/streadway/amqp" 229 | ) 230 | 231 | func failOnError(err error, msg string) { 232 | if err != nil { 233 | log.Fatalf("%s: %s", msg, err) 234 | } 235 | } 236 | 237 | func main() { 238 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 239 | failOnError(err, "Failed to connect to RabbitMQ") 240 | defer conn.Close() 241 | 242 | ch, err := conn.Channel() 243 | failOnError(err, "Failed to open a channel") 244 | defer ch.Close() 245 | 246 | err = ch.ExchangeDeclare( 247 | "logs", // name 248 | "fanout", // type 249 | true, // durable 250 | false, // auto-deleted 251 | false, // internal 252 | false, // no-wait 253 | nil, // arguments 254 | ) 255 | failOnError(err, "Failed to declare an exchange") 256 | 257 | q, err := ch.QueueDeclare( 258 | "", // name 259 | false, // durable 260 | false, // delete when usused 261 | true, // exclusive 262 | false, // no-wait 263 | nil, // arguments 264 | ) 265 | failOnError(err, "Failed to declare a queue") 266 | 267 | err = ch.QueueBind( 268 | q.Name, // queue name 269 | "", // routing key 270 | "logs", // exchange 271 | false, 272 | nil, 273 | ) 274 | failOnError(err, "Failed to bind a queue") 275 | 276 | msgs, err := ch.Consume( 277 | q.Name, // queue 278 | "", // consumer 279 | true, // auto-ack 280 | false, // exclusive 281 | false, // no-local 282 | false, // no-wait 283 | nil, // args 284 | ) 285 | failOnError(err, "Failed to register a consumer") 286 | 287 | forever := make(chan bool) 288 | 289 | go func() { 290 | for d := range msgs { 291 | log.Printf(" [x] %s", d.Body) 292 | } 293 | }() 294 | 295 | log.Printf(" [*] Waiting for logs. To exit press CTRL+C") 296 | <-forever 297 | } 298 | ``` 299 | 300 | ([receive_logs.go](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/receive_logs.go) 源码) 301 | 302 | 这样我们就完成了。如果你想把日志保存到文件中,只需要打开控制台输入: 303 | 304 | $ go run receive_logs.go > logs_from_rabbit.log 305 | 306 | 如果你想在屏幕中查看日志,那么打开一个新的终端然后运行: 307 | 308 | $ go run receive_logs.go 309 | 310 | 当然还要发送日志: 311 | 312 | $ go run emit_log.go 313 | 314 | 使用`rabbitmqctl list_bindings`你可确认已经创建的队列绑定。你可以看到运行中的两个`receive_logs.go`程序: 315 | 316 | sudo rabbitmqctl list_bindings 317 | # => Listing bindings ... 318 | # => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue [] 319 | # => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue [] 320 | # => ...done. 321 | 322 | 显示结果很直观:logs交换器把数据发送给两个系统命名的队列。这就是我们所期望的。 323 | 324 | 如何监听消息的子集呢?让我们移步教程4 325 | -------------------------------------------------------------------------------- /published/tutorials_with_golang/[4]Routing.md: -------------------------------------------------------------------------------- 1 | >原文:[Routing](https://www.rabbitmq.com/tutorials/tutorial-four-go.html) 2 | >状态:待校对 3 | >翻译:[Bingjian-Zhu](https://bingjian-zhu.github.io/) 4 | >校对: 5 | 6 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 7 | 8 | ## 路由(Routing) 9 | 10 | **(使用Go客户端)** 11 | 12 | 13 | 14 | 在前面的教程中,我们实现了一个简单的日志系统。可以把日志消息广播给多个接收者。 15 | 16 | 本篇教程中我们打算新增一个功能 —— 使得它能够只订阅消息的一个字集。例如,我们只需要把严重的错误日志信息写入日志文件(存储到磁盘),但同时仍然把所有的日志信息输出到控制台中 17 | 18 | ## 绑定(Bindings) 19 | 20 | 前面的例子,我们已经创建过绑定(bindings),代码如下: 21 | 22 | ```go 23 | err = ch.QueueBind( 24 | q.Name, // queue name 25 | "", // routing key 26 | "logs", // exchange 27 | false, 28 | nil) 29 | ``` 30 | 31 | 绑定(binding)是指交换机(exchange)和队列(queue)的关系。可以简单理解为:这个队列(queue)对这个交换机(exchange)的消息感兴趣。 32 | 33 | 绑定的时候可以带上一个额外的`routing_key`参数。为了避免与`Channel.Publish`的参数混淆,我们把它叫做绑定键`binding key`。以下是如何创建一个带绑定键的绑定。 34 | 35 | ```go 36 | err = ch.QueueBind( 37 | q.Name, // queue name 38 | "black", // routing key 39 | "logs", // exchange 40 | false, 41 | nil) 42 | ``` 43 | 44 | 绑定键的意义取决于交换机(exchange)的类型。我们之前使用过`fanout` 交换机会忽略这个值。 45 | 46 | ## 直连交换机(Direct exchange) 47 | 48 | 我们的日志系统广播所有的消息给所有的消费者(consumers)。我们打算扩展它,使其基于日志的严重程度进行消息过滤。例如我们也许只是希望将比较严重的错误(error)日志写入磁盘,以免在警告(warning)或者信息(info)日志上浪费磁盘空间。 49 | 50 | 我们使用的`fanout` 交换机没有足够的灵活性 —— 它能做的仅仅是广播。 51 | 52 | 我们将会使用`direct` 交换机来代替。路由的算法很简单 —— 交换机将会对`binding key`和`routing key`进行精确匹配,从而确定消息该分发到哪个队列。 53 | 54 | 下图能够很好的描述这个场景: 55 | 56 | ![](http://www.rabbitmq.com/img/tutorials/direct-exchange.png) 57 | 58 | 在这个场景中,我们可以看到`direct`交换机 X和两个队列进行了绑定。第一个队列使用`orange`作为binding key,第二个队列有两个绑定,一个使用`black`作为binding key,另外一个使用`green`。 59 | 60 | 这样以来,当消息发布到routing key为`orange`的交换机时,就会被路由到队列Q1。routing key为`black`或者`green`的消息就会路由到Q2。其他的所有消息都将会被丢弃。 61 | 62 | ## 多个绑定(Multiple bindings) 63 | 64 | ![](http://www.rabbitmq.com/img/tutorials/direct-exchange-multiple.png) 65 | 66 | 多个队列使用相同的binding key是合法的。这个例子中,我们可以添加一个X和Q1之间的绑定,使用`black`为binding key。这样一来,`direct`交换机就和`fanout`交换机的行为一样,会将消息广播到所有匹配的队列。带有routing key为`black`的消息会同时发送到Q1和Q2。 67 | 68 | ## 发送日志 69 | 70 | 我们将会发送消息到一个`fanout`,把日志等级作为`routing key`。这样接收日志就可以根据日志级别来选择它想要处理的日志。我们先看看发送日志。 71 | 72 | 我们需要创建一个交换机(exchange): 73 | 74 | ```go 75 | err = ch.ExchangeDeclare( 76 | "logs_direct", // name 77 | "direct", // type 78 | true, // durable 79 | false, // auto-deleted 80 | false, // internal 81 | false, // no-wait 82 | nil, // arguments 83 | ) 84 | ``` 85 | 86 | 然后我们发送一则消息: 87 | 88 | ```go 89 | err = ch.ExchangeDeclare( 90 | "logs_direct", // name 91 | "direct", // type 92 | true, // durable 93 | false, // auto-deleted 94 | false, // internal 95 | false, // no-wait 96 | nil, // arguments 97 | ) 98 | failOnError(err, "Failed to declare an exchange") 99 | 100 | body := bodyFrom(os.Args) 101 | err = ch.Publish( 102 | "logs_direct", // exchange 103 | severityFrom(os.Args), // routing key 104 | false, // mandatory 105 | false, // immediate 106 | amqp.Publishing{ 107 | ContentType: "text/plain", 108 | Body: []byte(body), 109 | }) 110 | ``` 111 | 112 | 我们假设日志等级的值是info、warning、error中的一个。 113 | 114 | ## 订阅 115 | 116 | 处理接收消息的方式和之前差不多,只有一个例外,我们将会为我们感兴趣的每个严重级别分别创建一个新的绑定。 117 | 118 | ```go 119 | q, err := ch.QueueDeclare( 120 | "", // name 121 | false, // durable 122 | false, // delete when usused 123 | true, // exclusive 124 | false, // no-wait 125 | nil, // arguments 126 | ) 127 | failOnError(err, "Failed to declare a queue") 128 | 129 | if len(os.Args) < 2 { 130 | log.Printf("Usage: %s [info] [warning] [error]", os.Args[0]) 131 | os.Exit(0) 132 | } 133 | for _, s := range os.Args[1:] { 134 | log.Printf("Binding queue %s to exchange %s with routing key %s", 135 | q.Name, "logs_direct", s) 136 | err = ch.QueueBind( 137 | q.Name, // queue name 138 | s, // routing key 139 | "logs_direct", // exchange 140 | false, 141 | nil) 142 | failOnError(err, "Failed to bind a queue") 143 | } 144 | ``` 145 | 146 | ## 代码整合 147 | 148 | ![](http://www.rabbitmq.com/img/tutorials/python-four.png) 149 | 150 | emit_log_direct.go的代码: 151 | 152 | ```go 153 | package main 154 | 155 | import ( 156 | "log" 157 | "os" 158 | "strings" 159 | 160 | "github.com/streadway/amqp" 161 | ) 162 | 163 | func failOnError(err error, msg string) { 164 | if err != nil { 165 | log.Fatalf("%s: %s", msg, err) 166 | } 167 | } 168 | 169 | func main() { 170 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 171 | failOnError(err, "Failed to connect to RabbitMQ") 172 | defer conn.Close() 173 | 174 | ch, err := conn.Channel() 175 | failOnError(err, "Failed to open a channel") 176 | defer ch.Close() 177 | 178 | err = ch.ExchangeDeclare( 179 | "logs_direct", // name 180 | "direct", // type 181 | true, // durable 182 | false, // auto-deleted 183 | false, // internal 184 | false, // no-wait 185 | nil, // arguments 186 | ) 187 | failOnError(err, "Failed to declare an exchange") 188 | 189 | body := bodyFrom(os.Args) 190 | err = ch.Publish( 191 | "logs_direct", // exchange 192 | severityFrom(os.Args), // routing key 193 | false, // mandatory 194 | false, // immediate 195 | amqp.Publishing{ 196 | ContentType: "text/plain", 197 | Body: []byte(body), 198 | }) 199 | failOnError(err, "Failed to publish a message") 200 | 201 | log.Printf(" [x] Sent %s", body) 202 | } 203 | 204 | func bodyFrom(args []string) string { 205 | var s string 206 | if (len(args) < 3) || os.Args[2] == "" { 207 | s = "hello" 208 | } else { 209 | s = strings.Join(args[2:], " ") 210 | } 211 | return s 212 | } 213 | 214 | func severityFrom(args []string) string { 215 | var s string 216 | if (len(args) < 2) || os.Args[1] == "" { 217 | s = "info" 218 | } else { 219 | s = os.Args[1] 220 | } 221 | return s 222 | } 223 | ``` 224 | 225 | receive_logs_direct.go的代码: 226 | 227 | ```go 228 | package main 229 | 230 | import ( 231 | "log" 232 | "os" 233 | 234 | "github.com/streadway/amqp" 235 | ) 236 | 237 | func failOnError(err error, msg string) { 238 | if err != nil { 239 | log.Fatalf("%s: %s", msg, err) 240 | } 241 | } 242 | 243 | func main() { 244 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 245 | failOnError(err, "Failed to connect to RabbitMQ") 246 | defer conn.Close() 247 | 248 | ch, err := conn.Channel() 249 | failOnError(err, "Failed to open a channel") 250 | defer ch.Close() 251 | 252 | err = ch.ExchangeDeclare( 253 | "logs_direct", // name 254 | "direct", // type 255 | true, // durable 256 | false, // auto-deleted 257 | false, // internal 258 | false, // no-wait 259 | nil, // arguments 260 | ) 261 | failOnError(err, "Failed to declare an exchange") 262 | 263 | q, err := ch.QueueDeclare( 264 | "", // name 265 | false, // durable 266 | false, // delete when usused 267 | true, // exclusive 268 | false, // no-wait 269 | nil, // arguments 270 | ) 271 | failOnError(err, "Failed to declare a queue") 272 | 273 | if len(os.Args) < 2 { 274 | log.Printf("Usage: %s [info] [warning] [error]", os.Args[0]) 275 | os.Exit(0) 276 | } 277 | for _, s := range os.Args[1:] { 278 | log.Printf("Binding queue %s to exchange %s with routing key %s", 279 | q.Name, "logs_direct", s) 280 | err = ch.QueueBind( 281 | q.Name, // queue name 282 | s, // routing key 283 | "logs_direct", // exchange 284 | false, 285 | nil) 286 | failOnError(err, "Failed to bind a queue") 287 | } 288 | 289 | msgs, err := ch.Consume( 290 | q.Name, // queue 291 | "", // consumer 292 | true, // auto ack 293 | false, // exclusive 294 | false, // no local 295 | false, // no wait 296 | nil, // args 297 | ) 298 | failOnError(err, "Failed to register a consumer") 299 | 300 | forever := make(chan bool) 301 | 302 | go func() { 303 | for d := range msgs { 304 | log.Printf(" [x] %s", d.Body) 305 | } 306 | }() 307 | 308 | log.Printf(" [*] Waiting for logs. To exit press CTRL+C") 309 | <-forever 310 | } 311 | ``` 312 | 313 | 如果你希望只是保存warning和error级别的日志到磁盘,只需要打开控制台并输入: 314 | 315 | $ go run receive_logs_direct.go warning error > logs_from_rabbit.log 316 | 317 | 如果你希望所有的日志信息都输出到屏幕中,打开一个新的终端,然后输入: 318 | 319 | go run receive_logs_direct.go info warning error 320 | # => [*] Waiting for logs. To exit press CTRL+C 321 | 322 | 如果要触发一个error级别的日志,只需要输入: 323 | 324 | go run emit_log_direct.go error "Run. Run. Or it will explode." 325 | # => [x] Sent 'error':'Run. Run. Or it will explode.' 326 | 327 | 这里是完整的代码:([emit_log_direct.go][1]和[receive_logs_direct.go][2]) 328 | 329 | 330 | [1]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/emit_log_direct.go 331 | [2]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/receive_logs_direct.go 332 | -------------------------------------------------------------------------------- /published/tutorials_with_golang/[5]Topics.md: -------------------------------------------------------------------------------- 1 | >原文:[Topics](https://www.rabbitmq.com/tutorials/tutorial-five-go.html) 2 | >状态:待校对 3 | >翻译:[Bingjian-Zhu](https://bingjian-zhu.github.io/) 4 | >校对: 5 | 6 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 7 | 8 | ## 为什么需要topic交换机? 9 | 10 | **(使用Go客户端)** 11 | 12 | 13 | 14 | [上一篇教程](https://bingjian-zhu.github.io/2019/09/01/RabbitMQ%E6%95%99%E7%A8%8B%EF%BC%88%E8%AF%91%EF%BC%89-Routing/),我们改进了我们的日志系统。我们使用`direct`交换机替代了`fanout`交换机,从只能盲目的广播消息改进为有可能选择性的接收日志。 15 | 16 | 尽管`direct`交换机能够改善我们的系统,但是它也有它的限制 —— 没办法基于多个标准执行路由操作。 17 | 18 | 在我们的日志系统中,我们不只希望订阅基于严重程度的日志,同时还希望订阅基于发送来源的日志。Unix工具[syslog](http://en.wikipedia.org/wiki/Syslog)就是同时基于严重程度-severity (info/warn/crit...) 和 设备-facility (auth/cron/kern...)来路由日志的。 19 | 20 | 如果这样的话,将会给予我们非常大的灵活性,我们既可以监听来源于“cron”的严重程度为“critical errors”的日志,也可以监听来源于“kern”的所有日志。 21 | 22 | 为了实现这个目的,接下来我们学习如何使用另一种更复杂的交换机 —— topic交换机。 23 | 24 | ## topic交换机 25 | 26 | 发送到`topic`交换机的消息不可以携带随意`routing_key`,它的routing_key必须是一个由`.`分隔开的词语列表。这些单词随便是什么都可以,但是最好是跟携带它们的消息有关系的词汇。以下是几个推荐的例子:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit"。词语的个数可以随意,但是不要超过255字节。 27 | 28 | binding key也必须拥有同样的格式。`topic`交换机背后的逻辑跟`direct`交换机很相似 —— 一个携带着特定routing_key的消息会被topic交换机投递给绑定键与之想匹配的队列。但是它的binding key和routing_key有两个特殊应用方式: 29 | 30 | - `*` (星号) 用来表示一个单词. 31 | - `#` (井号) 用来表示任意数量(零个或多个)单词。 32 | 33 | 下边用图说明: 34 | ![None](http://www.rabbitmq.com/img/tutorials/python-five.png) 35 | 36 | 这个例子里,我们发送的所有消息都是用来描述小动物的。发送的消息所携带的路由键是由三个单词所组成的,这三个单词被两个`.`分割开。路由键里的第一个单词描述的是动物的手脚的利索程度,第二个单词是动物的颜色,第三个是动物的种类。所以它看起来是这样的: `..`。 37 | 38 | 我们创建了三个绑定:Q1的绑定键为 `*.orange.*`,Q2的绑定键为 `*.*.rabbit` 和 `lazy.#` 。 39 | 40 | 这三个绑定键被可以总结为: 41 | 42 | - Q1 对*所有的桔黄色动物*都感兴趣。 43 | - Q2 则是对*所有的兔子*和*所有懒惰的动物*感兴趣。 44 | 45 | 一个携带有 `quick.orange.rabbit` 的消息将会被分别投递给这两个队列。携带着 `lazy.orange.elephant` 的消息同样也会给两个队列都投递过去。另一方面携带有 `quick.orange.fox` 的消息会投递给第一个队列,携带有 `lazy.brown.fox` 的消息会投递给第二个队列。携带有 `lazy.pink.rabbit` 的消息只会被投递给第二个队列一次,即使它同时匹配第二个队列的两个绑定。携带着 `quick.brown.fox` 的消息不会投递给任何一个队列。 46 | 47 | 如果我们违反约定,发送了一个携带有一个单词或者四个单词(`"orange"` or `"quick.orange.male.rabbit"`)的消息时,发送的消息不会投递给任何一个队列,而且会丢失掉。 48 | 49 | 但是另一方面,即使 `"lazy.orange.male.rabbit"` 有四个单词,他还是会匹配最后一个绑定,并且被投递到第二个队列中。 50 | 51 | >#### Topic交换机 52 | >Topic交换机是很强大的,它可以表现出跟其他交换机类似的行为 53 | >当一个队列的binding key为 "#"(井号) 的时候,这个队列将会无视消息的routing key,接收所有的消息。 54 | >当 `*` (星号) 和 `#` (井号) 这两个特殊字符都未在binding key中出现的时候,此时Topic交换机就拥有的direct交换机的行为。 55 | 56 | ## 代码整合 57 | 58 | 接下来我们会将Topic交换机应用到我们的日志系统中。在开始工作前,我们假设日志的routing key由两个单词组成,routing key看起来是这样的:`.` 59 | 60 | 代码跟[上一篇教程](https://bingjian-zhu.github.io/2019/09/01/RabbitMQ%E6%95%99%E7%A8%8B%EF%BC%88%E8%AF%91%EF%BC%89-Routing/)差不多。 61 | 62 | emit_log_topic.go的代码: 63 | 64 | ```go 65 | package main 66 | 67 | import ( 68 | "log" 69 | "os" 70 | "strings" 71 | 72 | "github.com/streadway/amqp" 73 | ) 74 | 75 | func failOnError(err error, msg string) { 76 | if err != nil { 77 | log.Fatalf("%s: %s", msg, err) 78 | } 79 | } 80 | 81 | func main() { 82 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 83 | failOnError(err, "Failed to connect to RabbitMQ") 84 | defer conn.Close() 85 | 86 | ch, err := conn.Channel() 87 | failOnError(err, "Failed to open a channel") 88 | defer ch.Close() 89 | 90 | err = ch.ExchangeDeclare( 91 | "logs_topic", // name 92 | "topic", // type 93 | true, // durable 94 | false, // auto-deleted 95 | false, // internal 96 | false, // no-wait 97 | nil, // arguments 98 | ) 99 | failOnError(err, "Failed to declare an exchange") 100 | 101 | body := bodyFrom(os.Args) 102 | err = ch.Publish( 103 | "logs_topic", // exchange 104 | severityFrom(os.Args), // routing key 105 | false, // mandatory 106 | false, // immediate 107 | amqp.Publishing{ 108 | ContentType: "text/plain", 109 | Body: []byte(body), 110 | }) 111 | failOnError(err, "Failed to publish a message") 112 | 113 | log.Printf(" [x] Sent %s", body) 114 | } 115 | 116 | func bodyFrom(args []string) string { 117 | var s string 118 | if (len(args) < 3) || os.Args[2] == "" { 119 | s = "hello" 120 | } else { 121 | s = strings.Join(args[2:], " ") 122 | } 123 | return s 124 | } 125 | 126 | func severityFrom(args []string) string { 127 | var s string 128 | if (len(args) < 2) || os.Args[1] == "" { 129 | s = "anonymous.info" 130 | } else { 131 | s = os.Args[1] 132 | } 133 | return s 134 | } 135 | ``` 136 | 137 | receive_logs_topic.go的代码: 138 | 139 | ```go 140 | package main 141 | 142 | import ( 143 | "log" 144 | "os" 145 | 146 | "github.com/streadway/amqp" 147 | ) 148 | 149 | func failOnError(err error, msg string) { 150 | if err != nil { 151 | log.Fatalf("%s: %s", msg, err) 152 | } 153 | } 154 | 155 | func main() { 156 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 157 | failOnError(err, "Failed to connect to RabbitMQ") 158 | defer conn.Close() 159 | 160 | ch, err := conn.Channel() 161 | failOnError(err, "Failed to open a channel") 162 | defer ch.Close() 163 | 164 | err = ch.ExchangeDeclare( 165 | "logs_topic", // name 166 | "topic", // type 167 | true, // durable 168 | false, // auto-deleted 169 | false, // internal 170 | false, // no-wait 171 | nil, // arguments 172 | ) 173 | failOnError(err, "Failed to declare an exchange") 174 | 175 | q, err := ch.QueueDeclare( 176 | "", // name 177 | false, // durable 178 | false, // delete when usused 179 | true, // exclusive 180 | false, // no-wait 181 | nil, // arguments 182 | ) 183 | failOnError(err, "Failed to declare a queue") 184 | 185 | if len(os.Args) < 2 { 186 | log.Printf("Usage: %s [binding_key]...", os.Args[0]) 187 | os.Exit(0) 188 | } 189 | for _, s := range os.Args[1:] { 190 | log.Printf("Binding queue %s to exchange %s with routing key %s", 191 | q.Name, "logs_topic", s) 192 | err = ch.QueueBind( 193 | q.Name, // queue name 194 | s, // routing key 195 | "logs_topic", // exchange 196 | false, 197 | nil) 198 | failOnError(err, "Failed to bind a queue") 199 | } 200 | 201 | msgs, err := ch.Consume( 202 | q.Name, // queue 203 | "", // consumer 204 | true, // auto ack 205 | false, // exclusive 206 | false, // no local 207 | false, // no wait 208 | nil, // args 209 | ) 210 | failOnError(err, "Failed to register a consumer") 211 | 212 | forever := make(chan bool) 213 | 214 | go func() { 215 | for d := range msgs { 216 | log.Printf(" [x] %s", d.Body) 217 | } 218 | }() 219 | 220 | log.Printf(" [*] Waiting for logs. To exit press CTRL+C") 221 | <-forever 222 | } 223 | ``` 224 | 225 | 执行下边命令 接收所有日志: 226 | 227 | go run receive_logs_topic.go "#" 228 | 229 | 执行下边命令 接收来自”kern“设备的日志: 230 | 231 | go run receive_logs_topic.go "kern.*" 232 | 233 | 执行下边命令 只接收严重程度为”critical“的日志: 234 | 235 | go run receive_logs_topic.go "*.critical" 236 | 237 | 执行下边命令 建立多个绑定: 238 | 239 | go run receive_logs_topic.go "kern.*" "*.critical" 240 | 241 | 执行下边命令 发送路由键为 "kern.critical" 的日志: 242 | 243 | go run emit_log_topic.go "kern.critical" "A critical kernel error" 244 | 245 | 执行上边命令试试看效果吧。另外,上边代码不会对路由键和绑定键做任何假设,所以你可以在命令中使用超过两个路由键参数。 246 | 247 | ### 如果你现在还没被搞晕,想想下边问题: 248 | - 绑定键为 `*` 的队列会取到一个routing key为空的消息吗? 249 | - 绑定键为 `#.*` 的队列会获取到一个名为`..`的路由键的消息吗?它会取到一个routing key为单个单词的消息吗? 250 | - `a.*.#` 和 `a.#`的区别在哪儿? 251 | 252 | (完整代码参见[emit_logs_topic.go](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/emit_log_topic.go) and [receive_logs_topic.go](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/receive_logs_topic.go)) 253 | -------------------------------------------------------------------------------- /published/tutorials_with_golang/[6]RPC.md: -------------------------------------------------------------------------------- 1 | >原文:[RPC](https://www.rabbitmq.com/tutorials/tutorial-six-go.html) 2 | >状态:待校对 3 | >翻译:[Bingjian-Zhu](https://bingjian-zhu.github.io/) 4 | >校对: 5 | 6 | ![CC-BY-SA](https://upload.wikimedia.org/wikipedia/commons/d/d0/CC-BY-SA_icon.svg) 7 | 8 | ## 远程过程调用(RPC) 9 | 10 | **(使用Go客户端)** 11 | 12 | 13 | 14 | 在[第二篇教程](https://bingjian-zhu.github.io/2019/09/01/RabbitMQ%E6%95%99%E7%A8%8B%EF%BC%88%E8%AF%91%EF%BC%89-Work-queues/)中我们介绍了如何使用工作队列(work queue)在多个工作者(woker)中间分发耗时的任务。 15 | 16 | 可是如果我们需要将一个函数运行在远程计算机上并且等待从那儿获取结果时,该怎么办呢?这就是另外的故事了。这种模式通常被称为远程过程调用(Remote Procedure Call)或者RPC。 17 | 18 | 这篇教程中,我们会使用RabbitMQ来构建一个RPC系统:包含一个客户端和一个RPC服务器。现在的情况是,我们没有一个值得被分发的足够耗时的任务,所以接下来,我们会创建一个模拟RPC服务来返回斐波那契数列。 19 | 20 | 21 | > #### 关于RPC的注意事项: 22 | > 尽管RPC在计算领域是一个常用模式,但它也经常被诟病。当一个问题被抛出的时候,程序员往往意识不到这到底是由本地调用还是由较慢的RPC调用引起的。同样的困惑还来自于系统的不可预测性和给调试工作带来的不必要的复杂性。跟软件精简不同的是,滥用RPC会导致不可维护的. 23 | > * 考虑到这一点,牢记以下建议: 24 | >* 确保能够明确的搞清楚哪个函数是本地调用的,哪个函数是远程调用的。给你的系统编写文档。保持各个组件间的依赖明确。处理错误案例。明了客户端该如何处理RPC服务器的宕机和长时间无响应情况。 25 | > * 当对避免使用RPC有疑问的时候。如果可以的话,你应该尽量使用异步管道来代替RPC类的阻塞。结果被异步地推送到下一个计算场景。 26 | 27 | ### 回调队列 28 | 29 | 一般来说通过RabbitMQ来实现RPC是很容易的。一个客户端发送请求信息,服务器端将其应用到一个回复信息中。为了接收到回复信息,客户端需要在发送请求的时候同时发送一个回调队列`callback queue`的地址。我们试试看: 30 | 31 | ```go 32 | q, err := ch.QueueDeclare( 33 | "", // name 34 | false, // durable 35 | false, // delete when usused 36 | true, // exclusive 37 | false, // noWait 38 | nil, // arguments 39 | ) 40 | 41 | err = ch.Publish( 42 | "", // exchange 43 | "rpc_queue", // routing key 44 | false, // mandatory 45 | false, // immediate 46 | amqp.Publishing{ 47 | ContentType: "text/plain", 48 | CorrelationId: corrId, 49 | ReplyTo: q.Name, 50 | Body: []byte(strconv.Itoa(n)), 51 | }) 52 | ``` 53 | 54 | > #### 消息属性 55 | > AMQP协议给消息预定义了一系列的14个属性。大多数属性很少会用到,除了以下几个: 56 | > * `persistent`(持久性):将消息标记为持久性(值为true)或瞬态(false)。第二篇教程中有说明这个属性。 57 | >* `content_type`(内容类型):用来描述编码的mime-type。例如在实际使用中常常使用`application/json`来描述JOSN编码类型。 58 | >* `reply_to`(回复目标):通常用来命名回调队列。 59 | >* `correlation_id`(关联标识):用来将RPC的响应和请求关联起来。 60 | 61 | ### 关联标识 62 | 63 | 上边介绍的方法中,我们建议给每一个RPC请求新建一个回调队列。这不是一个高效的做法,幸好这儿有一个更好的办法 —— 我们可以为每个客户端只建立一个独立的回调队列。 64 | 65 | 这就带来一个新问题,当此队列接收到一个响应的时候它无法辨别出这个响应是属于哪个请求的。`correlation\_id` 就是为了解决这个问题而来的。我们给每个请求设置一个独一无二的值。稍后,当我们从回调队列中接收到一个消息的时候,我们就可以查看这条属性从而将响应和请求匹配起来。如果我们接手到的消息的`correlation\_id`是未知的,那就直接销毁掉它,因为它不属于我们的任何一条请求。 66 | 67 | 你也许会问,为什么我们接收到未知消息的时候不抛出一个错误,而是要将它忽略掉?这是为了解决服务器端有可能发生的竞争情况。尽管可能性不大,但RPC服务器还是有可能在已将应答发送给我们但还未将确认消息发送给请求的情况下死掉。如果这种情况发生,RPC在重启后会重新处理请求。这就是为什么我们必须在客户端优雅的处理重复响应,同时RPC也需要尽可能保持幂等性。 68 | 69 | ### 总结 70 | 71 | ![](http://www.rabbitmq.com/img/tutorials/python-six.png) 72 | 73 | 我们的RPC如此工作: 74 | 75 | * 当客户端启动的时候,它创建一个匿名独享的回调队列。 76 | * 在RPC中,客户端发送带有两个属性的消息:一个是设置回调队列的 *reply\_to* 属性,另一个是设置唯一值的 *correlation\_id* 属性。 77 | * 将请求发送到一个 *rpc\_queue* 队列中。 78 | * RPC工作者(又名:服务器)等待请求发送到这个队列中来。当请求出现的时候,它执行他的工作并且将带有执行结果的消息发送给*reply\_to*字段指定的队列。 79 | * 客户端等待回调队列里的数据。当有消息出现的时候,它会检查*correlation_id*属性。如果此属性的值与请求匹配,将它返回给应用。 80 | 81 | ## 代码整合 82 | 83 | 斐波那列函数: 84 | ``` go 85 | func fib(n int) int { 86 | if n == 0 { 87 | return 0 88 | } else if n == 1 { 89 | return 1 90 | } else { 91 | return fib(n-1) + fib(n-2) 92 | } 93 | } 94 | ``` 95 | 声明斐波那契函数,假定只有有效的正整数输入。 (不要指望这个函数能适用于大数字,它可能是最慢的递归实现)。 96 | 97 | `rpc_server.go`代码: 98 | 99 | ```go 100 | package main 101 | 102 | import ( 103 | "log" 104 | "strconv" 105 | 106 | "github.com/streadway/amqp" 107 | ) 108 | 109 | func failOnError(err error, msg string) { 110 | if err != nil { 111 | log.Fatalf("%s: %s", msg, err) 112 | } 113 | } 114 | 115 | func fib(n int) int { 116 | if n == 0 { 117 | return 0 118 | } else if n == 1 { 119 | return 1 120 | } else { 121 | return fib(n-1) + fib(n-2) 122 | } 123 | } 124 | 125 | func main() { 126 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 127 | failOnError(err, "Failed to connect to RabbitMQ") 128 | defer conn.Close() 129 | 130 | ch, err := conn.Channel() 131 | failOnError(err, "Failed to open a channel") 132 | defer ch.Close() 133 | 134 | q, err := ch.QueueDeclare( 135 | "rpc_queue", // name 136 | false, // durable 137 | false, // delete when usused 138 | false, // exclusive 139 | false, // no-wait 140 | nil, // arguments 141 | ) 142 | failOnError(err, "Failed to declare a queue") 143 | 144 | err = ch.Qos( 145 | 1, // prefetch count 146 | 0, // prefetch size 147 | false, // global 148 | ) 149 | failOnError(err, "Failed to set QoS") 150 | 151 | msgs, err := ch.Consume( 152 | q.Name, // queue 153 | "", // consumer 154 | false, // auto-ack 155 | false, // exclusive 156 | false, // no-local 157 | false, // no-wait 158 | nil, // args 159 | ) 160 | failOnError(err, "Failed to register a consumer") 161 | 162 | forever := make(chan bool) 163 | 164 | go func() { 165 | for d := range msgs { 166 | n, err := strconv.Atoi(string(d.Body)) 167 | failOnError(err, "Failed to convert body to integer") 168 | 169 | log.Printf(" [.] fib(%d)", n) 170 | response := fib(n) 171 | 172 | err = ch.Publish( 173 | "", // exchange 174 | d.ReplyTo, // routing key 175 | false, // mandatory 176 | false, // immediate 177 | amqp.Publishing{ 178 | ContentType: "text/plain", 179 | CorrelationId: d.CorrelationId, 180 | Body: []byte(strconv.Itoa(response)), 181 | }) 182 | failOnError(err, "Failed to publish a message") 183 | 184 | d.Ack(false) 185 | } 186 | }() 187 | 188 | log.Printf(" [*] Awaiting RPC requests") 189 | <-forever 190 | } 191 | ``` 192 | 193 | 服务器端代码相当简单: 194 | 195 | * 像往常一样,我们建立连接,声明队列 196 | * 为了在多个服务器上平均分配负载,我们希望运行多个服务器进程,需要在通道上设置`prefetch` 197 | * 我们为 basic_consume 声明了一个回调函数,这是RPC服务器端的核心。它执行实际的操作并且作出响应。 198 | * 我们使用`Channel.Consume`来获取接收消息的队列的go频道。然后使用goroutine来处理并作出回应 199 | 200 | `rpc_client.go` 代码: 201 | 202 | ```go 203 | package main 204 | 205 | import ( 206 | "log" 207 | "math/rand" 208 | "os" 209 | "strconv" 210 | "strings" 211 | "time" 212 | 213 | "github.com/streadway/amqp" 214 | ) 215 | 216 | func failOnError(err error, msg string) { 217 | if err != nil { 218 | log.Fatalf("%s: %s", msg, err) 219 | } 220 | } 221 | 222 | func randomString(l int) string { 223 | bytes := make([]byte, l) 224 | for i := 0; i < l; i++ { 225 | bytes[i] = byte(randInt(65, 90)) 226 | } 227 | return string(bytes) 228 | } 229 | 230 | func randInt(min int, max int) int { 231 | return min + rand.Intn(max-min) 232 | } 233 | 234 | func fibonacciRPC(n int) (res int, err error) { 235 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 236 | failOnError(err, "Failed to connect to RabbitMQ") 237 | defer conn.Close() 238 | 239 | ch, err := conn.Channel() 240 | failOnError(err, "Failed to open a channel") 241 | defer ch.Close() 242 | 243 | q, err := ch.QueueDeclare( 244 | "", // name 245 | false, // durable 246 | false, // delete when usused 247 | true, // exclusive 248 | false, // noWait 249 | nil, // arguments 250 | ) 251 | failOnError(err, "Failed to declare a queue") 252 | 253 | msgs, err := ch.Consume( 254 | q.Name, // queue 255 | "", // consumer 256 | true, // auto-ack 257 | false, // exclusive 258 | false, // no-local 259 | false, // no-wait 260 | nil, // args 261 | ) 262 | failOnError(err, "Failed to register a consumer") 263 | 264 | corrId := randomString(32) 265 | 266 | err = ch.Publish( 267 | "", // exchange 268 | "rpc_queue", // routing key 269 | false, // mandatory 270 | false, // immediate 271 | amqp.Publishing{ 272 | ContentType: "text/plain", 273 | CorrelationId: corrId, 274 | ReplyTo: q.Name, 275 | Body: []byte(strconv.Itoa(n)), 276 | }) 277 | failOnError(err, "Failed to publish a message") 278 | 279 | for d := range msgs { 280 | if corrId == d.CorrelationId { 281 | res, err = strconv.Atoi(string(d.Body)) 282 | failOnError(err, "Failed to convert body to integer") 283 | break 284 | } 285 | } 286 | 287 | return 288 | } 289 | 290 | func main() { 291 | rand.Seed(time.Now().UTC().UnixNano()) 292 | 293 | n := bodyFrom(os.Args) 294 | 295 | log.Printf(" [x] Requesting fib(%d)", n) 296 | res, err := fibonacciRPC(n) 297 | failOnError(err, "Failed to handle RPC request") 298 | 299 | log.Printf(" [.] Got %d", res) 300 | } 301 | 302 | func bodyFrom(args []string) int { 303 | var s string 304 | if (len(args) < 2) || os.Args[1] == "" { 305 | s = "30" 306 | } else { 307 | s = strings.Join(args[1:], " ") 308 | } 309 | n, err := strconv.Atoi(s) 310 | failOnError(err, "Failed to convert arg to integer") 311 | return n 312 | } 313 | ``` 314 | (完整的[rpc_client.go][1] 和 [rpc_server.go][2]代码) 315 | 316 | [1]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/rpc_client.go 317 | [2]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/rpc_server.go 318 | 319 | 我们的RPC服务已经准备就绪了,现在启动服务器端: 320 | 321 | ```go 322 | go run rpc_server.go 323 | # => [x] Awaiting RPC requests 324 | ``` 325 | 326 | 运行客户端,请求一个fibonacci队列。 327 | 328 | ```go 329 | go run rpc_client.go 30 330 | # => [x] Requesting fib(30) 331 | ``` 332 | 333 | 此处呈现的设计并不是实现RPC服务的唯一方式,但是他有一些重要的优势: 334 | 335 | * 如果RPC服务器运行的过慢的时候,你可以通过运行另外一个服务器端轻松扩展它。试试在控制台中运行第二个 `rpc_server.go` 336 | * 在客户端,RPC请求只发送或接收一条消息。不需要像 queue_declare 这样的异步调用。所以RPC客户端的单个请求只需要一个网络往返。 337 | 338 | 我们的代码依旧非常简单,而且没有试图去解决一些复杂(但是重要)的问题,如: 339 | 340 | * 当没有服务器运行时,客户端如何作出反映。 341 | * 客户端是否需要实现类似RPC超时的东西。 342 | * 如果服务器发生故障,并且抛出异常,应该被转发到客户端吗? 343 | * 在处理前,防止混入无效的信息(例如检查边界) 344 | 345 | 如果你想做一些实验,你会发现[management UI ](https://www.rabbitmq.com/management.html)在观测队列方面是很有用处的 346 | 347 | -------------------------------------------------------------------------------- /published/tutorials_with_python/[1]Hello_World.md: -------------------------------------------------------------------------------- 1 | ##介绍 2 | 3 | RabbitMQ是一个消息代理。它的工作就是接收和转发消息。你可以把它想像成一个邮局:你把信件放入邮箱,邮递员就会把信件投递到你的收件人处。在这个比喻中,RabbitMQ就扮演着邮箱、邮局以及邮递员的角色。 4 | 5 | RabbitMQ和邮局的主要区别在于,它处理纸张,而是接收、存储和发送消息(message)这种二进制数据。 6 | 7 | 下面是RabbitMQ和消息所涉及到的一些术语。 8 | 9 | - 生产(Producing)的意思就是发送。发送消息的程序就是一个生产者(producer)。我们一般用"P"来表示: 10 | ![](http://www.rabbitmq.com/img/tutorials/producer.png) 11 | 12 | - 队列(queue)就是存在于RabbitMQ中邮箱的名称。虽然消息的传输经过了RabbitMQ和你的应用程序,但是它只能被存储于队列当中。实质上队列就是个巨大的消息缓冲区,它的大小只受主机内存和硬盘限制。多个生产者(producers)可以把消息发送给同一个队列,同样,多个消费者(consumers)也能够从同一个队列(queue)中获取数据。队列可以绘制成这样(图上是队列的名称): 13 | ![](http://www.rabbitmq.com/img/tutorials/queue.png) 14 | 15 | - 在这里,消费(Consuming)和接收(receiving)是同一个意思。一个消费者(consumer)就是一个等待获取消息的程序。我们把它绘制为"C": 16 | ![](http://www.rabbitmq.com/img/tutorials/consumer.png) 17 | 18 | 需要指出的是生产者、消费者、代理需不要待在同一个设备上;事实上大多数应用也确实不在会将他们放在一台机器上。 19 | 20 | ##Hello World! 21 | 22 | **(使用pika 0.10.0 Python客户端)** 23 | 24 | 接下来我们用Python写两个小程序。一个发送单条消息的生产者(producer)和一个接收消息并将其输出的消费者(consumer)。传递的消息是"Hello World"。 25 | 26 | 下图中,“P”代表生产者,“C”代表消费者,中间的盒子代表为消费者保留的消息缓冲区,也就是我们的队列。 27 | 28 | ![](http://www.rabbitmq.com/img/tutorials/python-one-overall.png) 29 | 30 | 生产者(producer)把消息发送到一个名为“hello”的队列中。消费者(consumer)从这个队列中获取消息。 31 | 32 | >####RabbitMQ库 33 | 34 | >RabbitMQ使用的是AMQP 0.9.1协议。这是一个用于消息传递的开放、通用的协议。针对[不同编程语言](https://www.rabbitmq.com/devtools.html)有大量的RabbitMQ客户端可用。在这个系列教程中,RabbitMQ团队推荐使用[Pika](https://pika.readthedocs.org/en/0.10.0/#)这个Python客户端。大家可以通过[pip](https://pip.pypa.io/en/stable/quickstart/)这个包管理工具进行安装: 35 | 36 | ###发送 37 | 38 | ![](http://www.rabbitmq.com/img/tutorials/sending.png) 39 | 40 | 我们第一个程序`send.py`会发送一个消息到队列中。首先要做的事情就是建立一个到RabbitMQ服务器的连接。 41 | 42 | ```python 43 | #!/usr/bin/env python 44 | import pika 45 | 46 | connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) 47 | channel = connection.channel() 48 | ``` 49 | 50 | 现在我们已经跟本地机器的代理建立了连接。如果你想连接到其他机器的代理上,需要把代表本地的`localhost`改为指定的名字或IP地址。 51 | 52 | 接下来,在发送消息之前,我们需要确认服务于消费者的队列已经存在。如果将消息发送给一个不存在的队列,RabbitMQ会将消息丢弃掉。下面我们创建一个名为"hello"的队列用来将消息投递进去。 53 | 54 | ```python 55 | channel.queue_declare(queue='hello') 56 | ``` 57 | 58 | 这时候我们就可以发送消息了,我们第一条消息只包含了Hello World!字符串,我们打算把它发送到hello队列。 59 | 60 | 在RabbitMQ中,消息是不能直接发送到队列中的,这个过程需要通过交换机(exchange)来进行。但是为了不让细节拖累我们的进度,这里我们只需要知道如何使用由空字符串表示的默认交换机即可。如果你想要详细了解交换机,可以查看我们[教程的第三部分](https://www.rabbitmq.com/tutorials/tutorial-three-python.html)来获取更多细节。默认交换机比较特别,它允许我们指定消息究竟需要投递到哪个具体的队列中,队列名字需要在`routing_key`参数中指定。 61 | 62 | ```python 63 | channel.basic_publish(exchange='', 64 | routing_key='hello', 65 | body='Hello World!') 66 | print(" [x] Sent 'Hello World!'") 67 | ``` 68 | 69 | 在退出程序之前,我们需要确认网络缓冲已经被刷写、消息已经投递到RabbitMQ。通过安全关闭连接可以做到这一点。 70 | 71 | ```python 72 | connection.close() 73 | ``` 74 | 75 | > 发送不成功! 76 | 77 | > 如果这是你第一次使用RabbitMQ,并且没有看到“Sent”消息出现在屏幕上,你可能会抓耳挠腮不知所以。这也许是因为没有足够的磁盘空间给代理使用所造成的(代理默认需要200MB的空闲空间),所以它才会拒绝接收消息。查看一下代理的日志文件进行确认,如果需要的话也可以减少限制。[配置文件文档](http://www.rabbitmq.com/configure.html#config-items)会告诉你如何更改磁盘空间限制(disk_free_limit)。 78 | 79 | ###接收 80 | 81 | ![](https://www.rabbitmq.com/img/tutorials/receiving.png) 82 | 83 | 我们的第二个程序`receive.py`,将会从队列中获取消息并将其打印到屏幕上。 84 | 85 | 这次我们还是需要要先连接到RabbitMQ服务器。连接服务器的代码和之前是一样的。 86 | 87 | 下一步也和之前一样,我们需要确认队列是存在的。我们可以多次使用`queue_declare`命令来创建同一个队列,但是只有一个队列会被真正的创建。 88 | 89 | ```python 90 | channel.queue_declare(queue='hello') 91 | ``` 92 | 93 | 你也许要问: 为什么要重复声明队列呢 —— 我们已经在前面的代码中声明过它了。如果我们确定了队列是已经存在的,那么我们可以不这么做,比如此前预先运行了send.py程序。可是我们并不确定哪个程序会首先运行。这种情况下,在程序中重复将队列重复声明一下是种值得推荐的做法。 94 | 95 | >####列出所有队列 96 | 97 | >你也许希望查看RabbitMQ中有哪些队列、有多少消息在队列中。此时你可以使用rabbitmqctl工具(使用有权限的用户): 98 | >```bash 99 | >sudo rabbitmqctl list_queues 100 | >``` 101 | >(在Windows中不需要sudo命令) 102 | >```bash 103 | >rabbitmqctl list_queues 104 | >``` 105 | 106 | 从队列中获取消息相对来说稍显复杂。需要为队列定义一个回调(callback)函数。当我们获取到消息的时候,Pika库就会调用此回调函数。这个回调函数会将接收到的消息内容输出到屏幕上。 107 | 108 | ```python 109 | def callback(ch, method, properties, body): 110 | print(" [x] Received %r" % body) 111 | ``` 112 | 113 | 下一步,我们需要告诉RabbitMQ这个回调函数将会从名为"hello"的队列中接收消息: 114 | 115 | ```python 116 | channel.basic_consume(callback, 117 | queue='hello', 118 | no_ack=True) 119 | ``` 120 | 121 | 要成功运行这些命令,我们必须保证队列是存在的,我们的确可以确保它的存在——因为我们之前已经使用`queue_declare`将其声明过了。 122 | 123 | `no_ack`参数[稍后](https://www.rabbitmq.com/tutorials/tutorial-two-python.html)会进行介绍。 124 | 125 | 最后,我们运行一个用来等待消息数据并且在需要的时候运行回调函数的无限循环。 126 | 127 | ```python 128 | print(' [*] Waiting for messages. To exit press CTRL+C') 129 | channel.start_consuming() 130 | ``` 131 | 132 | ###将代码整合到一起 133 | 134 | **send.py的完整代码:** 135 | 136 | ```python 137 | #!/usr/bin/env python 138 | import pika 139 | 140 | connection = 141 | pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 142 | channel = connection.channel() 143 | 144 | channel.queue_declare(queue='hello') 145 | 146 | channel.basic_publish(exchange='', 147 | routing_key='hello', 148 | body='Hello World!') 149 | print(" [x] Sent 'Hello World!'") 150 | connection.close() 151 | ``` 152 | 153 | ([send.py源码](http://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/send.py)) 154 | 155 | **receive.py的完整代码:** 156 | 157 | ```python 158 | #!/usr/bin/env python 159 | import pika 160 | 161 | connection = 162 | pika.BlockingConnection(pika.ConnectionParameters(host='localhost')) 163 | channel = connection.channel() 164 | 165 | channel.queue_declare(queue='hello') 166 | 167 | def callback(ch, method, properties, body): 168 | print(" [x] Received %r" % body) 169 | 170 | channel.basic_consume(callback, 171 | queue='hello', 172 | no_ack=True) 173 | 174 | print(' [*] Waiting for messages. To exit press CTRL+C') 175 | channel.start_consuming() 176 | ``` 177 | 178 | ([receive.py源码](http://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/receive.py)) 179 | 180 | 现在我们可以在终端中尝试一下我们的程序了。 181 | 首先我们启动一个消费者,它会持续的运行来等待投递到达。 182 | 183 | ```bash 184 | python receive.py 185 | # => [*] Waiting for messages. To exit press CTRL+C 186 | # => [x] Received 'Hello World!' 187 | ``` 188 | 189 | 然后启动生产者,生产者程序每次执行后都会停止运行。 190 | 191 | ```bash 192 | python send.py 193 | # => [x] Sent 'Hello World!' 194 | ``` 195 | 196 | **成功了!**我们已经通过RabbitMQ发送第一条消息。你也许已经注意到了,receive.py程序并没有退出。它一直在准备获取消息,你可以通过Ctrl-C来中止它。 197 | 198 | 试下在新的终端中再次运行`send.py`。 199 | 200 | 我们已经学会如何发送消息到一个已知队列中并接收消息。是时候移步到第二部分了,我们将会建立一个简单的工作队列(work queue)。 201 | 202 | >原文:[Hello World](http://www.rabbitmq.com/tutorials/tutorial-one-python.html) 203 | >Updated at 2017-06-16 204 | -------------------------------------------------------------------------------- /published/tutorials_with_python/[2]Work_Queues.md: -------------------------------------------------------------------------------- 1 | >原文:[Work Queues](https://www.rabbitmq.com/tutorials/tutorial-two-python.html) 2 | >状态:待校对 3 | >翻译:[Adam](http://adamlu.net/dev/author/adam/) 4 | >校对:[Ping](http://weibo.com/370321376) 5 | 6 | ##工作队列 7 | 8 | **(使用pika 0.9.5 Python客户端)** 9 | 10 | ![](http://www.rabbitmq.com/img/tutorials/python-two.png) 11 | 12 | 在第一篇教程中,我们已经写了一个从已知队列中发送和获取消息的程序。在这篇教程中,我们将创建一个工作队列(Work Queue),它会发送一些耗时的任务给多个工作者(Worker)。 13 | 14 | 工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息发送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在它们之间共享。 15 | 16 | 这个概念在网络应用中是非常有用的,它可以在短暂的HTTP请求中处理一些复杂的任务。 17 | 18 | ##准备 19 | 20 | 之前的教程中,我们发送了一个包含“Hello World!”的字符串消息。现在,我们将发送一些字符串,把这些字符串当作复杂的任务。我们没有真实的例子,例如图片缩放、pdf文件转换。所以使用time.sleep()函数来模拟这种情况。我们在字符串中加上点号(.)来表示任务的复杂程度,一个点(.)将会耗时1秒钟。比如"Hello..."就会耗时3秒钟。 21 | 22 | 我们对之前教程的send.py做些简单的调整,以便可以发送随意的消息。这个程序会按照计划发送任务到我们的工作队列中。我们把它命名为new_task.py: 23 | 24 | ```python 25 | import sys 26 | 27 | message = ' '.join(sys.argv[1:]) or "Hello World!" 28 | channel.basic_publish(exchange='', 29 | routing_key='hello', 30 | body=message) 31 | print " [x] Sent %r" % (message,) 32 | ``` 33 | 34 | 我们的旧脚本(receive.py)同样需要做一些改动:它需要为消息体中每一个点号(.)模拟1秒钟的操作。它会从队列中获取消息并执行,我们把它命名为worker.py: 35 | 36 | ```python 37 | import time 38 | 39 | def callback(ch, method, properties, body): 40 | print " [x] Received %r" % (body,) 41 | time.sleep( body.count('.') ) 42 | print " [x] Done" 43 | ``` 44 | 45 | ##循环调度: 46 | 47 | 使用工作队列的一个好处就是它能够并行的处理队列。如果堆积了很多任务,我们只需要添加更多的工作者(workers)就可以了,扩展很简单。 48 | 49 | 首先,我们先同时运行两个worker.py脚本,它们都会从队列中获取消息,到底是不是这样呢?我们看看。 50 | 51 | 你需要打开三个终端,两个用来运行worker.py脚本,这两个终端就是我们的两个消费者(consumers)—— C1 和 C2。 52 | 53 | ```shell 54 | shell1$ python worker.py 55 | [*] Waiting for messages. To exit press CTRL+C 56 | ``` 57 | 58 | ```shell 59 | shell2$ python worker.py 60 | [*] Waiting for messages. To exit press CTRL+C 61 | ``` 62 | 第三个终端,我们用来发布新任务。你可以发送一些消息给消费者(consumers): 63 | 64 | ```shell 65 | shell3$ python new_task.py First message. 66 | shell3$ python new_task.py Second message.. 67 | shell3$ python new_task.py Third message... 68 | shell3$ python new_task.py Fourth message.... 69 | shell3$ python new_task.py Fifth message..... 70 | ``` 71 | 看看到底发送了什么给我们的工作者(workers): 72 | 73 | ```python 74 | shell1$ python worker.py 75 | [*] Waiting for messages. To exit press CTRL+C 76 | [x] Received 'First message.' 77 | [x] Received 'Third message...' 78 | [x] Received 'Fifth message.....' 79 | ``` 80 | 81 | ```python 82 | shell2$ python worker.py 83 | [*] Waiting for messages. To exit press CTRL+C 84 | [x] Received 'Second message..' 85 | [x] Received 'Fourth message....' 86 | ``` 87 | 默认来说,RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)。试着添加三个或更多得工作者(workers)。 88 | 89 | ##消息确认 90 | 91 | 当处理一个比较耗时得任务的时候,你也许想知道消费者(consumers)是否运行到一半就挂掉。当前的代码中,当消息被RabbitMQ发送给消费者(consumers)之后,马上就会在内存中移除。这种情况,你只要把一个工作者(worker)停止,正在处理的消息就会丢失。同时,所有发送到这个工作者的还没有处理的消息都会丢失。 92 | 93 | 我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望任务会重新发送给其他的工作者(worker)。 94 | 95 | 为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息。 96 | 97 | 如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,及时工作者(workers)偶尔的挂掉,也不会丢失消息。 98 | 99 | 消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。 100 | 101 | 消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。是时候移除这个标识了,当工作者(worker)完成了任务,就发送一个响应。 102 | 103 | ```python 104 | def callback(ch, method, properties, body): 105 | print " [x] Received %r" % (body,) 106 | time.sleep( body.count('.') ) 107 | print " [x] Done" 108 | ch.basic_ack(delivery_tag = method.delivery_tag) 109 | 110 | channel.basic_consume(callback, 111 | queue='hello') 112 | ``` 113 | 运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。当工作者(worker)挂掉这后,所有没有响应的消息都会重新发送。 114 | 115 | > #### 忘记确认 116 | 117 | > 一个很容易犯的错误就是忘了basic_ack,后果很严重。消息在你的程序退出之后就会重新发送,如果它不能够释放没响应的消息,RabbitMQ就会占用越来越多的内存。 118 | 119 | > 为了排除这种错误,你可以使用rabbitmqctl命令,输出messages_unacknowledged字段: 120 | 121 | > ``` 122 | > $ sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged 123 | > Listing queues ... 124 | > hello 0 0 125 | > ...done. 126 | > ``` 127 | 128 | ##消息持久化 129 | 130 | 如果你没有特意告诉RabbitMQ,那么在它退出或者崩溃的时候,将会丢失所有队列和消息。为了确保信息不会丢失,有两个事情是需要注意的:我们必须把“队列”和“消息”设为持久化。 131 | 132 | 首先,为了不让队列消失,需要把队列声明为持久化(durable): 133 | 134 | channel.queue_declare(queue='hello', durable=True) 135 | 136 | 尽管这行代码本身是正确的,但是仍然不会正确运行。因为我们已经定义过一个叫hello的非持久化队列。RabbitMq不允许你使用不同的参数重新定义一个队列,它会返回一个错误。但我们现在使用一个快捷的解决方法——用不同的名字,例如task_queue。 137 | 138 | channel.queue_declare(queue='task_queue', durable=True) 139 | 140 | 这个queue_declare必须在生产者(producer)和消费者(consumer)对应的代码中修改。 141 | 142 | 这时候,我们就可以确保在RabbitMq重启之后queue_declare队列不会丢失。另外,我们需要把我们的消息也要设为持久化——将delivery_mode的属性设为2。 143 | 144 | ```python 145 | channel.basic_publish(exchange='', 146 | routing_key="task_queue", 147 | body=message, 148 | properties=pika.BasicProperties( 149 | delivery_mode = 2, # make message persistent 150 | )) 151 | ``` 152 | 153 | > #### 注意:消息持久化 154 | 155 | > 将消息设为持久化并不能完全保证不会丢失。以上代码只是告诉了RabbitMq要把消息存到硬盘,但从RabbitMq收到消息到保存之间还是有一个很小的间隔时间。因为RabbitMq并不是所有的消息都使用fsync(2)——它有可能只是保存到缓存中,并不一定会写到硬盘中。并不能保证真正的持久化,但已经足够应付我们的简单工作队列。如果你一定要保证持久化,你需要改写你的代码来支持事务(transaction)。 156 | 157 | ##公平调度 158 | 159 | 你应该已经发现,它仍旧没有按照我们期望的那样进行分发。比如有两个工作者(workers),处理奇数消息的比较繁忙,处理偶数消息的比较轻松。然而RabbitMQ并不知道这些,它仍然一如既往的派发消息。 160 | 161 | 这时因为RabbitMQ只管分发进入队列的消息,不会关心有多少消费者(consumer)没有作出响应。它盲目的把第n-th条消息发给第n-th个消费者。 162 | 163 | ![](http://www.rabbitmq.com/img/tutorials/prefetch-count.png) 164 | 165 | 我们可以使用basic.qos方法,并设置prefetch_count=1。这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者(worker)。 166 | 167 | channel.basic_qos(prefetch_count=1) 168 | 169 | > #### 关于队列大小 170 | 171 | > 如果所有的工作者都处理繁忙状态,你的队列就会被填满。你需要留意这个问题,要么添加更多的工作者(workers),要么使用其他策略。 172 | 173 | ## 整合代码 174 | 175 | new_task.py的完整代码: 176 | 177 | ```python 178 | #!/usr/bin/env python 179 | import pika 180 | import sys 181 | 182 | connection = pika.BlockingConnection(pika.ConnectionParameters( 183 | host='localhost')) 184 | channel = connection.channel() 185 | 186 | channel.queue_declare(queue='task_queue', durable=True) 187 | 188 | message = ' '.join(sys.argv[1:]) or "Hello World!" 189 | channel.basic_publish(exchange='', 190 | routing_key='task_queue', 191 | body=message, 192 | properties=pika.BasicProperties( 193 | delivery_mode = 2, # make message persistent 194 | )) 195 | print " [x] Sent %r" % (message,) 196 | connection.close() 197 | ``` 198 | 199 | ([new_task.py](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/new_task.py)源码) 200 | 201 | 我们的worker: 202 | 203 | ```python 204 | #!/usr/bin/env python 205 | import pika 206 | import time 207 | 208 | connection = pika.BlockingConnection(pika.ConnectionParameters( 209 | host='localhost')) 210 | channel = connection.channel() 211 | 212 | channel.queue_declare(queue='task_queue', durable=True) 213 | print ' [*] Waiting for messages. To exit press CTRL+C' 214 | 215 | def callback(ch, method, properties, body): 216 | print " [x] Received %r" % (body,) 217 | time.sleep( body.count('.') ) 218 | print " [x] Done" 219 | ch.basic_ack(delivery_tag = method.delivery_tag) 220 | 221 | channel.basic_qos(prefetch_count=1) 222 | channel.basic_consume(callback, 223 | queue='task_queue') 224 | 225 | channel.start_consuming() 226 | ``` 227 | 228 | ([worker.py](http://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/worker.py) source) 229 | 230 | 使用消息响应和prefetch_count你就可以搭建起一个工作队列了。这些持久化的选项使得在RabbitMQ重启之后仍然能够恢复。 231 | 232 | 现在我们可以移步教程3学习如何发送相同的消息给多个消费者(consumers)。 -------------------------------------------------------------------------------- /published/tutorials_with_python/[3]Publish_Subscribe.md: -------------------------------------------------------------------------------- 1 | >原文:[Publish/Subscribe](https://www.rabbitmq.com/tutorials/tutorial-three-python.html) 2 | >状态:校对完毕 3 | >翻译:[Adam](http://adamlu.net/dev/author/adam/) 4 | >校对:[Ping](http://weibo.com/370321376) 5 | 6 | ## 发布/订阅 7 | 8 | **(使用pika 0.9.5 Python客户端)** 9 | 10 | 在上篇教程中,我们搭建了一个工作队列,每个任务只分发给一个工作者(worker)。在本篇教程中,我们要做的跟之前完全不一样 —— 分发一个消息给多个消费者(consumers)。这种模式被称为“发布/订阅”。 11 | 12 | 为了描述这种模式,我们将会构建一个简单的日志系统。它包括两个程序——第一个程序负责发送日志消息,第二个程序负责获取消息并输出内容。 13 | 14 | 在我们的这个日志系统中,所有正在运行的接收方程序都会接受消息。我们用其中一个接收者(receiver)把日志写入硬盘中,另外一个接受者(receiver)把日志输出到屏幕上。 15 | 16 | 最终,日志消息被广播给所有的接受者(receivers)。 17 | 18 | ## 交换机(Exchanges) 19 | 20 | 前面的教程中,我们发送消息到队列并从中取出消息。现在是时候介绍RabbitMQ中完整的消息模型了。 21 | 22 | 让我们简单的概括一下之前的教程: 23 | 24 | * 发布者(producer)是发布消息的应用程序。 25 | * 队列(queue)用于消息存储的缓冲。 26 | * 消费者(consumer)是接收消息的应用程序。 27 | 28 | RabbitMQ消息模型的核心理念是:发布者(producer)不会直接发送任何消息给队列。事实上,发布者(producer)甚至不知道消息是否已经被投递到队列。 29 | 30 | 发布者(producer)只需要把消息发送给一个交换机(exchange)。交换机非常简单,它一边从发布者方接收消息,一边把消息推送到队列。交换机必须知道如何处理它接收到的消息,是应该推送到指定的队列还是是多个队列,或者是直接忽略消息。这些规则是通过交换机类型(exchange type)来定义的。 31 | 32 | ![](http://www.rabbitmq.com/img/tutorials/exchanges.png) 33 | 34 | 有几个可供选择的交换机类型:直连交换机(direct), 主题交换机(topic), (头交换机)headers和 扇型交换机(fanout)。我们在这里主要说明最后一个 —— 扇型交换机(fanout)。先创建一个fanout类型的交换机,命名为logs: 35 | 36 | ```python 37 | channel.exchange_declare(exchange='logs', 38 | type='fanout') 39 | ``` 40 | 41 | 扇型交换机(fanout)很简单,你可能从名字上就能猜测出来,它把消息发送给它所知道的所有队列。这正是我们的日志系统所需要的。 42 | 43 | > #### 交换器列表 44 | 45 | > rabbitmqctl能够列出服务器上所有的交换器: 46 | 47 | > $ sudo rabbitmqctl list_exchanges 48 | > Listing exchanges ... 49 | > logs fanout 50 | > amq.direct direct 51 | > amq.topic topic 52 | > amq.fanout fanout 53 | > amq.headers headers 54 | > ...done. 55 | 56 | > 这个列表中有一些叫做amq.*的交换器。这些都是默认创建的,不过这时候你还不需要使用他们。 57 | 58 | > #### 匿名的交换器 59 | 60 | > 前面的教程中我们对交换机一无所知,但仍然能够发送消息到队列中。因为我们使用了命名为空字符串("")默认的交换机。 61 | 62 | > 回想我们之前是如何发布一则消息: 63 | 64 | > channel.basic_publish(exchange='', 65 | > routing_key='hello', 66 | > body=message) 67 | 68 | > exchange参数就是交换机的名称。空字符串代表默认或者匿名交换机:消息将会根据指定的routing_key分发到指定的队列。 69 | 70 | 现在,我们就可以发送消息到一个具名交换机了: 71 | 72 | ```python 73 | channel.basic_publish(exchange='logs', 74 | routing_key='', 75 | body=message) 76 | ``` 77 | 78 | ## 临时队列 79 | 80 | 你还记得之前我们使用的队列名吗( hello和task_queue)?给一个队列命名是很重要的——我们需要把工作者(workers)指向正确的队列。如果你打算在发布者(producers)和消费者(consumers)之间共享同队列的话,给队列命名是十分重要的。 81 | 82 | 但是这并不适用于我们的日志系统。我们打算接收所有的日志消息,而不仅仅是一小部分。我们关心的是最新的消息而不是旧的。为了解决这个问题,我们需要做两件事情。 83 | 84 | 首先,当我们连接上RabbitMQ的时候,我们需要一个全新的、空的队列。我们可以手动创建一个随机的队列名,或者让服务器为我们选择一个随机的队列名(推荐)。我们只需要在调用queue_declare方法的时候,不提供queue参数就可以了: 85 | 86 | result = channel.queue_declare() 87 | 88 | 这时候我们可以通过result.method.queue获得已经生成的随机队列名。它可能是这样子的:amq.gen-U0srCoW8TsaXjNh73pnVAw==。 89 | 90 | 第二步,当与消费者(consumer)断开连接的时候,这个队列应当被立即删除。exclusive标识符即可达到此目的。 91 | 92 | result = channel.queue_declare(exclusive=True) 93 | 94 | ## 绑定(Bindings) 95 | 96 | ![](http://www.rabbitmq.com/img/tutorials/bindings.png) 97 | 98 | 我们已经创建了一个扇型交换机(fanout)和一个队列。现在我们需要告诉交换机如何发送消息给我们的队列。交换器和队列之间的联系我们称之为绑定(binding)。 99 | 100 | ```python 101 | channel.queue_bind(exchange='logs', 102 | queue=result.method.queue) 103 | ``` 104 | 105 | 现在,logs交换机将会把消息添加到我们的队列中。 106 | 107 | > #### 绑定(binding)列表 108 | 109 | > 你可以使用`rabbitmqctl list_bindings` 列出所有现存的绑定。 110 | 111 | ## 代码整合 112 | 113 | ![](http://www.rabbitmq.com/img/tutorials/python-three-overall.png) 114 | 115 | 发布日志消息的程序看起来和之前的没有太大区别。最重要的改变就是我们把消息发送给logs交换机而不是匿名交换机。在发送的时候我们需要提供routing_key参数,但是它的值会被扇型交换机(fanout exchange)忽略。以下是emit_log.py脚本: 116 | 117 | ```python 118 | #!/usr/bin/env python 119 | import pika 120 | import sys 121 | 122 | connection = pika.BlockingConnection(pika.ConnectionParameters( 123 | host='localhost')) 124 | channel = connection.channel() 125 | 126 | channel.exchange_declare(exchange='logs', 127 | exchange_type='fanout') 128 | 129 | message = ' '.join(sys.argv[1:]) or "info: Hello World!" 130 | channel.basic_publish(exchange='logs', 131 | routing_key='', 132 | body=message) 133 | print " [x] Sent %r" % (message,) 134 | connection.close() 135 | ``` 136 | 137 | ([emit_log.py](http://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/emit_log.py) 源文件) 138 | 139 | 正如你看到的那样,在连接成功之后,我们声明了一个交换器,这一个是很重要的,因为不允许发布消息到不存在的交换器。 140 | 141 | 如果没有绑定队列到交换器,消息将会丢失。但这个没有所谓,如果没有消费者监听,那么消息就会被忽略。 142 | 143 | receive_logs.py的代码: 144 | 145 | ```python 146 | #!/usr/bin/env python 147 | import pika 148 | 149 | connection = pika.BlockingConnection(pika.ConnectionParameters( 150 | host='localhost')) 151 | channel = connection.channel() 152 | 153 | channel.exchange_declare(exchange='logs', 154 | exchange_type='fanout') 155 | 156 | result = channel.queue_declare(exclusive=True) 157 | queue_name = result.method.queue 158 | 159 | channel.queue_bind(exchange='logs', 160 | queue=queue_name) 161 | 162 | print ' [*] Waiting for logs. To exit press CTRL+C' 163 | 164 | def callback(ch, method, properties, body): 165 | print " [x] %r" % (body,) 166 | 167 | channel.basic_consume(callback, 168 | queue=queue_name, 169 | no_ack=True) 170 | 171 | channel.start_consuming() 172 | ``` 173 | 174 | ([receive_logs.py](http://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/receive_logs.py) source) 175 | 176 | 这样我们就完成了。如果你想把日志保存到文件中,只需要打开控制台输入: 177 | 178 | $ python receive_logs.py > logs_from_rabbit.log 179 | 180 | 如果你想在屏幕中查看日志,那么打开一个新的终端然后运行: 181 | 182 | $ python receive_logs.py 183 | 184 | 当然还要发送日志: 185 | 186 | $ python emit_log.py 187 | 188 | 使用`rabbitmqctl list_bindings`你可确认已经创建的队列绑定。你可以看到运行中的两个receive_logs.py程序: 189 | 190 | $ sudo rabbitmqctl list_bindings 191 | Listing bindings ... 192 | ... 193 | logs amq.gen-TJWkez28YpImbWdRKMa8sg== [] 194 | logs amq.gen-x0kymA4yPzAT6BoC/YP+zw== [] 195 | ...done. 196 | 197 | 显示结果很直观:logs交换器把数据发送给两个系统命名的队列。这就是我们所期望的。 198 | 199 | 如何监听消息的子集呢?让我们移步教程4 -------------------------------------------------------------------------------- /published/tutorials_with_python/[4]Routing.md: -------------------------------------------------------------------------------- 1 | >原文:[Routing](https://www.rabbitmq.com/tutorials/tutorial-four-python.html) 2 | >状态:翻译完成 3 | >翻译:[Adam](http://adamlu.net/dev/author/adam/) 4 | >校对:[Ping](http://weibo.com/370321376) 5 | 6 | ## 路由(Routing) 7 | 8 | **(使用pika 0.9.5 Python客户端)** 9 | 10 | 在前面的教程中,我们实现了一个简单的日志系统。可以把日志消息广播给多个接收者。 11 | 12 | 本篇教程中我们打算新增一个功能 —— 使得它能够只订阅消息的一个字集。例如,我们只需要把严重的错误日志信息写入日志文件(存储到磁盘),但同时仍然把所有的日志信息输出到控制台中 13 | 14 | ## 绑定(Bindings) 15 | 16 | 前面的例子,我们已经创建过绑定(bindings),代码如下: 17 | 18 | ```python 19 | channel.queue_bind(exchange=exchange_name, 20 | queue=queue_name) 21 | ``` 22 | 23 | 绑定(binding)是指交换机(exchange)和队列(queue)的关系。可以简单理解为:这个队列(queue)对这个交换机(exchange)的消息感兴趣。 24 | 25 | 绑定的时候可以带上一个额外的routing_key参数。为了避免与basic_publish的参数混淆,我们把它叫做绑定键(binding key)。以下是如何创建一个带绑定键的绑定。 26 | 27 | ```python 28 | channel.queue_bind(exchange=exchange_name, 29 | queue=queue_name, 30 | routing_key='black') 31 | ``` 32 | 33 | 绑定键的意义取决于交换机(exchange)的类型。我们之前使用过的扇型交换机(fanout exchanges)会忽略这个值。 34 | 35 | ## 直连交换机(Direct exchange) 36 | 37 | 我们的日志系统广播所有的消息给所有的消费者(consumers)。我们打算扩展它,使其基于日志的严重程度进行消息过滤。例如我们也许只是希望将比较严重的错误(error)日志写入磁盘,以免在警告(warning)或者信息(info)日志上浪费磁盘空间。 38 | 39 | 我们使用的扇型交换机(fanout exchange)没有足够的灵活性 —— 它能做的仅仅是广播。 40 | 41 | 我们将会使用直连交换机(direct exchange)来代替。路由的算法很简单 —— 交换机将会对绑定键(binding key)和路由键(routing key)进行精确匹配,从而确定消息该分发到哪个队列。 42 | 43 | 下图能够很好的描述这个场景: 44 | 45 | ![](http://www.rabbitmq.com/img/tutorials/direct-exchange.png) 46 | 47 | 在这个场景中,我们可以看到直连交换机 X和两个队列进行了绑定。第一个队列使用orange作为绑定键,第二个队列有两个绑定,一个使用black作为绑定键,另外一个使用green。 48 | 49 | 这样以来,当路由键为orange的消息发布到交换机,就会被路由到队列Q1。路由键为black或者green的消息就会路由到Q2。其他的所有消息都将会被丢弃。 50 | 51 | ## 多个绑定(Multiple bindings) 52 | 53 | ![](http://www.rabbitmq.com/img/tutorials/direct-exchange-multiple.png) 54 | 55 | 多个队列使用相同的绑定键是合法的。这个例子中,我们可以添加一个X和Q1之间的绑定,使用black绑定键。这样一来,直连交换机就和扇型交换机的行为一样,会将消息广播到所有匹配的队列。带有black路由键的消息会同时发送到Q1和Q2。 56 | 57 | ## 发送日志 58 | 59 | 我们将会发送消息到一个直连交换机,把日志级别作为路由键。这样接收日志的脚本就可以根据严重级别来选择它想要处理的日志。我们先看看发送日志。 60 | 61 | 我们需要创建一个交换机(exchange): 62 | 63 | ```python 64 | channel.exchange_declare(exchange='direct_logs', 65 | type='direct') 66 | ``` 67 | 68 | 然后我们发送一则消息: 69 | 70 | ```python 71 | channel.basic_publish(exchange='direct_logs', 72 | routing_key=severity, 73 | body=message) 74 | ``` 75 | 76 | 我们先假设“severity”的值是info、warning、error中的一个。 77 | 78 | ## 订阅 79 | 80 | 处理接收消息的方式和之前差不多,只有一个例外,我们将会为我们感兴趣的每个严重级别分别创建一个新的绑定。 81 | 82 | ```python 83 | result = channel.queue_declare(exclusive=True) 84 | queue_name = result.method.queue 85 | 86 | for severity in severities: 87 | channel.queue_bind(exchange='direct_logs', 88 | queue=queue_name, 89 | routing_key=severity) 90 | ``` 91 | 92 | ## 代码整合 93 | 94 | ![](http://www.rabbitmq.com/img/tutorials/python-four.png) 95 | 96 | emit_log_direct.py的代码: 97 | 98 | ```python 99 | #!/usr/bin/env python 100 | import pika 101 | import sys 102 | 103 | connection = pika.BlockingConnection(pika.ConnectionParameters( 104 | host='localhost')) 105 | channel = connection.channel() 106 | 107 | channel.exchange_declare(exchange='direct_logs', 108 | type='direct') 109 | 110 | severity = sys.argv[1] if len(sys.argv) > 1 else 'info' 111 | message = ' '.join(sys.argv[2:]) or 'Hello World!' 112 | channel.basic_publish(exchange='direct_logs', 113 | routing_key=severity, 114 | body=message) 115 | print " [x] Sent %r:%r" % (severity, message) 116 | connection.close() 117 | ``` 118 | 119 | receive_logs_direct.py的代码: 120 | 121 | ```python 122 | #!/usr/bin/env python 123 | import pika 124 | import sys 125 | 126 | connection = pika.BlockingConnection(pika.ConnectionParameters( 127 | host='localhost')) 128 | channel = connection.channel() 129 | 130 | channel.exchange_declare(exchange='direct_logs', 131 | type='direct') 132 | 133 | result = channel.queue_declare(exclusive=True) 134 | queue_name = result.method.queue 135 | 136 | severities = sys.argv[1:] 137 | if not severities: 138 | print >> sys.stderr, "Usage: %s [info] [warning] [error]" % \ 139 | (sys.argv[0],) 140 | sys.exit(1) 141 | 142 | for severity in severities: 143 | channel.queue_bind(exchange='direct_logs', 144 | queue=queue_name, 145 | routing_key=severity) 146 | 147 | print ' [*] Waiting for logs. To exit press CTRL+C' 148 | 149 | def callback(ch, method, properties, body): 150 | print " [x] %r:%r" % (method.routing_key, body,) 151 | 152 | channel.basic_consume(callback, 153 | queue=queue_name, 154 | no_ack=True) 155 | 156 | channel.start_consuming() 157 | ``` 158 | 159 | 如果你希望只是保存warning和error级别的日志到磁盘,只需要打开控制台并输入: 160 | 161 | $ python receive_logs_direct.py warning error > logs_from_rabbit.log 162 | 163 | 如果你希望所有的日志信息都输出到屏幕中,打开一个新的终端,然后输入: 164 | 165 | $ python receive_logs_direct.py info warning error 166 | [*] Waiting for logs. To exit press CTRL+C 167 | 168 | 如果要触发一个error级别的日志,只需要输入: 169 | 170 | $ python emit_log_direct.py error "Run. Run. Or it will explode." 171 | [x] Sent 'error':'Run. Run. Or it will explode.' 172 | 173 | 这里是完整的代码:([emit_log_direct.py][1]和[receive_logs_direct.py][2]) 174 | 175 | 176 | [1]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/emit_log_direct.py 177 | [2]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/receive_logs_direct.py -------------------------------------------------------------------------------- /published/tutorials_with_python/[5]Topics.md: -------------------------------------------------------------------------------- 1 | >原文:[Topics](https://www.rabbitmq.com/tutorials/tutorial-five-python.html) 2 | >状态:翻译完成 3 | >翻译:[Ping](http://weibo.com/370321376) 4 | >校对:[Ping](http://weibo.com/370321376) 5 | 6 | ##为什么需要主题交换机? 7 | 8 | (使用Python 客户端 —— pika 0.9.8) 9 | 10 | [上一篇教程]()里,我们改进了我们的日志系统。我们使用直连交换机替代了扇型交换机,从只能盲目的广播消息改进为有可能选择性的接收日志。 11 | 12 | 尽管直连交换机能够改善我们的系统,但是它也有它的限制 —— 没办法基于多个标准执行路由操作。 13 | 14 | 在我们的日志系统中,我们不只希望订阅基于严重程度的日志,同时还希望订阅基于发送来源的日志。Unix工具[syslog](http://en.wikipedia.org/wiki/Syslog)就是同时基于严重程度-severity (info/warn/crit...) 和 设备-facility (auth/cron/kern...)来路由日志的。 15 | 16 | 如果这样的话,将会给予我们非常大的灵活性,我们既可以监听来源于“cron”的严重程度为“critical errors”的日志,也可以监听来源于“kern”的所有日志。 17 | 18 | 为了实现这个目的,接下来我们学习如何使用另一种更复杂的交换机 —— 主题交换机。 19 | 20 | ##主题交换机 21 | 22 | 发送到主题交换机(topic exchange)的消息不可以携带随意什么样子的路由键(routing_key),它的路由键必须是一个由`.`分隔开的词语列表。这些单词随便是什么都可以,但是最好是跟携带它们的消息有关系的词汇。以下是几个推荐的例子:"stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit"。词语的个数可以随意,但是不要超过255字节。 23 | 24 | 绑定键也必须拥有同样的格式。主题交换机背后的逻辑跟直连交换机很相似 —— 一个携带着特定路由键的消息会被主题交换机投递给绑定键与之想匹配的队列。但是它的绑定键和路由键有两个特殊应用方式: 25 | 26 | - `*` (星号) 用来表示一个单词. 27 | - `#` (井号) 用来表示任意数量(零个或多个)单词。 28 | 29 | 下边用图说明: 30 | ![None](http://www.rabbitmq.com/img/tutorials/python-five.png) 31 | 32 | 这个例子里,我们发送的所有消息都是用来描述小动物的。发送的消息所携带的路由键是由三个单词所组成的,这三个单词被两个`.`分割开。路由键里的第一个单词描述的是动物的手脚的利索程度,第二个单词是动物的颜色,第三个是动物的种类。所以它看起来是这样的: `..`。 33 | 34 | 我们创建了三个绑定:Q1的绑定键为 `*.orange.*`,Q2的绑定键为 `*.*.rabbit` 和 `lazy.#` 。 35 | 36 | 这三个绑定键被可以总结为: 37 | 38 | - Q1 对*所有的桔黄色动物*都感兴趣。 39 | - Q2 则是对*所有的兔子*和*所有懒惰的动物*感兴趣。 40 | 41 | 一个携带有 `quick.orange.rabbit` 的消息将会被分别投递给这两个队列。携带着 `lazy.orange.elephant` 的消息同样也会给两个队列都投递过去。另一方面携带有 `quick.orange.fox` 的消息会投递给第一个队列,携带有 `lazy.brown.fox` 的消息会投递给第二个队列。携带有 `lazy.pink.rabbit` 的消息只会被投递给第二个队列一次,即使它同时匹配第二个队列的两个绑定。携带着 `quick.brown.fox` 的消息不会投递给任何一个队列。 42 | 43 | 如果我们违反约定,发送了一个携带有一个单词或者四个单词(`"orange"` or `"quick.orange.male.rabbit"`)的消息时,发送的消息不会投递给任何一个队列,而且会丢失掉。 44 | 45 | 但是另一方面,即使 `"lazy.orange.male.rabbit"` 有四个单词,他还是会匹配最后一个绑定,并且被投递到第二个队列中。 46 | 47 | >####主题交换机 48 | 49 | >主题交换机是很强大的,它可以表现出跟其他交换机类似的行为 50 | 51 | >当一个队列的绑定键为 "#"(井号) 的时候,这个队列将会无视消息的路由键,接收所有的消息。 52 | 53 | >当 `*` (星号) 和 `#` (井号) 这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。 54 | 55 | ##组合在一起 56 | 57 | 接下来我们会将主题交换机应用到我们的日志系统中。在开始工作前,我们假设日志的路由键由两个单词组成,路由键看起来是这样的:`.` 58 | 59 | 代码跟[上一篇教程]()差不多。 60 | 61 | emit_log_topic.py的代码: 62 | 63 | ```python 64 | #!/usr/bin/env python 65 | import pika 66 | import sys 67 | 68 | connection = pika.BlockingConnection(pika.ConnectionParameters( 69 | host='localhost')) 70 | channel = connection.channel() 71 | 72 | channel.exchange_declare(exchange='topic_logs', 73 | type='topic') 74 | 75 | routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info' 76 | message = ' '.join(sys.argv[2:]) or 'Hello World!' 77 | channel.basic_publish(exchange='topic_logs', 78 | routing_key=routing_key, 79 | body=message) 80 | print " [x] Sent %r:%r" % (routing_key, message) 81 | connection.close() 82 | ``` 83 | 84 | receive_logs_topic.py的代码: 85 | 86 | ```python 87 | #!/usr/bin/env python 88 | import pika 89 | import sys 90 | 91 | connection = pika.BlockingConnection(pika.ConnectionParameters( 92 | host='localhost')) 93 | channel = connection.channel() 94 | 95 | channel.exchange_declare(exchange='topic_logs', 96 | type='topic') 97 | 98 | result = channel.queue_declare(exclusive=True) 99 | queue_name = result.method.queue 100 | 101 | binding_keys = sys.argv[1:] 102 | if not binding_keys: 103 | print >> sys.stderr, "Usage: %s [binding_key]..." % (sys.argv[0],) 104 | sys.exit(1) 105 | 106 | for binding_key in binding_keys: 107 | channel.queue_bind(exchange='topic_logs', 108 | queue=queue_name, 109 | routing_key=binding_key) 110 | 111 | print ' [*] Waiting for logs. To exit press CTRL+C' 112 | 113 | def callback(ch, method, properties, body): 114 | print " [x] %r:%r" % (method.routing_key, body,) 115 | 116 | channel.basic_consume(callback, 117 | queue=queue_name, 118 | no_ack=True) 119 | 120 | channel.start_consuming() 121 | ``` 122 | 123 | 执行下边命令 接收所有日志: 124 | `python receive_logs_topic.py "#"` 125 | 126 | 执行下边命令 接收来自”kern“设备的日志: 127 | `python receive_logs_topic.py "kern.*"` 128 | 129 | 执行下边命令 只接收严重程度为”critical“的日志: 130 | `python receive_logs_topic.py "*.critical"` 131 | 132 | 执行下边命令 建立多个绑定: 133 | `python receive_logs_topic.py "kern.*" "*.critical"` 134 | 135 | 执行下边命令 发送路由键为 "kern.critical" 的日志: 136 | `python emit_log_topic.py "kern.critical" "A critical kernel error"` 137 | 138 | 执行上边命令试试看效果吧。另外,上边代码不会对路由键和绑定键做任何假设,所以你可以在命令中使用超过两个路由键参数。 139 | 140 | ###如果你现在还没被搞晕,想想下边问题: 141 | - 绑定键为 `*` 的队列会取到一个路由键为空的消息吗? 142 | - 绑定键为 `#.*` 的队列会获取到一个名为`..`的路由键的消息吗?它会取到一个路由键为单个单词的消息吗? 143 | - `a.*.#` 和 `a.#`的区别在哪儿? 144 | 145 | (完整代码参见[emit_logs_topic.py](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/emit_log_topic.py) and [receive_logs_topic.py](https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/receive_logs_topic.py)) 146 | 147 | 移步至[教程 6]() 学习RPC。 148 | -------------------------------------------------------------------------------- /published/tutorials_with_python/[6]RPC.md: -------------------------------------------------------------------------------- 1 | >原文:[Remote procedure call (RPC)](https://www.rabbitmq.com/tutorials/tutorial-six-python.html) 2 | >状态:翻译完成 3 | >翻译:[Ping](http://weibo.com/370321376) 4 | >校对:[Ping](http://weibo.com/370321376) 5 | 6 | ## 远程过程调用(RPC) 7 | 8 | #### (Python客户端 —— 使用 pika 0.9.8) 9 | 10 | 在[第二篇教程][4]中我们介绍了如何使用工作队列(work queue)在多个工作者(woker)中间分发耗时的任务。 11 | 12 | 可是如果我们需要将一个函数运行在远程计算机上并且等待从那儿获取结果时,该怎么办呢?这就是另外的故事了。这种模式通常被称为远程过程调用(Remote Procedure Call)或者RPC。 13 | 14 | 这篇教程中,我们会使用RabbitMQ来构建一个RPC系统:包含一个客户端和一个RPC服务器。现在的情况是,我们没有一个值得被分发的足够耗时的任务,所以接下来,我们会创建一个模拟RPC服务来返回斐波那契数列。 15 | 16 | ### 客户端接口 17 | 18 | 为了展示RPC服务如何使用,我们创建了一个简单的客户端类。它会暴露出一个名为“call”的方法用来发送一个RPC请求,并且在收到回应前保持阻塞。 19 | 20 | ```python 21 | fibonacci_rpc = FibonacciRpcClient() 22 | result = fibonacci_rpc.call(4) 23 | print "fib(4) is %r" % (result,) 24 | ``` 25 | 26 | > ####关于RPC的注意事项: 27 | 28 | > 尽管RPC在计算领域是一个常用模式,但它也经常被诟病。当一个问题被抛出的时候,程序员往往意识不到这到底是由本地调用还是由较慢的RPC调用引起的。同样的困惑还来自于系统的不可预测性和给调试工作带来的不必要的复杂性。跟软件精简不同的是,滥用RPC会导致不可维护的[面条代码][5]. 29 | 30 | > 考虑到这一点,牢记以下建议: 31 | 32 | > 确保能够明确的搞清楚哪个函数是本地调用的,哪个函数是远程调用的。给你的系统编写文档。保持各个组件间的依赖明确。处理错误案例。明了客户端改如何处理RPC服务器的宕机和长时间无响应情况。 33 | 34 | > 当对避免使用RPC有疑问的时候。如果可以的话,你应该尽量使用异步管道来代替RPC类的阻塞。结果被异步地推送到下一个计算场景。 35 | 36 | ### 回调队列 37 | 38 | 一般来说通过RabbitMQ来实现RPC是很容易的。一个客户端发送请求信息,服务器端将其应用到一个回复信息中。为了接收到回复信息,客户端需要在发送请求的时候同时发送一个回调队列(callback queue)的地址。我们试试看: 39 | 40 | ```python 41 | result = channel.queue_declare(exclusive=True) 42 | callback_queue = result.method.queue 43 | 44 | channel.basic_publish(exchange='', 45 | routing_key='rpc_queue', 46 | properties=pika.BasicProperties( 47 | reply_to = callback_queue, 48 | ), 49 | body=request) 50 | 51 | # ... and some code to read a response message from the callback_queue ... 52 | ``` 53 | 54 | > ####消息属性 55 | 56 | > AMQP协议给消息预定义了一系列的14个属性。大多数属性很少会用到,除了以下几个: 57 | 58 | > * delivery_mode(投递模式):将消息标记为持久的(值为2)或暂存的(除了2之外的其他任何值)。第二篇教程里接触过这个属性,记得吧? 59 | * content_type(内容类型):用来描述编码的mime-type。例如在实际使用中常常使用application/json来描述JOSN编码类型。 60 | * reply_to(回复目标):通常用来命名回调队列。 61 | * correlation_id(关联标识):用来将RPC的响应和请求关联起来。 62 | 63 | ### 关联标识 64 | 65 | 上边介绍的方法中,我们建议给每一个RPC请求新建一个回调队列。这不是一个高效的做法,幸好这儿有一个更好的办法 —— 我们可以为每个客户端只建立一个独立的回调队列。 66 | 67 | 这就带来一个新问题,当此队列接收到一个响应的时候它无法辨别出这个响应是属于哪个请求的。**correlation\_id** 就是为了解决这个问题而来的。我们给每个请求设置一个独一无二的值。稍后,当我们从回调队列中接收到一个消息的时候,我们就可以查看这条属性从而将响应和请求匹配起来。如果我们接手到的消息的correlation\_id是未知的,那就直接销毁掉它,因为它不属于我们的任何一条请求。 68 | 69 | 你也许会问,为什么我们接收到未知消息的时候不抛出一个错误,而是要将它忽略掉?这是为了解决服务器端有可能发生的竞争情况。尽管可能性不大,但RPC服务器还是有可能在已将应答发送给我们但还未将确认消息发送给请求的情况下死掉。如果这种情况发生,RPC在重启后会重新处理请求。这就是为什么我们必须在客户端优雅的处理重复响应,同时RPC也需要尽可能保持幂等性。 70 | 71 | ### 总结 72 | 73 | ![](http://www.rabbitmq.com/img/tutorials/python-six.png) 74 | 75 | 我们的RPC如此工作: 76 | 77 | * 当客户端启动的时候,它创建一个匿名独享的回调队列。 78 | * 在RPC请求中,客户端发送带有两个属性的消息:一个是设置回调队列的 *reply\_to* 属性,另一个是设置唯一值的 *correlation\_id* 属性。 79 | * 将请求发送到一个 *rpc\_queue* 队列中。 80 | * RPC工作者(又名:服务器)等待请求发送到这个队列中来。当请求出现的时候,它执行他的工作并且将带有执行结果的消息发送给reply\_to字段指定的队列。 81 | * 客户端等待回调队列里的数据。当有消息出现的时候,它会检查correlation_id属性。如果此属性的值与请求匹配,将它返回给应用。 82 | 83 | ## 整合到一起 84 | 85 | rpc_server.py代码: 86 | 87 | ```python 88 | #!/usr/bin/env python 89 | import pika 90 | 91 | connection = pika.BlockingConnection(pika.ConnectionParameters( 92 | host='localhost')) 93 | 94 | channel = connection.channel() 95 | 96 | channel.queue_declare(queue='rpc_queue') 97 | 98 | def fib(n): 99 | if n == 0: 100 | return 0 101 | elif n == 1: 102 | return 1 103 | else: 104 | return fib(n-1) + fib(n-2) 105 | 106 | def on_request(ch, method, props, body): 107 | n = int(body) 108 | 109 | print " [.] fib(%s)" % (n,) 110 | response = fib(n) 111 | 112 | ch.basic_publish(exchange='', 113 | routing_key=props.reply_to, 114 | properties=pika.BasicProperties(correlation_id = \ 115 | props.correlation_id), 116 | body=str(response)) 117 | ch.basic_ack(delivery_tag = method.delivery_tag) 118 | 119 | channel.basic_qos(prefetch_count=1) 120 | channel.basic_consume(on_request, queue='rpc_queue') 121 | 122 | print " [x] Awaiting RPC requests" 123 | channel.start_consuming() 124 | ``` 125 | 126 | 服务器端代码相当简单: 127 | 128 | * (4)像往常一样,我们建立连接,声明队列 129 | * (11)我们声明我们的fibonacci函数,它假设只有合法的正整数当作输入。(别指望这个函数能处理很大的数值,函数递归你们都懂得...) 130 | * (19)我们为 basic_consume 声明了一个回调函数,这是RPC服务器端的核心。它执行实际的操作并且作出响应。 131 | * (32)或许我们希望能在服务器上多开几个线程。为了能将负载平均地分摊到多个服务器,我们需要将 prefetch_count 设置好。 132 | 133 | rpc_client.py 代码: 134 | 135 | ```python 136 | #!/usr/bin/env python 137 | import pika 138 | import uuid 139 | 140 | class FibonacciRpcClient(object): 141 | def __init__(self): 142 | self.connection = pika.BlockingConnection(pika.ConnectionParameters( 143 | host='localhost')) 144 | 145 | self.channel = self.connection.channel() 146 | 147 | result = self.channel.queue_declare(exclusive=True) 148 | self.callback_queue = result.method.queue 149 | 150 | self.channel.basic_consume(self.on_response, no_ack=True, 151 | queue=self.callback_queue) 152 | 153 | def on_response(self, ch, method, props, body): 154 | if self.corr_id == props.correlation_id: 155 | self.response = body 156 | 157 | def call(self, n): 158 | self.response = None 159 | self.corr_id = str(uuid.uuid4()) 160 | self.channel.basic_publish(exchange='', 161 | routing_key='rpc_queue', 162 | properties=pika.BasicProperties( 163 | reply_to = self.callback_queue, 164 | correlation_id = self.corr_id, 165 | ), 166 | body=str(n)) 167 | while self.response is None: 168 | self.connection.process_data_events() 169 | return int(self.response) 170 | 171 | fibonacci_rpc = FibonacciRpcClient() 172 | 173 | print " [x] Requesting fib(30)" 174 | response = fibonacci_rpc.call(30) 175 | print " [.] Got %r" % (response,) 176 | ``` 177 | 178 | 客户端代码稍微有点难懂: 179 | 180 | * (7)建立连接、通道并且为回复(replies)声明独享的回调队列。 181 | * (16)我们订阅这个回调队列,以便接收RPC的响应。 182 | * (18)“on\_response”回调函数对每一个响应执行一个非常简单的操作,检查每一个响应消息的correlation\_id属性是否与我们期待的一致,如果一致,将响应结果赋给self.response,然后跳出consuming循环。 183 | * (23)接下来,我们定义我们的主要方法 call 方法。它执行真正的RPC请求。 184 | * (24)在这个方法中,首先我们生成一个唯一的 correlation\_id 值并且保存起来,'on\_response'回调函数会用它来获取符合要求的响应。 185 | * (25)接下来,我们将带有 reply\_to 和 correlation\_id 属性的消息发布出去。 186 | * (32)现在我们可以坐下来,等待正确的响应到来。 187 | * (33)最后,我们将响应返回给用户。 188 | 189 | 我们的RPC服务已经准备就绪了,现在启动服务器端: 190 | 191 | ```python 192 | $ python rpc_server.py 193 | [x] Awaiting RPC requests 194 | ``` 195 | 196 | 运行客户端,请求一个fibonacci队列。 197 | 198 | ```python 199 | $ python rpc_client.py 200 | [x] Requesting fib(30) 201 | ``` 202 | 203 | 此处呈现的设计并不是实现RPC服务的唯一方式,但是他有一些重要的优势: 204 | 205 | * 如果RPC服务器运行的过慢的时候,你可以通过运行另外一个服务器端轻松扩展它。试试在控制台中运行第二个 rpc_server.py 。 206 | * 在客户端,RPC请求只发送或接收一条消息。不需要像 queue_declare 这样的异步调用。所以RPC客户端的单个请求只需要一个网络往返。 207 | 208 | 我们的代码依旧非常简单,而且没有试图去解决一些复杂(但是重要)的问题,如: 209 | 210 | * 当没有服务器运行时,客户端如何作出反映。 211 | * 客户端是否需要实现类似RPC超时的东西。 212 | * 如果服务器发生故障,并且抛出异常,应该被转发到客户端吗? 213 | * 在处理前,防止混入无效的信息(例如检查边界) 214 | 215 | > 如果你想做一些实验,你会发现[rabbitmq-management plugin][0]在观测队列方面是很有用处的。 216 | 217 | (完整的[rpc_client.py][1] 和 [rpc_server.py][2]代码) 218 | 219 | 220 | [0]:http://www.rabbitmq.com/plugins.html 221 | [1]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/rpc_client.py 222 | [2]:https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/python/rpc_server.py 223 | [4]:http://www.rabbitmq.com/tutorials/tutorial-two-python.html 224 | [5]:http://zh.wikipedia.org/wiki/%E9%9D%A2%E6%9D%A1%E5%BC%8F%E4%BB%A3%E7%A0%81 -------------------------------------------------------------------------------- /source/AMQP_0-9-1_Quick_Reference.md: -------------------------------------------------------------------------------- 1 | ## AMQP 0-9-1 Quick Reference 2 | 3 | This page provides a guide to RabbitMQ's implementation of AMQP 0-9-1. In addition to the classes and methods defined in the AMQP specification, RabbitMQ supports several protocol extensions, which are also listed here. The original and extended specification downloads can be found on the protocol page. 4 | 5 | For your convenience, links are provided from this guide to the relevant sections of the API guides for the RabbitMQ Java and .NET clients. Full details of each method and its parameters are available in our complete AMQP 0-9-1 reference. 6 | 7 | ### Basic 8 | 9 | ###### basic.ack(delivery-tag delivery-tag, bit multiple) 10 | 11 | 支持: 全部 12 | 确认一条或多条消息。 13 | 14 | When sent by the client, this method acknowledges one or more messages delivered via the Deliver or Get-Ok methods. When sent by server, this method acknowledges one or more messages published with the Publish method on a channel in confirm mode. The acknowledgement can be for a single message or a set of messages up to and including a specific message. 15 | 16 | 当传送到客户端时,这个方法会确认一条或多条消息已经通过 Deliver 或 Get-Ok 方法投递成功。 17 | 18 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 19 | basic.cancel(consumer-tag consumer-tag, no-wait no-wait) ➔ cancel-ok 20 | 21 | Support: full 22 | End a queue consumer. 23 | 24 | This method cancels a consumer. This does not affect already delivered messages, but it does mean the server will not send any more messages for that consumer. The client may receive an arbitrary number of messages in between sending the cancel method and receiving the cancel-ok reply. It may also be sent from the server to the client in the event of the consumer being unexpectedly cancelled (i.e. cancelled for any reason other than the server receiving the corresponding basic.cancel from the client). This allows clients to be notified of the loss of consumers due to events such as queue deletion. Note that as it is not a MUST for clients to accept this method from the client, it is advisable for the broker to be able to identify those clients that are capable of accepting the method, through some means of capability negotiation. 25 | 26 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 27 | basic.consume(short reserved-1, queue-name queue, consumer-tag consumer-tag, no-local no-local, no-ack no-ack, bit exclusive, no-wait no-wait, table arguments) ➔ consume-ok 28 | 29 | Support: partial 30 | Start a queue consumer. 31 | 32 | This method asks the server to start a "consumer", which is a transient request for messages from a specific queue. Consumers last as long as the channel they were declared on, or until the client cancels them. 33 | 34 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 35 | basic.deliver(consumer-tag consumer-tag, delivery-tag delivery-tag, redelivered redelivered, exchange-name exchange, shortstr routing-key) 36 | 37 | Support: full 38 | Notify the client of a consumer message. 39 | 40 | This method delivers a message to the client, via a consumer. In the asynchronous message delivery model, the client starts a consumer using the Consume method, then the server responds with Deliver methods as and when messages arrive for that consumer. 41 | 42 | [amqpdoc](back to top) 43 | basic.get(short reserved-1, queue-name queue, no-ack no-ack) ➔ get-ok | get-empty 44 | 45 | Support: full 46 | Direct access to a queue. 47 | 48 | This method provides a direct access to the messages in a queue using a synchronous dialogue that is designed for specific types of application where synchronous functionality is more important than performance. 49 | 50 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 51 | basic.nack(delivery-tag delivery-tag, bit multiple, bit requeue) 52 | 53 | THIS METHOD IS A RABBITMQ-SPECIFIC EXTENSION OF AMQP 54 | 55 | Reject one or more incoming messages. 56 | 57 | This method allows a client to reject one or more incoming messages. It can be used to interrupt and cancel large incoming messages, or return untreatable messages to their original queue. This method is also used by the server to inform publishers on channels in confirm mode of unhandled messages. If a publisher receives this method, it probably needs to republish the offending messages. 58 | 59 | RabbitMQ Documentation 60 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 61 | basic.publish(short reserved-1, exchange-name exchange, shortstr routing-key, bit mandatory, bit immediate) 62 | 63 | Support: full 64 | Publish a message. 65 | 66 | This method publishes a message to a specific exchange. The message will be routed to queues as defined by the exchange configuration and distributed to any active consumers when the transaction, if any, is committed. 67 | 68 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 69 | basic.qos(long prefetch-size, short prefetch-count, bit global) ➔ qos-ok 70 | 71 | Support: partial 72 | Specify quality of service. 73 | 74 | This method requests a specific quality of service. The QoS can be specified for the current channel or for all channels on the connection. The particular properties and semantics of a qos method always depend on the content class semantics. Though the qos method could in principle apply to both peers, it is currently meaningful only for the server. 75 | 76 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 77 | basic.recover(bit requeue) 78 | 79 | Support: partial 80 | Redeliver unacknowledged messages. 81 | 82 | This method asks the server to redeliver all unacknowledged messages on a specified channel. Zero or more messages may be redelivered. This method replaces the asynchronous Recover. 83 | 84 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 85 | basic.recover-async(bit requeue) 86 | 87 | Redeliver unacknowledged messages. 88 | 89 | This method asks the server to redeliver all unacknowledged messages on a specified channel. Zero or more messages may be redelivered. This method is deprecated in favour of the synchronous Recover/Recover-Ok. 90 | 91 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 92 | basic.reject(delivery-tag delivery-tag, bit requeue) 93 | 94 | Support: partial 95 | Reject an incoming message. 96 | 97 | This method allows a client to reject a message. It can be used to interrupt and cancel large incoming messages, or return untreatable messages to their original queue. 98 | 99 | RabbitMQ blog post 100 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 101 | basic.return(reply-code reply-code, reply-text reply-text, exchange-name exchange, shortstr routing-key) 102 | 103 | Support: full 104 | Return a failed message. 105 | 106 | This method returns an undeliverable message that was published with the "immediate" flag set, or an unroutable message published with the "mandatory" flag set. The reply code and text provide information about the reason that the message was undeliverable. 107 | 108 | [amqpdoc](back to top) 109 | Channel 110 | 111 | channel.close(reply-code reply-code, reply-text reply-text, class-id class-id, method-id method-id) ➔ close-ok 112 | 113 | Support: full 114 | Request a channel close. 115 | 116 | This method indicates that the sender wants to close the channel. This may be due to internal conditions (e.g. a forced shut-down) or due to an error handling a specific method, i.e. an exception. When a close is due to an exception, the sender provides the class and method id of the method which caused the exception. 117 | 118 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 119 | channel.flow(bit active) ➔ flow-ok 120 | 121 | Support: partial 122 | Enable/disable flow from peer. 123 | 124 | This method asks the peer to pause or restart the flow of content data sent by a consumer. This is a simple flow-control mechanism that a peer can use to avoid overflowing its queues or otherwise finding itself receiving more messages than it can process. Note that this method is not intended for window control. It does not affect contents returned by Basic.Get-Ok methods. 125 | 126 | [amqpdoc](back to top) 127 | channel.open(shortstr reserved-1) ➔ open-ok 128 | 129 | Support: full 130 | Open a channel for use. 131 | 132 | This method opens a channel to the server. 133 | 134 | [amqpdoc](back to top) 135 | Confirm 136 | 137 | THIS CLASS IS A RABBITMQ-SPECIFIC EXTENSION OF AMQP 138 | 139 | confirm.select(bit nowait) ➔ select-ok 140 | 141 | . 142 | 143 | This method sets the channel to use publisher acknowledgements. The client can only use this method on a non-transactional channel. 144 | 145 | RabbitMQ Documentation 146 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 147 | Exchange 148 | 149 | exchange.bind(short reserved-1, exchange-name destination, exchange-name source, shortstr routing-key, no-wait no-wait, table arguments) ➔ bind-ok 150 | 151 | THIS METHOD IS A RABBITMQ-SPECIFIC EXTENSION OF AMQP 152 | 153 | Bind exchange to an exchange. 154 | 155 | This method binds an exchange to an exchange. 156 | 157 | RabbitMQ Documentation 158 | RabbitMQ blog post 159 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 160 | exchange.declare(short reserved-1, exchange-name exchange, shortstr type, bit passive, bit durable, bit auto-delete*, bit internal*, no-wait no-wait, table arguments) ➔ declare-ok 161 | 162 | * RABBITMQ-SPECIFIC EXTENSION OF AMQP 163 | 164 | Support: full 165 | Verify exchange exists, create if needed. 166 | 167 | This method creates an exchange if it does not already exist, and if the exchange exists, verifies that it is of the correct and expected class. 168 | 169 | RabbitMQ implements an extension to the AMQP specification that allows for unroutable messages to be delivered to an Alternate Exchange (AE). The AE feature helps to detect when clients are publishing messages that cannot be routed and can provide "or else" routing semantics where some messages are handled specifically and the remainder are processed by a generic handler. 170 | 171 | AE documention 172 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 173 | exchange.delete(short reserved-1, exchange-name exchange, bit if-unused, no-wait no-wait) ➔ delete-ok 174 | 175 | Support: partial 176 | Delete an exchange. 177 | 178 | This method deletes an exchange. When an exchange is deleted all queue bindings on the exchange are cancelled. 179 | 180 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 181 | exchange.unbind(short reserved-1, exchange-name destination, exchange-name source, shortstr routing-key, no-wait no-wait, table arguments) ➔ unbind-ok 182 | 183 | THIS METHOD IS A RABBITMQ-SPECIFIC EXTENSION OF AMQP 184 | 185 | Unbind an exchange from an exchange. 186 | 187 | This method unbinds an exchange from an exchange. 188 | 189 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 190 | Queue 191 | 192 | queue.bind(short reserved-1, queue-name queue, exchange-name exchange, shortstr routing-key, no-wait no-wait, table arguments) ➔ bind-ok 193 | 194 | Support: full 195 | Bind queue to an exchange. 196 | 197 | This method binds a queue to an exchange. Until a queue is bound it will not receive any messages. In a classic messaging model, store-and-forward queues are bound to a direct exchange and subscription queues are bound to a topic exchange. 198 | 199 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 200 | queue.declare(short reserved-1, queue-name queue, bit passive, bit durable, bit exclusive, bit auto-delete, no-wait no-wait, table arguments) ➔ declare-ok 201 | 202 | Support: full 203 | Declare queue, create if needed. 204 | 205 | This method creates or checks a queue. When creating a new queue the client can specify various properties that control the durability of the queue and its contents, and the level of sharing for the queue. 206 | 207 | RabbitMQ implements extensions to the AMQP specification that permits the creator of a queue to control various aspects of its behaviour. 208 | 209 | Per-Queue Message TTL 210 | This extension determines for how long a message published to a queue can live before it is discarded by the server. The time-to-live is configured with the x-message-ttl argument to the arguments parameter of this method. 211 | 212 | Queue Expiry 213 | Queues can be declared with an optional lease time. The lease time determines how long a queue can remain unused before it is automatically deleted by the server. The lease time is provided as an x-expires argument in the arguments parameter to this method. 214 | 215 | x-message-ttl documentation 216 | x-expires documentation 217 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 218 | queue.delete(short reserved-1, queue-name queue, bit if-unused, bit if-empty, no-wait no-wait) ➔ delete-ok 219 | 220 | Support: partial 221 | Delete a queue. 222 | 223 | This method deletes a queue. When a queue is deleted any pending messages are sent to a dead-letter queue if this is defined in the server configuration, and all consumers on the queue are cancelled. 224 | 225 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 226 | queue.purge(short reserved-1, queue-name queue, no-wait no-wait) ➔ purge-ok 227 | 228 | Support: full 229 | Purge a queue. 230 | 231 | This method removes all messages from a queue which are not awaiting acknowledgment. 232 | 233 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 234 | queue.unbind(short reserved-1, queue-name queue, exchange-name exchange, shortstr routing-key, table arguments) ➔ unbind-ok 235 | 236 | Support: partial 237 | Unbind a queue from an exchange. 238 | 239 | This method unbinds a queue from an exchange. 240 | 241 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 242 | Tx 243 | 244 | tx.commit() ➔ commit-ok 245 | 246 | Support: full 247 | Commit the current transaction. 248 | 249 | This method commits all message publications and acknowledgments performed in the current transaction. A new transaction starts immediately after a commit. 250 | 251 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 252 | tx.rollback() ➔ rollback-ok 253 | 254 | Support: full 255 | Abandon the current transaction. 256 | 257 | This method abandons all message publications and acknowledgments performed in the current transaction. A new transaction starts immediately after a rollback. Note that unacked messages will not be automatically redelivered by rollback; if that is required an explicit recover call should be issued. 258 | 259 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 260 | tx.select() ➔ select-ok 261 | 262 | Support: full 263 | Select standard transaction mode. 264 | 265 | This method sets the channel to use standard transactions. The client must use this method at least once on a channel before using the Commit or Rollback methods. 266 | 267 | [javadoc] [dotnetdoc] [amqpdoc](back to top) 268 | In This Section 269 | Server Documentation 270 | Client Documentation 271 | Plugins 272 | News 273 | Protocol 274 | Compatibility 275 | Interoperability 276 | Broker Semantics 277 | Quick Reference 278 | Full Reference 279 | Errata 280 | Differences from AMQP 0-8 to 0-9-1 281 | Our Extensions 282 | Building 283 | Previous Releases 284 | License 285 | In This Page 286 | basic 287 | basic.ack 288 | basic.cancel 289 | basic.consume 290 | basic.deliver 291 | basic.get 292 | basic.nack 293 | basic.publish 294 | basic.qos 295 | basic.recover 296 | basic.recover-async 297 | basic.reject 298 | basic.return 299 | channel 300 | channel.close 301 | channel.flow 302 | channel.open 303 | confirm 304 | confirm.select 305 | exchange 306 | exchange.bind 307 | exchange.declare 308 | exchange.delete 309 | exchange.unbind 310 | queue 311 | queue.bind 312 | queue.declare 313 | queue.delete 314 | queue.purge 315 | queue.unbind 316 | tx 317 | tx.commit 318 | tx.rollback 319 | tx.select 320 | Sitemap | Contact 321 | -------------------------------------------------------------------------------- /source/demo_file.md: -------------------------------------------------------------------------------- 1 | >原文:[AMQP 0-9-1 Model Explained](https://www.rabbitmq.com/tutorials/amqp-concepts.html) 2 | >状态:待翻译 --------------------------------------------------------------------------------