Python网络编程 8 缓存与消息队列

前面已经介绍了套接字API以及在Python中使用的基础IP网络操作来构建通信信道的方式。本章研究服务负载较重时常用的两项基本技术:缓存与消息队列。这两项技术有如下一些共同特点:

  • 都是非常强大的工具,因而广为流行。使用Memcached或一个消息队列,不是为了实现一个有趣的协议来与其他工具进行交互,二是为了编写优雅的服务来解决特定的问题。
  • 这两项技术解决的问题通常是机构内部特有的问题。我们通常无法仅从外界就得知一个特定的网站或网络服务使用了哪种缓存、哪种消息队列以及哪种负载分配工具。
  • 尽管HTTP和SMTP这样的工具都是针对一个特定的负载设计的(HTTP针对超文本文档,SMTP针对电子邮件消息),但是缓存和消息队列是无需了解它们所要传输的数据的。
  • 我们可以将缓存和消息队列视为提供了两个已经造好的轮子,以避免重复的劳动。

Memcached意为“内存缓存守护进程“(memory cache daemon).Memcached将安装它的服务器上的空闲RAM与一个很大的近期最少使用(LRU)的缓存结合使用。我们可以从Memcached的实现中学习到一个重要的现代网络概念--分区(Sharding).使用Memcached的实际步骤是相当简单的。
  • 在每台拥有空闲内存的服务器上都运行一个Memcached守护进程。
  • 将所有Memcached守护进程的IP地址与端口号列出,并将该列表发送给所有将要使用Memcached的客户端。
  • 客户端程序现在可以访问一个组织级的速度极快的键值缓存,它就像是所有服务器之间共享的一个巨大的Python字典。该缓存是基于LRU的。如果有些项长时间没有被访问的话,就会将这些项丢弃,为新访问的项挪出空间,并记录被频繁访问的项。
Memcached Points :
  • Memcached的接口和Python的字典极其类似。将一个字符串作为值传入set()时,该字符串会以utf-8编码直接被写入Memcached,稍后在通过get()获取该字符串时会进行解码。除了简单字符串之外,写入任何其他Python对象都会自动触发memcache模块的pickle操作,然后将二进制的pickle存储在Memcached中。要牢记这一不同点,因为有时我们编写的Python应用程序会与用其他语言写的客户端共享Memcached缓存,此时,只有一字符串形式存储的值才可以被使用其他语言编写的客户端直接使用。
  • 服务器是可以丢弃存储在Memcached中的数据的。Memcached的目的是将重复计算花销较高的结果记录下来,以此来加速操作。它不是用来作为数据的唯一存储区的!
  • 当Memcached客户端得到了包含多个Memcached实例的列表时,会根据每个键的字符串的散列值对Memcached数据库进行分区(shard),由计算出的散列值决定用Memcached集群中的哪台服务器来存储特定的记录。

消息队列协议允许我们发送可靠的数据块。协议将这样的数据块称为消息(message),而不是数据报(datagram)。这是因为,数据报这一概念是用来特指不可靠服务的,传输过程中数据可能会丢失、重复或是被网络重新排列。一般来说,消息队列保证消息的可靠自动传输:一条消息约么被完好无损地传输至目的地,要么完全不传输。 消息队列协议会负责封帧,使用消息队列的客户端从来都不需要在接收到完整的消息之前一直在循环中不断调用recv()这样的函数。
消息队列还有一个创新之处,即与TCP这样基于IP传输机制提供的点对点连接不同,使用消息队列的客户端之间可以设置各种各样的拓扑结构。消息队列有很多可能的应用场景。
  • 当使用电子邮箱地址在网站注册账号时,通常会立刻返回一个页面,请于邮件收件箱内查收确认邮件。在这一过程中,用户无需等待,而网站通过我们的电子邮箱服务提供商传输邮件则可能需要好几分钟的时间。网站的通常做法是将电子邮箱地址放在一个消息队列里,当后台服务器准备好建立一个用于发送的SMTP连接时,就会从消息队列中获取邮箱地址。如果发送暂时失败,那么电子邮箱地址会被直接放回到队列中,在经历更长的时间间隔后重试。
消息队列可以作为自定义远程过程调用(RPC,Remote Procedure Call)服务的基础。远程过程调用服务允许繁忙的前端服务器将一些困难的工作交给后端服务器来负责。前端服务器可以将请求置于消息队列中,几十甚至几百个后端服务器会对该消息队列进行监听。后端服务器在处理完消息队列中的请求后会将响应返回给正在等待的前端服务器。经常需要将一些大容量的事件数据作为小型的有效消息流集中存储在消息队列中并进行分析。在一些网站中,消息队列已经彻底代替了存储到本地硬盘中的日志系统以及syslog这样更古老的日志传输机制。
消息队列应用程序设计有一个重要特点,那就是它具有混合安排并匹配所有客户端与服务器或发布者与订阅者进程的能力。需要注意的是,它们都需要连接到同一个消息队列系统。
消息队列的意义:给程序编写带来了一些革命性的进步。典型的传统应用程序在单个应用程序中包含了所有功能。它由一层一层的API组成。一个控制线程可能会负责对所有API的调用,比如先从套接字读取HTTP数据,然后进行认证,请求解析,调用API进行特定的图像处理,最后将结果写入磁盘中,该控制线程使用所有的API都必须存在于同一台机器上,并且被载入到同一个Python运行时实例内。然而,一旦我们能够使用消息队列,那么久可能会产生一个疑惑:为什么像图像处理这样计算密集型、专业且对于网络不可见的工作要与前端HTTP服务共享CPU和磁盘驱动器?因此,我们可以不使用安装了大量不同库的强大机器来构建服务,二是转而使用一些专门用于单一目的的机器,将这些机器集合到集群中,共同提供某个服务。这样一来,只要负责运维的同事理解消息传递的拓扑结构,并且保证在进行服务器分离时没有任何信息丢失,他们在卸载、安装以及重装图像处理服务器时就完全不会影响位于消息队列前端的HTTP服务负载均衡池。
通常来说,所有消息队列都支持多种拓扑结构。
  • 管道(pipeline)拓扑结构可能是与我们脑海中对于队列的直观映像最相似的一种模式:生产者创建消息,然后将消息提交至队列中,消费者从队列中接收消息。例如,一个照片分享网站的前端网络服务器可能会将用户上传的图片存储在一个专门用于接收文件的内部队列中。包含许多缩略图生成工具的机房会从队列中读取图片。每个图像处理服务器每次从队列中接收一条消息(消息中包含需要生成缩略图的图片),然后为其生成缩略图。站点较为繁忙时,队列在运行过程中可能会越来越长;站点较为空闲时,队列就会变短或是再次清空。不过,无论站点是否繁忙,前端网络服务器都可以直接向等待的客户端返回一个响应,告诉用户,上传已经成功,并且很快就能在他们的照片流中看到。
  • 发布者-订阅者(publisher-subscriber)或扇出(fanout)结构看上去和管道结构差不多,不过二者有一个重要的区别。虽然管道结构的消息队列能够保证队列中的没个消息都会被发送给一个消费者(这是由于,把同一张图片发送给两台图像服务器毕竟是很浪费的),但是消息订阅者通常想要接收队列中的所有消息。因此另一种方法是,由订阅者设置一个过滤器,通过某种特定的格式限定有兴趣的消息范围。该类型的队列可以用于需要向外部世界推送事件的外部服务。服务器机房同样可以使用队列系统来对哪些系统启动了,哪些系统因为维护而关闭进行通知。除此之外,甚至还可以使用这种队列系统在其他消息队列创建和销毁的时候发布他们的地址。
  • 最后一个是请求-响应(request-reply)模式,这也是最为复杂的模式。复杂的原因在于消息需要进行往返。在前面两种模式中,消息生产者的工作是非常少的。生产者连接到队列,然后发送消息,仅此而已。但是,发起请求的消息队列客户端需要保持连接,并等待接收响应。为了支持这一点,队列必须提供某种寻址机制,从成百上千个已经连接并且仍然处于等待的客户端中找到正确的客户端,将响应发送到该客户端。不过也正是由于这一复杂性使得请求-响应模式几乎成为了最强大的模式。它允许我们将几十或是几百个客户端请求均匀分布到大量服务器中,除了设置消息队列外,不需要做其它任何工作。一个优秀的消息队列允许服务器在不丢失消息的前提下绑定到消息队列或解除绑定,因此这种拓扑结构的消息队列同样允许服务器在需要维护而关闭时的行为对客户端机器保持不可见。
请求-响应模式的队列是将能够在某台机器上大量运行的多个轻量级线程(比如网络前端服务器的许多线程)与数据库客户端或文件服务器连接起来的一种很好的方式。数据库客户端或文件服务器有事需要被调用,代替前端服务器进行一个高负荷的运算。请求-响应模式很自然地适用于RPC机制,而且还提供了普通RPC系统没有提供的额外优点:许多消费者或生产者都可以使用扇入或扇出的工作模式绑定到同一个队列,而模式的不同对于客户端来说是不可见的。

在Python中使用消息队列

最流行的消息队列被实现为独立的服务器。构建应用程序时我们为了完成各种任务选用的所有组件(比如生产者、消费者、过滤器以及RPC服务)都可以绑定到消息队列,并且互相不知道彼此的地址,甚至不知道彼此的身份。AMQP协议是最常见的跨语言消息队列协议实现之一,我们可以安装许多支持AMQP协议的开源服务器,比如RabbitMQ、Apache Qpid服务器以及许多其他项目。
许多程序员从来都不去学习消息协议。相反,他们会去依赖一些第三方库,这些第三方库将消息队列的重要功能封装起来,并提供了易于使用的API。例如许多Django网络框架的Python程序员会使用非常流行的Celery分布式任务队列,而并不去学习AMQP协议。这些库同样可以支持其他后端服务,使得其不依赖于特定的协议。在Celery中,我们可以使用简单的Redis键值存储作为我们的”消息队列",而不需要使用专门的消息机制。

剩余内容在P143~146

你可能感兴趣的:(Python网络编程 8 缓存与消息队列)