Python操作 RabbitMQ、Memcache、Redis、SQLAlchemy

RabbitMQ

MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消 息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 # Written by LiYang
 4 import Queue
 5 import threading
 6 import time
 7 __author__ = 'LiYang'
 8 
 9 message = Queue.Queue(10)
10 
11 
12 def producer(i):
13     message.put(i)
14 
15 
16 def consumer(i):
17     msg = message.get()
18     print(msg)
19 
20 
21 for i in range(12):
22     t = threading.Thread(target=producer, args=(i,))
23     t.start()
24 
25 for i in range(10):
26     t = threading.Thread(target=consumer, args=(i,))
27     t.start()
28 
29 time.sleep(3)  # 如果没有下面的消费者,以上线程只从队列中取出0-9。
30 
31 for i in range(3):
32     t = threading.Thread(target=consumer, args=(i,))
33     t.start()
34 
35 time.sleep(3)  # 队列中没有消息,线程会阻塞。
36 message.put("X")
基于Queue实现的简单消息队列
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 import Queue
 4 import threading
 5 
 6 
 7 message = Queue.Queue(10)
 8 
 9 
10 def producer(i):
11     while True:
12         message.put(i)
13 
14 
15 def consumer(i):
16     while True:
17         msg = message.get()
18 
19 
20 for i in range(12):
21     t = threading.Thread(target=producer, args=(i,))
22     t.start()
23 
24 for i in range(10):
25     t = threading.Thread(target=consumer, args=(i,))
26     t.start()
基于Queue实现的生产消费模型

对于RabbitMQ来说,生产和消费不再针对内存里的一个Queue对象,而是某台服务器上的RabbitMQ Server实现的消息队列。

 1 #!/usr/bin/env python
 2 import pika
 3  
 4 # ### 生产者 ####
 5  
 6 connection = pika.BlockingConnection(pika.ConnectionParameters(
 7         host='localhost'))
 8 channel = connection.channel()
 9  
10 channel.queue_declare(queue='hello')
11  
12 channel.basic_publish(exchange='',
13                       routing_key='hello',
14                       body='Hello World!')
15 print(" [x] Sent 'Hello World!'")
16 connection.close()
基于RabbitMQ实现的生产者
 1 #!/usr/bin/env python
 2 import pika
 3  
 4 # ###消费者 ####
 5  
 6 connection = pika.BlockingConnection(pika.ConnectionParameters(
 7         host='localhost'))
 8 channel = connection.channel()  
 9 
10 # 此处和生产者端的queue不冲突,仅防止生产者端的channel未启动先。 
11 channel.queue_declare(queue='hello')
12 
13 # 从消息队列中取得任务后,所执行的函数。 
14 def callback(ch, method, properties, body):
15     print(" [x] Received %r" % body)
16 
17 # 消费队列中的任务 
18 channel.basic_consume(callback,
19                       queue='hello',
20                       no_ack=True)
21  
22 print(' [*] Waiting for messages. To exit press CTRL+C')
23 channel.start_consuming()
基于RabbitMQ实现的消费者

RabbitMQ消费者失联策略

1、如果在消费者中的任务处理中加入no-ack = False,当消费者遇到情况(its channel is closed, connection is closed, or TCP connection is lost)挂掉了,那么,RabbitMQ会重新将该任务添加到队列中。

 1 import pika
 2 
 3 connection = pika.BlockingConnection(pika.ConnectionParameters(
 4         host='RabbitMQ-server'))
 5 channel = connection.channel()
 6 
 7 channel.queue_declare(queue='hello')
 8 
 9 def callback(ch, method, properties, body):
10     print(" [x] Received %r" % body)
11     import time
12     time.sleep(10)
13     print 'ok'
14     ch.basic_ack(delivery_tag = method.delivery_tag)
15 
16 channel.basic_consume(callback,
17                       queue='hello',
18                       no_ack=False)
19 
20 print(' [*] Waiting for messages. To exit press CTRL+C')
21 channel.start_consuming()
基于RabbitMQ实现的消费者---第二版

2、消息持久化,durable,防止RabbitMQ服务器宕机。

 1 #!/usr/bin/env python
 2 import pika
 3 
 4 connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
 5 channel = connection.channel()
 6 
 7 # make message persistent, durable 持久化
 8 channel.queue_declare(queue='hello', durable=True)
 9 
10 channel.basic_publish(exchange='',
11                       routing_key='hello',
12                       body='Hello World!',
13                       properties=pika.BasicProperties(
14                           delivery_mode=2, # make message persistent
15                       ))
16 print(" [x] Sent 'Hello World!'")
17 connection.close()
RabbitMQ消息持久化---durable---生产者
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 import pika
 4 
 5 connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
 6 channel = connection.channel()
 7 
 8 # make message persistent
 9 channel.queue_declare(queue='hello', durable=True)
10 
11 
12 def callback(ch, method, properties, body):
13     print(" [x] Received %r" % body)
14     import time
15     time.sleep(10)
16     print 'ok'
17     ch.basic_ack(delivery_tag = method.delivery_tag)
18 
19 channel.basic_consume(callback,
20                       queue='hello',
21                       no_ack=False)
22 
23 print(' [*] Waiting for messages. To exit press CTRL+C')
24 channel.start_consuming()
RabbitMQ消息持久化---durable---消费者

 3、获取消息的顺序

默认消息队列里的数据是按照顺序被消费者拿走,channel.basic_qos(prefetch_count=1) 表示谁来谁取,不再按照奇偶数排列。

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 import pika
 4 
 5 connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
 6 channel = connection.channel()
 7 
 8 # make message persistent
 9 channel.queue_declare(queue='hello')
10 
11 
12 def callback(ch, method, properties, body):
13     print(" [x] Received %r" % body)
14     import time
15     time.sleep(10)
16     print 'ok'
17     ch.basic_ack(delivery_tag = method.delivery_tag)
18 
19 channel.basic_qos(prefetch_count=1)
20 
21 channel.basic_consume(callback,
22                       queue='hello',
23                       no_ack=False)
24 
25 print(' [*] Waiting for messages. To exit press CTRL+C')
26 channel.start_consuming()
消费者取消息的顺序---谁来谁取

4、消息的发布和订阅

发布订阅和简单的消息队列区别在于,发布订阅会将消息发送给所有的订阅者,而消息队列中的数据被消费一次便消失。所以,RabbitMQ实现发布和订阅时,会为每一个订阅者创建一个队列,而发布者发布消息时,会将消息放置在所有相关队列中。

 1 #!/usr/bin/env python
 2 import pika
 3 import sys
 4 
 5 connection = pika.BlockingConnection(pika.ConnectionParameters(
 6         host='localhost'))
 7 channel = connection.channel()
 8 
 9 # 生成交换队列
10 channel.exchange_declare(exchange='logs',
11                          type='fanout')
12 
13 message = ' '.join(sys.argv[1:]) or "info: Hello World!"
14 # 将消息发送给名为‘logs’的exchange
15 channel.basic_publish(exchange='logs',
16                       routing_key='',
17                       body=message)
18 print(" [x] Sent %r" % message)
19 connection.close()
基于RabbitMQ实现的发布者
 1 #!/usr/bin/env python
 2 import pika
 3 
 4 connection = pika.BlockingConnection(pika.ConnectionParameters(
 5         host='localhost'))
 6 channel = connection.channel()
 7 
 8 channel.exchange_declare(exchange='logs',
 9                          type='fanout')
10 
11 #在不同的producer和consumer间共享queue时指明queue的name是重要的  
12 #但某些时候,比如日志系统,需要接收所有的log message而非一个子集  
13 #而且仅对当前的message 流感兴趣,对于过时的message不感兴趣,那么  
14 #可以申请一个临时队列这样,每次连接到RabbitMQ时会以一个随机的名字生成  
15 #一个新的空的queue,将exclusive置为True,这样在consumer从RabbitMQ断开后会删除该queue
16 result = channel.queue_declare(exclusive=True)
17 queue_name = result.method.queue
18 
19 # 绑定server端名为‘logs’的exchange
20 channel.queue_bind(exchange='logs',
21                    queue=queue_name)
22 
23 print(' [*] Waiting for logs. To exit press CTRL+C')
24 
25 def callback(ch, method, properties, body):
26     print(" [x] %r" % body)
27 
28 channel.basic_consume(callback,
29                       queue=queue_name,
30                       no_ack=True)
31 
32 channel.start_consuming()
基于RabbitMQ实现的订阅者

5、关键字---消息发送

  RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列。

6、模糊匹配---消息发送

  在topic类型下,可以让队列绑定几个模糊的关键字,之后发送者将数据发送到exchange,exchange将传入”路由值“和 ”关键字“进行匹配,匹配成功,则将数据发送到指定队列。exchange type = topic

# 表示可以匹配 0 个 或 多个 单词
*  表示只能匹配 一个 单词

Memcached

  Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。Memcached通过指定监听的IP、端口,以及分配内存大小来处理客户端请求,同时采用epoll非阻塞网络I/O模型处理网络连接,同时libevent事件处理机制来协同多个server来协同工作。

事件处理

  libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。关于事件处理这里就不再详细介绍,可以参考Dan Kegel的The C10K Problem。

存储方式

  为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。

通信分布式

  memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢?这完全取决于客户端的实现!

  Memecached重要机制:

  1.     预热
  2.     SLAB 内存分配机制
  3.     LRU 对象清除机制
  4.     Hash 对象检索机制

  Python Memcached方法,操控Memcached首先要导入memcache模块,

    import memcache
    mc = memcache.Client(['IP_ADDRESS:PORT'], debug=True)

    存储命令:    add  添加一条键值对,如果已经存在的 key,重复执行add操作异常

          replace  修改某个key的值,如果key不存在,则异常

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 import memcache
 4  
 5 mc = memcache.Client(['10.211.55.4:12000'], debug=True)
 6 mc.add('k1', 'v1')
 7 # mc.add('k1', 'v2') # 报错,对已经存在的key重复添加,失败!!!
 8 
 9 
10 # 如果memcache中存在kkkk,则替换成功,否则一场
11 mc.replace('kkkk','999')
示例

          set            设置一个键值对,如果key不存在,则创建,如果key存在,则修改
          set_multi   设置多个键值对,如果key不存在,则创建,如果key存在,则修改

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 import memcache
4  
5 mc = memcache.Client(['10.211.55.4:12000'], debug=True)
6  
7 mc.set('key0', 'wupeiqi')
8  
9 mc.set_multi({'key1': 'val1', 'key2': 'val2'})
示例

              append    修改指定key的值,在该值 后面 追加内容
              prepend   修改指定key的值,在该值 前面 插入内容

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 import memcache
 4  
 5 mc = memcache.Client(['10.211.55.4:12000'], debug=True)
 6 # k1 = "v1"
 7  
 8 mc.append('k1', 'after')
 9 # k1 = "v1after"
10  
11 mc.prepend('k1', 'before')
12 # k1 = "beforev1after"
示例

 

    获取命令:  get             获取一个键值对

          get_multi   获取多一个键值对

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 import memcache
4  
5 mc = memcache.Client(['10.211.55.4:12000'], debug=True)
6  
7 val = mc.get('key0')
8 item_dict = mc.get_multi(["key1", "key2", "key3"])
示例

    

    其他命令: delete/delete_multi/stats/

          delete             在Memcached中删除指定的一个键值对
          delete_multi    在Memcached中删除指定的多个键值对

1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 import memcache
4  
5 mc = memcache.Client(['10.211.55.4:12000'], debug=True)
6  
7 mc.delete('key0')
8 mc.delete_multi(['key1', 'key2'])
示例

           incr  自增,将Memcached中的某一个值增加 N ( N默认为1 )

           decr 自减,将Memcached中的某一个值减少 N ( N默认为1 )

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 import memcache
 4  
 5 mc = memcache.Client(['10.211.55.4:12000'], debug=True)
 6 mc.set('k1', '777')
 7  
 8 mc.incr('k1')
 9 # k1 = 778
10  
11 mc.incr('k1', 10)
12 # k1 = 788
13  
14 mc.decr('k1')
15 # k1 = 787
16  
17 mc.decr('k1', 10)
18 # k1 = 777
示例

 

 Memcached集群

Redis

  redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的

  1.   持久化:以电商举例,session用memcache来做的,购物车用redis来做的,当你退出的时候会提示你购物车里的物品会在你退出后继续保存。相对来说memcache存储更单一化!
  2.   主从复制:redis的主从复制类似mysql的主从复制但是原理是不同的!

   Python操作Redis,首先要导入redis模块

    import redis

    r = redis.Redis(host='IP_ADDRESS:PORT', port=6379)

  1. 模块:redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py
  2. 连接池:redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。

    pool = redis.ConnectionPool(host='IP_ADDRESS:PORT', port=6379)
     
    r = redis.Redis(connection_pool=pool)

  3. 管道:redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。

  

 

  利用Python开发监控主机的软件就是利用redis的发布订阅功能,随后粘,有意者联系我。

Redis和Memcached比较

 

1、网络IO模型
  Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接描述字pipe 传递给worker线程,进行读写IO, 网络层使用libevent封装的事件库,多线程模型可以发挥多核作用,但是引入了cache coherency和锁的问题,比如,Memcached最常用的stats 命令,实际Memcached所有操作都要对这个全局变量加锁,进行计数等工作,带来了性能损耗。

  Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select,对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大,但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞住的。

 

2、内存管理方面

  Memcached使用预分配的内存池的方式,使用slab和大小不同的chunk来管理内存,Item根据大小选择合适的chunk存储,内存池的方式可以省去申请/释放内存的开销,并且能减小内存碎片产生,但这种方式也会带来一定程度上的空间浪费,并且在内存仍然有很大空间时,新的数据也可能会被剔除,原因可以参考Timyang的文章:http://timyang.net/data/Memcached-lru-evictions/

  Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片,Redis跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致swap也不会剔除任何非临时数据(但会尝试剔除部分临时数据),这点上Redis更适合作为存储而不是cache。

3、数据一致性

  Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题。

  Redis没有提供cas 命令,并不能保证这点,不过Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断。

 4、存储方式及其它方面

  Memcached基本只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能

  Redis除key/value之外,还支持list,set,sorted set,hash等众多数据结构,提供了KEYS进行枚举操作,但不能在线上使用,如果需要枚举线上数据,Redis提供了工具可以直接扫描其dump文件,枚举出所有数据,Redis还同时提供了持久化和复制等功能。

 

参考文档

 

posted on 2016-01-22 16:41  曦字号  阅读(227)  评论(0)    收藏  举报

导航