2.RabbitMQ实战 --- 理解消息通信

  AMQP消息能以一对多的广播方式进行路由,也可以选择以一对一的方式路由。在IM中,你只能一对一通信。

2.1 消费者和生产者
	生产者(producer)创建消息,然后发布(发送)到代理服务器(RabbitMQ)。什么是消息呢?消息包含2部分内容:
  有效载荷(payload)和标签(lab)。有效载荷就是你想要传输的数据。它可以是任何内容,一个json数组或者是其他。
  RabbitMQ不会在意这些。标签就更有趣了,它描述了有效载荷,并且RabbitMQ用它来决定谁将获得消息的拷贝。举例来
  说,不同于TCP协议的是,当你明确指定发送方和接收方时,AMQP只会用标签表述这条消息(一个交换器的名称和可选的主题
  标记),然后把消息交由Rabbit。Rabbit会根据标签把消息发送给感兴趣的接收方。

    消费者很容易理解。它们连接到代理服务器上,并订阅到队列上。把消息队列想象成一个具名邮箱。每当消息到达特定的邮箱时,
  RabbitMQ 会将其发送给其中一个订阅的/监听的消费者。当消费者接收到消息的时候,它只是得到消息的一部分:有效载荷。在
  消息路由过程中,消息的标签并没有随着有效载荷一同传递。RabbitMQ甚至不会告诉你是谁生产/发送了消息。就好比你拿起信封时,
  却发现所有的信封都是空白的。同理,如果需要明确知道是谁生成的AMQP消息的话,就要看生产者是否把发送方信息放入有效载荷中。

    什么是信道呢?
    你首先连接到Rabbit,才能消费或者发布消息。你的应用程序和 Rabbit 代理服务器之间创建一条TCP连接。一旦TCP连接打开,应用
  程序就可以创建一条AMQP信道。信道是建立在'真实的'TCP连接内的虚拟连接。AMQP命令都是通过信道发送出去的。每条信道都会被指派
  一个唯一ID(AMQP库会帮你记住ID的)。不论是发布消息,订阅队列或者是接收信息,这些动作都是通过信道完成的。

    为什么需要信道?不直接通过TCP连接发送AMQP命令呢?
    主要原因在于对操作系统来说建立和销毁TCP会话是非常昂贵的开销。假设应用程序从队列消费消息,并根据服务需求合理调度线程。假设你
  只进行TCP连接,那么每个线程都需要自行连接到Rabbit。也就是说,高峰期有每秒成百上千连接。这不仅造成tcp连接的巨大浪费,而且操作系统
  每秒也就只能建立这点数量连接。因此,你的性能可能很快就到瓶颈了。如果我们为所有线程只是用一条tcp连接以满足性能方面的要求,但又能确保
  每个线程的私密性,就像拥有独立连接一样的话,那不就非常完美了吗?这就是要引入信道概念的原因。线程启动后,会在现成的连接上创建一条信道,
  也就获得了链接到rabbit上的私密通信路径,而不会给操作系统的tcp栈造成额外的负担。在一条tcp连接上创建多少条信道是没有限制的。把它想象成
  一条光纤电缆就可以了。tcp就像电缆,而AMQP信道就像一条条独立光纤束。
    使用多个信道,线程可以同时共享连接。这意味着对请求的应答不会阻塞消费新的请求,而且也不会浪费tcp连接。有时,你看你会选择仅使用一条信道,
  但是有了AMQP,你可以灵活的使用多个信道来满足应用程序的需求,而不会又多个tcp连接的开销。

    消息通信,特别是AMQP,可以被当作加强版的传输层。使用信道,你能够根据应用需要,尽可能多的创建并行的传输层,而不会被tcp连接所约束。当你了解
  这些概念的时候,你就能把RabbitMQ看做软件的路由器了。

2.2 从底部开始构造:队列
	AMQP消息路由必须有3个部分:交换器,队列和绑定。生产者把消息发布到交换器上;消息最终到达队列,并被消费者接收;绑定决定了消息如何从路由器路由到
  特定的队列。
    就像我们之前讨论生产者和消费者时说的那样,队列就如同具名邮箱。消息最终到达队列并等待消费。消费者通过以下2种方式从特定的队列接收消息:
    (1)通过 AMQP 的 basic.consume 命令订阅。这样做会将信道置为接收模式,直到取消对队列的订阅为止。订阅了消息后,消费者在消费(或拒绝)最近接收的那条
  消息后,就能从队列中(可用的)自动接收下一条消息。如果消费者处理队列,并且/或者需要在消息一到达队列时就自动接收的话,你应该使用 basic.consume。
    (2)某些时候,你只想从队列获得单条消息而不是持续订阅。向队列请求单条消息是通过 AMQP 的 basic.get 命令实现的。这样做可以让消费者接收队列中的下一条
  消息。如果要获得更多的消息的话,需要再次发送 basic.get 命令。你不应该将 basic.get 放在一个循环里替代 basic.consume.因为这样做会影响rabbit性能。
  大致上将,basic.get 命令会订阅消息,获得单条信息,然后取消订阅。消费者理应选择使用 basic.consume 来实现高吞吐量。

  	如果至少有一个消费者订阅了队列的话,消息会立即发送给这些订阅的消费者。但是如果消息到达了无人订阅的队列呢?在这种情况下,消息会在队列中等待。一旦有消费者
  订阅到该队列,那么队列上的消息就会发送给消费者。更有趣的问题是,当有多个消费者订阅到同一队列上时,消息是如何发布的。
    当rabbit 队列拥有多个消费者时,队列收到的消息将以循环的方式发送给消费者。每条消息只会发送给一个订阅的消费者。假设有 seed_bin 队列,消费者 A 和 消费者B
  订阅队列 seed_bin 队列。当消息到达 seed_bin 队列时,消息投递方式如下:
  (1) 消息 Message_A 到达 seed_bin 队列。
  (2) rabbitMQ 把消息 Message_A 发送给 A
  (3) A 确认接收到消息 Message_A
  (4) rabbitMQ把消息 Message_A 从 seed_bin 中删除
  (5) 消息Message_B 打到 seed_bin 队列。
  (6) rabbitMQ 把消息 Message_B 发送给 B
  (7) B 确认接收到了消息 Message_B
  (8) RabbitMQ 把消息Message_B从 seed_bin 中删除

  	你可能注意到了 A,B 做了一些我们还未讨论的事情:他们对消息进行了确认。消费者接收到的每一条消息都必须进行确认。消费者必须通过AMQP的 basic.ack 命令显示的向
  RabbitMQ 发送了一个确认,或者在订阅到队列的时候就将 auto_ack 参数设置为 true。当设置了 auto_ack 时,一旦消费者接收消息,RabbitMQ 会自动视其确认了消息。
  但需要记住的是,消费者对消息的确认和告诉生产者消息已经被接受了这2件事毫不相干。因此,消费者通过确认命令告诉 RabbitMQ 它已经正确了接收了消息,同时 RabbitMQ
  才能安全的把消息从队列中删除。

    如果消费者收到一条消息,然后确认之前从rabbit 断开连接了(或者从队列上取消订阅),rabbitMQ 会认为这条消息没有分发,然后重新分发给下一个订阅的消费者。如果你的
  应用程序崩溃了,这样做可以确保消息会被发送给另外一个消费者进行处理。
    另外一方面,如果应用程序有bug而忘记确认消息的话,rabbit将不会给消费者发送更多消息了。这是因为在上一条消息被确认之前,rabbit会认为这个消费者并没有准备好接受
  下一条消息。你可以好好利用这一点。如果处理消息内容非常耗时,则你的应用程序可以延迟确认该消息,直到消息处理完成。这样可以防止rabbit持续不断的消息涌向你的应用
  而导致过载。

  	在收到消息后,如果你想要明确拒绝而不是确认收到消息的话,该如何呢?举例来说,假设在处理消息的时候你遇到了不可恢复的错误,但是由于硬件问题,只影响到当前的消费者
  (这就是一个很好的示例,直到消息处理完成之前,你绝不能进行确认)。只要消息尚未确认,则你有以下2个选择:
  (1)把消费者从 rabbitMQ 服务器断开连接。这会导致 rabbitMQ 自动重新把消息入队并发送给另外一个消费者。这样做的好处是所有的rabbitMQ版本都支持。缺点是,这样连接/
  断开连接的方式会额外增加 rabbitMQ 的负担(如果消费者在处理每条消息时都遇到错误的话,会导致潜在的重大负荷)。
  (2)如果你正在使用rabbitMQ2.0.0 或者更新的版本,那就使用 AMQP 的basic.reject 命令。顾名思义:basic.reject 允许消费者拒绝 RabbitMQ 发送的消息。如果把 reject
  命令的 requeue 参数设置成 true的话,rabbitMQ 会将消息重新发送给下一个订阅的消费者。如果设置成false的话,rabbitMQ立即会把消息从队列中移除,而不会把它发送给新的
  消费者。你也可以通过对消息确认的方式来简单的忽略该消息(这种忽略消息的方式的优势在于所有的版本的rabbitMQ都支持)。如果你检测到一条格式错误的消息而任何一个消费者都无法
  处理的时候,这样做就十分有用。

    注意:
    	当丢弃一条消息的时候,为什么要使用 basic.reject 命令,并将requeue参数设置成false来替代确认消息呢?在将来的 rabbitMQ版本中会支持一个特殊的 '死信(dead letter)'
      队列,用来存放那些被拒绝而不重入队列的消息。死信队列让你通过检测拒绝/未发送的消息来发现问题。如果应用程序想自动从死信队列功能中获益的话,需要使用 reject命令并将requeue
      参数设置为false。

    还有一件更重要的事情:如何创建队列。消费者和生产者都能使用 AMQP 的 queue.declare命令来创建队列。但是如果消费者在同一条信道上订阅了另外一个队列的话,就无法声明队列了。
  必须首先取消订阅,将信道设置为 '传输' 模式。当创建队列的时候,你常常需要指定队列的名称。如果不指定队列的名称的话,rabbit会分配一个随机名称并在 queue.declare命令的响应
  中返回(对于构建在 AMQP 上的 RPC 应用来说,使用临时 '匿名'队列很有用)。以下是队列设置的另外一些有用的参数:
  (1)exclusive : 如果设置为 true的话,队列将变成私有的,此时只有你的应用程序能够消费队列。当你想要限制一个队列只有一个消费者的时候很有帮助
  (2)auto-delete : 当最后一个消费者取消订阅的时候,队列就会自动移除。
    如果你需要临时队列只为一个消费者服务的话,请结合使用 auto-delete 和 exclusive。当消费者断开连接时,队列就被移除了。
    如果尝试声明一个已经存在的队列会发生什么呢?只要声明参数完全匹配现存的队列的话,rabbit就什么都不做,并成功返回,就好像这个队列已经创建成功一样(如果参数不匹配的话,队列
  声明就会失败)。如果你只是想检测队列是否存在的话,则可以设置 queue.declare 的 passive 选项为 true。

    当设计应用程序的时候,你最有可能问自己,是该由生产者还是消费者来创建所需的队列呢?看起来最自然的答案是由消费者来创建队列。毕竟,消费者才需要订阅队列,而且总不能订阅一个不存在
  的队列,是吧?你首先需要想清楚消息的生产者能否承担的起丢失消息。发送出去的消息如果路由到了不存在的队列的话,rabbit会忽略它们。因此,如果你不能承担的起消息进入'黑洞'而丢失的话,
  你的生产者和消费者就都应该尝试去创建队列。另外一方面,如果你能承担的起丢失消息,或者你实现一种方法来重新发布未处理的消息的话,你可以让你的消费者来声明队列。

    队列是 AMQP 消息通信的基础模块:
    (1)为消息提供了处所,消息在此等待消费
    (2)对负载均衡来说,对列是绝佳方案。只需要附加一堆消费者,并让RabbitMQ以循环的方式均匀的分配发来的消息。
    (3)队列是 Rabbit 中消息的最后的重点(除非消息进入了黑洞)
2.3 联合起来:交换器和绑定
	当你想要将消息投递到队列时,你通过把消息发送给交换器来完成。然后,根据确定的规则,RabbitMQ会将决定消息该投递到哪个队列。
  这些规则被称作路由键。队列通过路由键绑定到交换器。当你把消息发送到代理服务器时,消息会将拥有一个路由键---即便是空的---rabbitMQ
  也会将其和绑定使用的路由键进行匹配。如果匹配的话,那么消息将会投递到该队列。如果路由的消息不匹配任何绑定的话,消息将进入'黑洞'。
    
    你可以将这个场景和邮件进行比较:如果你想把一条消息发送给任何一个联系人,则只需要把消息发送到对方邮件地址,smtp服务器会检查消息是
  发送给谁的并会负责投递到用户的收件箱。但是,如果你的联系人想把来自你的每条消息都归档到商务文件夹下的话会如何?为了达成这个目的,他们
  需要根据消息内容设置明确的规则。举例来说,他们可能也想通过设置基于主机名的规则,将某些商业供应商归类到同一个文件夹中。通过交换器,绑定
  和队列的概念,AMQP实现上述及更多的使用场景,因此你能将队列绑定到交换器上,而不使用路由键,然后你发送给交换器的每一条没有路由键的消息会
  投递到上述的队列中去。这一点和邮件系统非常类似。

    除了可以用交换器和绑定来完成不同的使用场景之外,还有一个好处是:对于发送消息给服务器的发布者来说,它不需要关心服务器的另一端(整个消息
  处理环节中的队列和消费者)的逻辑。

    服务器会根据路由键将消息从交换器路由到队列,但它是如何处理投递到多个队列的情况呢?协议中定义的不同类型交换器发挥了作用。一共4个类型:
    1.direct
    2.fanout
    3.topic
    4.headers
    每一种类型实现了不同的理由算法。我们会讲解除了 headers交换器之外的其他3种。headers交换器允许你匹配AMQP消息的header而非路由键。除此之外,
  headers 交换器和 direct 交换器完全一致,但性能会差很多。因此它并不实用,而且几乎再也用不到了。

    1.direct : 如果路由键匹配的话,消息就被投递到对应的队列。
    	服务器必须实现direct类型交换器,包含一个空白字符串名称的默认交换器。当声明一个队列时,它会自动绑定到默认交换器,并以队列名称作为路由键。
      这意味着你可以使用如下代码发送消息到之前声明的队列去。前提是你已经获得了信道实例:
      $channel->basic_publish($msg, '', 'queue-name');
      第一个参数是你想要发送的消息内容;
      第二个参数是一个空的字符串,指定了默认交换器;
      第三个参数就是路由键了。这个路由键就是之前用来声明队列的名称。之后,你就会看到如何使用默认交换器和临时队列来实现RPC消息通信模式。

      当默认的direct交换器无法满足应用需求的时候,你可以声明你自己的交换器。只需发送exchange.declare命令并设置合适的参数就行了。

    2.fanout : 这种类型的交换器会将收到的消息广播到绑定的队列上。消息通信模式很简单:当你发送一条消息到 fanout交换器时,它会把消息投递到所有附加在此
      交换器上的队列。这允许你对单条消息作出不同方式的反应。
        举例来说,一个web应用可能需要在用户上传新的图片的时候,用户相册必须清除缓存,同时用户应该得到写积分奖励。你可以将两个队列绑定到图片上传交换器上。
      一个用于清楚缓存,另一个用于增加用户积分。从这个场景中你可以了解到,使用交换器,绑定和队列比直接向指定的队列发送消息要有优势。假设应用程序的第一个需求
      是在图片上传到网站上后,需要清楚用户相册缓存。你可以通过只是用一个队列就能轻易完成。但是当产品负责人让你实现一个新的功能,即在上传完成后给用户一些奖励,
      你该怎么办?如果你是直接昂消息发送给队列的话,就不得不修改发送方的代码,以将消息发送给新的用户积分队列。如果你使用的是 fanout 交换器的话,你唯一需要做的
      就是为新的消费者写一段代码,然后声明新的队列并将其绑定到 fanout 交换器上。发送方的代码和消费者的代码两者之间完全解耦了,这就允许你轻而易举的添加应用程序的
      功能了。

    3.topic
    	它使得来自不同源头的消息能够到达同一个队列。让我们用web应用程序日志系统为例。你拥有多个不同的日志级别,例如,error,info和 warning。与此同时,你的应用程序
      分为以下几个模块:user-profile,image-gallery,msg-inbox等。
      $channel->basic_publish($msg, 'logs-exchange', 'error.msg-inbox');
        假设你声明了一个 msg-inbox-errors 队列,你可以将其绑定到交换器上来接收消息,例如:
        $channel->queue_bind('msg-inbox-errors', 'logs-exchange', 'error.msg-inbox');
        到目前为止,这看起来和使用 direct 很像。你作为队列绑定操作和消息发布路由键指定了相同的 error.msg-inbox 字符串作为绑定规则。那样就能确保你的消息会路由到
      msg-inbox-errors 对列,这没有什么特别的。但是如果你想要一个队列监听msg-inbox 模块的所有 error级别的话,你该怎么做?你可以通过将新的队列绑定到已有的同一个
      交换器来实现,就像下面:
      $channel->queue_bind('msg-inbox-logs', 'logs-exchange', '*.msg-inbox');
      msg-inbox-logs 队列将会接收从 msg-inbox 模块发来的所有的error,warning和 info 日志信息。那么如何接收所有的日志呢?你可以在队列绑定到交换器的时候使用通配符。
    单个'.'把路由键分为了几部分,'*'匹配特定位置的任意文本。为了实现匹配所有规则,你可以使用'#'字符:
    $channel->queue_bind('all-logs', 'logs-exchange', '#');
      通过这样的绑定方式,all-logs 队列将会接收所有从web应用程序发布的日志。'*'操作符将'.'视为分隔符;与之不同的是,'#'操作符没有分块的概念,它将任意'.'字符均视为
    关键字的匹配部分。


    4.headers
2.4 多租户模式:虚拟主机和隔离
	每一个RabbitMQ服务器都能创建虚拟消息服务器,我们称之为虚拟主机vhost。每一个vhost本质上是一个mini版的RabbitMQ服务器,
  拥有自己的队列,交换器和绑定...更重要的是,它拥有自己的权限机制。这使得你能够安全的使用一个rabbitMQ服务器来服务众多应用
  程序。vhost之于rabbit就像虚拟机之于物理服务器一样:它们通过在各个实例间提供逻辑上分离,允许你为不同应用程序安全保密的运行
  数据。这很有用,它既能将同一rabbit的众多客户区分开来,又可以避免队列和交换器的命名冲突。相反,你可以只运行一个rabbit,然后
  按需启动或者关闭vhost。
    vhost是AMQP概念的基础,你必须在连接时进行指定。由于rabbitMQ包含了开箱即用的默认vhost: '/'。如果你不需要多个vhost的话,
  那就使用默认的。通过使用缺省的guest用户名和密码guest就可以访问默认vhost。为了安全起见,你应该修改它。AMQP的一个有趣的地方
  在于它并没有指定权限控制是在vhost级别还是服务器端级别实现,给开发者自己去觉得。在rabbit的例子中,权限控制是以vhost为单位的。
    当你在rabbit里创建一个用户时,用户通常会被指派给至少一个vhost,并且只能访问被指派vhost内的队列,交换器和绑定。当你设计消息
  通信架构时,记住vhost之间是绝对隔离的。你无法将vhost banana_tree的交换器绑定到vhost oak_tree中的队列去。事实上,这既保证了
  安全性,又确保了可移植性。
    记住,当你在rabbit集群上创建vhost时,整个集群都会创建该vhost。vhost不仅消除了为基础架构中的每一层运行一个rabbitMQ服务器的需要,
  同样避免了为每一层创建不同集群。
    vhost和权限控制非常独特,它们是AMQP种唯一无法通过AMQP协议创建的基元(不同于队列,交换器和绑定)。对于rabbitMQ来说,你需要通过RabbitMQ
  的 rabbitmqctl 工具来创建。通过简单的运行 
  rabbitmqctl add_vhost [vhost_name] //就可以创建一个vhost。
  rabbitmqctl delete_vhost [vhost_name]  //删除
  rabbitmqclt list_vhosts //查看

    通常情况下,你将在服务器上直接运行 rabbitmqctl 来管理自己的 rabbitMQ节点。不过你也可以指定 -n rabbit@[server_name] 来管理远程节点。
  @符号将节点标识符(rabbit@[server_name])分成2部分:左边是 Erlang 应用程序名称,在这里永远是 rabbit。右边是服务器主机名或者ip地址。
2.5我的消息去哪儿了?持久化和你的策略
	rabbitMQ里创建的队列和交换器,默认情况下它们无法辛免于服务器重启。原因在于每个队列和交换器的 durable 属性。该属性默认为false,
  它决定了rabbitMQ是否需要在崩溃或者重启之前重新创建队列(或者交换器)。将它们设置为true,这样你就不需要在服务器断点后重新创建队列和
  交换器了。
    能从AMQP服务器崩溃中恢复的消息,我们称之为持久化消息。在消息发布之前把它的'投递模式'选项设置为2来把消息标记成持久化。到目前为止,
  消息还是只是被表示为持久化,但是它还必须被发布到持久化的交换器中并到达持久化的队列中才行。如果不是这样的话,则包含持久化消息的队列会在
  rabbit崩溃重启后不复存在,从而导致消息称为孤儿。因此,如果消息想要从rabbit崩溃中恢复,那么消息必须:
  	1.把它的投递模式选项设置为2(持久)
  	2.发送到持久化的交换器
  	3.到达持久化的队列

  	rabbitmq 确保持久化消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件。当发布一条持久化消息到持久化交换器上时,
  rabbit会在消息提交到日志文件后才发送响应。记住,之后这条消息如果路由到了非持久化队列的话,它会自动从持久性日志中移除,并且无法从服务器
  重启中恢复。如果你使用持久性消息的话,则确保之前提到的持久性消息的那3点必须都做到。一旦你从持久化队列中消费了一条持久性消息的话(并且确认了
  它),rabbitmq会在持久化日志中把这条消息标记为等待垃圾收集。在你消费持久性消息前,如果rabbitmq重启的话,服务器会自动重建交换器和队列(以及绑定),
  重播持久性日志文件中的消息到合适的队列或者交换器。

    持久化付出的代价:性能。写入磁盘要比存入内存中慢不止一点点,而且会极大的减少rabbitmq服务器每秒可处理的消息总数。使用持久化机制从而导致消息吞吐降低
  至少10倍的情况并不少见。还有一点,持久性消息在rabbitmq内建集群环境下工作的并不好。虽然rabbitmq集群允许你和集群中的任何节点的任意队列进行通信,但是事实上
  那些队列均匀的分布在各个节点,而没有冗余(在集群中任何一个队列都没有备份的拷贝)。如果运行 seed_bin 队列的集群节点崩溃了,那么直到节点恢复前,这个队列也就从
  整个集群中消失了(如果队列是可持久化的)。更重要的是,当节点宕机,其他上的队列也都不可用了,而且持久化队列也无法重建。这会导致消息丢失。

    权衡取舍,我们运行两种类型的rabbit集群:非持久化消息通信的传统rabbitMQ集群和持久化消息通信的活动/热备非集群rabbit服务器。这样做确保了为持久化消息通信处理
  负载不会减慢非持久化消息的处理。这也意味着rabbit内建集群节点在崩溃时不会让持久化消息消失。

    AMPQ(事务):
    	和消息持久化相关的一个概念是AMQP事务。到目前为止,我们讨论的是将消息,队列和交换器设置为持久化。这一切都工作的很好,并且rabbit也负责保证消息的安全。但是由于
      发布操作不返回任何消息给生产者,那你怎么知道服务器是否已经持久化了持久消息到硬盘呢?服务器可能会把消息写入磁盘之前宕机了,消息因此丢失。而你却不知道。
      这就是事务发挥作用的地方。
        当继续处理其他任务之前,你必须确保代理接收到了消息(并且已经将消息路由给所有匹配的订阅队列),你需要把这些行为包装到一个事务中。在AMQP中,在把信道设置成事务模式后,
      你通过信道发送那些想要确认的消息,之后还有多个其他AMQP命令。这些命令是执行还是忽略,取决于第一条消息发送是否成功。一旦你发送完所有命令,就可以提交事务了。如果事务中的
      首次发布成功了,那么信道会在事务中完成其他AMQP命令。如果发送失败的话,其他AMQP命令将不会执行。事务填补了生产者发布消息以及RabbitMQ将它们提交到磁盘上这2者之间'最后1英里'
      的差距。
        虽然事务是正式AMQP 0-9-1规范的一部分,但是它们有缺点:几乎吸干了 rabbit 性能。使用事务不但会降低大约2~10倍的消息吞吐量,而且会使生产者应用程序产生同步。而你使用的
      消息通信就是想要避免同步。RabbitMQ团队拿出来更好的解决方案来保证消息的投递:发送方确认模式。和事务相反,你需要告诉rabbit将信道设置为 confirm模式,而且你只能通过重新
      创建信道来关闭该设置。一旦信道进入 confirm 模式,所有在信道上发布的消息都会被指派一个唯一的ID号(从1开始)。一旦消息被投递给所有匹配的队列之后,信道会发送一个发送方确认模式
      给生产者应用程序(包含消息的唯一ID)。这使得生产者知晓消息已经安全到达目的队列了。如果消息和队列是可持久化的,那么确认消息只会在队列将消息写入磁盘后才会发出。发送方确认模式的
      最大好处是它们是异步的。一旦消息发布了,生产者应用程序就可以等待确认的同时继续发送下一条。当确认消息最终收到的时候,生产者应用的回调方法就会被触发来出来该确认消息。如果
      rabbit发生了内部错误从而导致了消息的丢失,rabbit会发送一条nack(not acknowledged,未确认)消息。就像发送方确认消息那样,只不过这次说明是消息已经丢失了。同时,由于没有消息
      回滚的概念(同事务对比),因此发送方确认模式更加轻量级,同时对rabbit代理服务器的性能影响几乎可以忽略不计。
2.6 把所有内容结合起来:一条消息的一生
	生产者需要完成的任务:
		1.连接到rabbitmq
		2.获取信道
		3.声明交换器
		4.创建消息
		5.发布消息
		6.关闭信道
		7.关闭连接

	消费者需要完成的任务:
		1.连接到rabbitmq
		2.获取信道
		3.声明交换器
		4.声明队列
		5.把队列和交换器绑定起来
		6.消费消息
		7.关闭信道
		8.关闭连接
2.7 使用发送方确认模式来投递
	当信道设置成confirm模式时,发布的每一条消息都会获得唯一的ID。这可能会让你猜测basic_publish 会返回消息ID,不过事实上消息ID
  不是这样工作的。由于一条信道只能别单个线程使用,因而可以确保信道上发布的消息都是连续的。因此,rabbit做了个简单的假设:任一信道上
  发布的第一条消息获得 ID 1,并且信道上接下来的每一条消息的ID都将步进 1.也就是说,信道上发布的第二天消息将会拥有ID 2 等等。对信道
  来说,消息ID是唯一的。所以一旦信道关闭后,你讲无法追踪发布在该信道上任何未完成的发送方确认消息状态。这意味着rabbit不必告诉你刚
  发布的消息的ID;你在应用程序内部通过一个计数器自己来追踪。每次应用程序发布消息时,你需要把计数器加 1。同时,由于每条信道的消息
  ID 都是从1计数的,因此如果你同时拥有众多运行的信道的话,就需要为每条信道分别维护一个内部消息ID计数器。

1.理解消息通信

2.3 联合起来:交换器和绑定
    

2.4 多租户模式:虚拟主机和隔离
    

2.5我的消息去哪儿了?持久化和你的策略
    

2.6 把所有内容结合起来:一条消息的一生
    

2.7 使用发送方确认模式来投递
    

猜你喜欢

转载自blog.csdn.net/enlyhua/article/details/86353107
今日推荐