python16_day11【MQ、Redis、Memcache】
一、RabbitMQ
是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消 息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。
1.RabbitMQ install
1 安装配置epel源 2 $ rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm 3 4 安装erlang 5 $ yum -y install erlang 6 7 安装RabbitMQ 8 $ yum -y install rabbitmq-server
注意:service rabbitmq-server start/stop
2. Python API install
1 pip install pika 2 or 3 easy_install pika 4 or 5 源码 6 https://pypi.python.org/pypi/pika
3.基于QUEUE实现生产消费模型
1 import Queue 2 import threading 3 4 5 message = Queue.Queue(10) 6 7 8 def producer(i): 9 while True: 10 message.put(i) 11 12 13 def consumer(i): 14 while True: 15 msg = message.get() 16 17 18 for i in range(12): 19 t = threading.Thread(target=producer, args=(i,)) 20 t.start() 21 22 for i in range(10): 23 t = threading.Thread(target=consumer, args=(i,)) 24 t.start()
4.基于RabbitMQ
对于RabbitMQ来说,生产和消费不再针对内存里的一个Queue对象,而是某台服务器上的RabbitMQ Server实现的消息队列。
1 import pika 2 3 # ######################### 生产者 ######################### 4 5 connection = pika.BlockingConnection(pika.ConnectionParameters( 6 host='localhost')) 7 channel = connection.channel() 8 9 channel.queue_declare(queue='hello') 10 11 channel.basic_publish(exchange='', 12 routing_key='hello', 13 body='Hello World!') 14 print(" [x] Sent 'Hello World!'") 15 connection.close() 16 17 18 19 # ########################## 消费者 ########################## 20 import pika 21 connection = pika.BlockingConnection(pika.ConnectionParameters( 22 host='localhost')) 23 channel = connection.channel() 24 25 channel.queue_declare(queue='hello') 26 27 def callback(ch, method, properties, body): 28 print(" [x] Received %r" % body) 29 30 channel.basic_consume(callback, 31 queue='hello', 32 no_ack=True) 33 34 print(' [*] Waiting for messages. To exit press CTRL+C') 35 channel.start_consuming()
5.消费者ack
1 import pika 2 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672)) 3 channel = connection.channel() 4 channel.queue_declare(queue='hello') 5 6 7 def callback(ch, method, properties, body): 8 print(" [x] Received %r" % body) 9 10 channel.basic_consume(callback, queue='hello', no_ack=False) 11 # no_ack: acknowledgment 消息不丢失,MQ判读出现异常,没有消费,没有ack,则把消息放回队列. 12 channel.start_consuming()
6.durable消息持久化
1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters( 4 host='127.0.0.1', port=5672)) 5 channel = connection.channel() 6 7 channel.queue_declare(queue='hello1', durable=True) # 创建通道, 持久化修改1:durable=True 8 9 channel.basic_publish(exchange='', 10 routing_key='hello', 11 body='Hello World!', 12 properties=pika.BasicProperties(delivery_mode=2) # 持久化修改2 13 ) 14 connection.close()
1 import pika 2 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672)) 3 channel = connection.channel() 4 5 6 def callback(ch, method, properties, body): 7 print(" [x] Received %r" % body) 8 import time 9 time.sleep(10) 10 print('ok') 11 ch.basic_ack(delivery_tag=method.delivery_tag) # 持久化:修改2 12 13 channel.basic_consume(callback, 14 queue='hello', 15 no_ack=False) # 持久化:修改1 16 17 channel.start_consuming()
7.消息获取顺序
1 import pika 2 connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', port=5672)) 3 channel = connection.channel() 4 5 6 def callback(ch, method, properties, body): 7 print(" [x] Received %r" % body) 8 import time 9 time.sleep(10) 10 print('ok') 11 ch.basic_ack(delivery_tag=method.delivery_tag) 12 channel.basic_qos(prefetch_count=1) # 默认消息队列里的数据是按照顺序被消费者拿走, 13 # 例如:消费者1 去队列中获取 奇数 序列的任务,消费者1去队列中获取 偶数 序列的任务。 14 # 表示谁来谁取,不再按照奇偶数排列 15 channel.basic_consume(callback, 16 queue='hello', 17 no_ack=False) 18 19 channel.start_consuming()
8.发布订阅
exchange type = fanout
1 import pika 2 import sys 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 message = ' '.join(sys.argv[1:]) or "info: Hello World!" 12 channel.basic_publish(exchange='logs', 13 routing_key='', 14 body=message) 15 print(" [x] Sent %r" % message) 16 connection.close()
1 import pika 2 3 connection = pika.BlockingConnection(pika.ConnectionParameters( 4 host='localhost')) 5 channel = connection.channel() 6 7 channel.exchange_declare(exchange='logs', 8 type='fanout') 9 10 result = channel.queue_declare(exclusive=True) 11 queue_name = result.method.queue 12 13 channel.queue_bind(exchange='logs', 14 queue=queue_name) 15 16 print(' [*] Waiting for logs. To exit press CTRL+C') 17 18 19 def callback(ch, method, properties, body): 20 print(" [x] %r" % body) 21 22 channel.basic_consume(callback, 23 queue=queue_name, 24 no_ack=True) 25 26 channel.start_consuming()
9.发布订阅(关键字)
exchange type = direct
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters( 5 host='localhost')) 6 channel = connection.channel() 7 8 channel.exchange_declare(exchange='direct_logs', 9 type='direct') 10 11 severity = sys.argv[1] if len(sys.argv) > 1 else 'info' 12 message = ' '.join(sys.argv[2:]) or 'Hello World!' 13 channel.basic_publish(exchange='direct_logs', 14 routing_key=severity, 15 body=message) 16 print(" [x] Sent %r:%r" % (severity, message)) 17 connection.close()
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters( 5 host='localhost')) 6 channel = connection.channel() 7 8 channel.exchange_declare(exchange='direct_logs', 9 type='direct') 10 11 result = channel.queue_declare(exclusive=True) 12 queue_name = result.method.queue 13 14 severities = sys.argv[1:] 15 if not severities: 16 sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0]) 17 sys.exit(1) 18 19 for severity in severities: 20 channel.queue_bind(exchange='direct_logs', 21 queue=queue_name, 22 routing_key=severity) 23 24 print(' [*] Waiting for logs. To exit press CTRL+C') 25 26 def callback(ch, method, properties, body): 27 print(" [x] %r:%r" % (method.routing_key, body)) 28 29 channel.basic_consume(callback, 30 queue=queue_name, 31 no_ack=True) 32 33 channel.start_consuming()
10.发布订阅(模糊匹配)
exchange type = topic
在topic类型下,可以让队列绑定几个模糊的关键字,之后发送者将数据发送到exchange,exchange将传入”路由值“和 ”关键字“进行匹配,匹配成功,则将数据发送到指定队列。
- # 表示可以匹配 0 个 或 多个 单词
- * 表示只能匹配 一个 单词
发送者路由值 队列中 old.boy.python old.* -- 不匹配 old.boy.python old.# -- 匹配
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters( 5 host='localhost')) 6 channel = connection.channel() 7 8 channel.exchange_declare(exchange='topic_logs', 9 type='topic') 10 11 routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info' 12 message = ' '.join(sys.argv[2:]) or 'Hello World!' 13 channel.basic_publish(exchange='topic_logs', 14 routing_key=routing_key, 15 body=message) 16 print(" [x] Sent %r:%r" % (routing_key, message)) 17 connection.close()
1 import pika 2 import sys 3 4 connection = pika.BlockingConnection(pika.ConnectionParameters( 5 host='localhost')) 6 channel = connection.channel() 7 8 channel.exchange_declare(exchange='topic_logs', 9 type='topic') 10 11 result = channel.queue_declare(exclusive=True) 12 queue_name = result.method.queue 13 14 binding_keys = sys.argv[1:] 15 if not binding_keys: 16 sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0]) 17 sys.exit(1) 18 19 for binding_key in binding_keys: 20 channel.queue_bind(exchange='topic_logs', 21 queue=queue_name, 22 routing_key=binding_key) 23 24 print(' [*] Waiting for logs. To exit press CTRL+C') 25 26 def callback(ch, method, properties, body): 27 print(" [x] %r:%r" % (method.routing_key, body)) 28 29 channel.basic_consume(callback, 30 queue=queue_name, 31 no_ack=True) 32 33 channel.start_consuming()
二、Memcached
1.安装API
pip3 install python-memcached
2.基本使用
import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) mc.set("foo", "bar") ret = mc.get('foo') print ret
3.支持集群
- 根据算法将 k1 转换成一个数字
- 将数字和主机列表长度求余数,得到一个值 N( 0 <= N < 列表长度 )
- 在主机列表中根据 第2步得到的值为索引获取主机,例如:host_list[N]
- 连接 将第3步中获取的主机,将 k1 = "v1" 放置在该服务器的内存中
mc = memcache.Client([('1.1.1.1:12000', 1), ('1.1.1.2:12000', 2), ('1.1.1.3:12000', 1)], debug=True) mc.set('k1', 'v1')
4.add命令
添加一条键值对,如果已经存在的 key,重复执行add操作异常
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) mc.add('k1', 'v1') # mc.add('k1', 'v2') # 报错,对已经存在的key重复添加,失败!!!
5.replace命令
replace 修改某个key的值,如果key不存在,则异常
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) # 如果memcache中存在kkkk,则替换成功,否则一场 mc.replace('kkkk','999')
6.set 和 set_multi
set 设置一个键值对,如果key不存在,则创建,如果key存在,则修改!
set_multi 设置多个键值对,如果key不存在,则创建,如果key存在,则修改!
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) mc.set('key0', 'wupeiqi') mc.set_multi({'key1': 'val1', 'key2': 'val2'})
7.delete 和 delete_multi
delete 在Memcached中删除指定的一个键值对
delete_multi 在Memcached中删除指定的多个键值对
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) mc.delete('key0') mc.delete_multi(['key1', 'key2'])
8.get 和 get_multi
get 获取一个键值对
get_multi 获取多一个键值对
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) val = mc.get('key0') item_dict = mc.get_multi(["key1", "key2", "key3"])
9.append 和 prepend
append 修改指定key的值,在该值 后面 追加内容
prepend 修改指定key的值,在该值 前面 插入内容
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) # k1 = "v1" mc.append('k1', 'after') # k1 = "v1after" mc.prepend('k1', 'before') # k1 = "beforev1after"
10.decr 和 incr
incr 自增,将Memcached中的某一个值增加 N ( N默认为1 )
decr 自减,将Memcached中的某一个值减少 N ( N默认为1 )
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True) mc.set('k1', '777') mc.incr('k1') # k1 = 778 mc.incr('k1', 10) # k1 = 788 mc.decr('k1') # k1 = 787 mc.decr('k1', 10) # k1 = 777
11.gets 和 cas
如商城商品剩余个数,假设改值保存在memcache中,product_count = 900
A用户刷新页面从memcache中读取到product_count = 900
B用户刷新页面从memcache中读取到product_count = 900
如果A、B用户均购买商品
A用户修改商品剩余个数 product_count=899
B用户修改商品剩余个数 product_count=899
如此一来缓存内的数据便不在正确,两个用户购买商品后,商品剩余还是 899
如果使用python的set和get来操作以上过程,那么程序就会如上述所示情况!
如果想要避免此情况的发生,只要使用 gets 和 cas 即可,如:
#!/usr/bin/env python # -*- coding:utf-8 -*- import memcache mc = memcache.Client(['10.211.55.4:12000'], debug=True, cache_cas=True) v = mc.gets('product_count') # ... # 如果有人在gets之后和cas之前修改了product_count,那么,下面的设置将会执行失败,剖出异常,从而避免非正常数据的产生 mc.cas('product_count', "899")
Ps:本质上每次执行gets时,会从memcache中获取一个自增的数字,通过cas去修改gets的值时,会携带之前获取的自增值和memcache中的自增值进行比较,如果相等,则可以提交,如果不想等,那表示在gets和cas执行之间,又有其他人执行了gets(获取了缓冲的指定值), 如此一来有可能出现非正常数据,则不允许修改。
三、Redis
1.安装API
pip3 install redis
2.功能介绍
- 连接方式
- 连接池
- 操作
- String 操作
- Hash 操作
- List 操作
- Set 操作
- Sort Set 操作
- 管道
- 发布订阅
3.基本操作
import redis r = redis.Redis(host='10.211.55.4', port=6379) r.set('foo', 'Bar') print r.get('foo')
4.连接池
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
import redis pool = redis.ConnectionPool(host='10.211.55.4', port=6379) r = redis.Redis(connection_pool=pool) r.set('foo', 'Bar') print r.get('foo')
5.操作
参考:http://www.cnblogs.com/wupeiqi/articles/5132791.html