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")
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()
对于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()
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消费者失联策略
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()
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()
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()
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()
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()
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重要机制:
- 预热
- SLAB 内存分配机制
- LRU 对象清除机制
- 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
Redis
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的
- 持久化:以电商举例,session用memcache来做的,购物车用redis来做的,当你退出的时候会提示你购物车里的物品会在你退出后继续保存。相对来说memcache存储更单一化!
- 主从复制:redis的主从复制类似mysql的主从复制但是原理是不同的!
Python操作Redis,首先要导入redis模块
import redis
r = redis.Redis(host='IP_ADDRESS:PORT', port=6379)
- 模块:redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py
- 连接池:redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
pool = redis.ConnectionPool(host='IP_ADDRESS:PORT', port=6379)
r = redis.Redis(connection_pool=pool) - 管道: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还同时提供了持久化和复制等功能。
浙公网安备 33010602011771号