Python学习笔记day10
Twsited异步网络框架
twisted是一个用python语言写的事件驱动的网络框架,他支持很多种协议,包括UDP,TCP,TLS和其他应用层协议,比如HTTP,SMTP,NNTM,IRC,XMPP/Jabber。 非常好的一点是twisted实现和很多应用层的协议,开发人员可以直接只用这些协议的实现。其实要修改Twisted的SSH服务器端实现非常简单。很多时候,开发人员需要实现protocol类。
Twisted是一个事件驱动的网络框架,其中包含了诸多功能,例如:网络协议、线程、数据库管理、网络操作、电子邮件等。
事件驱动
简而言之,事件驱动分为二个部分:第一,注册事件;第二,触发事件。
自定义事件驱动框架,命名为:“弑君者”:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#!/usr/bin/env python # -*- coding:utf-8 -*- # event_drive.py event_list = [] def run(): for event in event_list: obj = event() obj.execute() class BaseHandler( object ): """ 用户必须继承该类,从而规范所有类的方法(类似于接口的功能) """ def execute( self ): raise Exception( 'you must overwrite execute' ) 最牛逼的事件驱动框架 |
程序员使用“弑君者框架”:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#!/usr/bin/env python # -*- coding:utf-8 -*- from source import event_drive class MyHandler(event_drive.BaseHandler): def execute( self ): print 'event-drive execute MyHandler' event_drive.event_list.append(MyHandler) event_drive.run() |
Protocols
Protocols描述了如何以异步的方式处理网络中的事件。HTTP、DNS以及IMAP是应用层协议中的例子。Protocols实现了IProtocol接口,它包含如下的方法:
makeConnection 在transport对象和服务器之间建立一条连接
connectionMade 连接建立起来后调用
dataReceived 接收数据时调用
connectionLost 关闭连接时调用
Transports
Transports代表网络中两个通信结点之间的连接。Transports负责描述连接的细节,比如连接是面向流式的还是面向数据报的,流控以及可靠性。TCP、UDP和Unix套接字可作为transports的例子。它们被设计为“满足最小功能单元,同时具有最大程度的可复用性”,而且从协议实现中分离出来,这让许多协议可以采用相同类型的传输。Transports实现了ITransports接口,它包含如下的方法:
write 以非阻塞的方式按顺序依次将数据写到物理连接上
writeSequence 将一个字符串列表写到物理连接上
loseConnection 将所有挂起的数据写入,然后关闭连接
getPeer 取得连接中对端的地址信息
getHost 取得连接中本端的地址信息
将transports从协议中分离出来也使得对这两个层次的测试变得更加简单。可以通过简单地写入一个字符串来模拟传输,用这种方式来检查。
EchoServer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
from twisted.internet import protocol from twisted.internet import reactor class Echo(protocol.Protocol): def dataReceived( self , data): self .transport.write(data) def main(): factory = protocol.ServerFactory() factory.protocol = Echo reactor.listenTCP( 1234 ,factory) reactor.run() if __name__ = = '__main__' : main() |
EchoClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
from twisted.internet import reactor, protocol # a client protocol class EchoClient(protocol.Protocol): """Once connected, send a message, then print the result.""" def connectionMade( self ): self .transport.write( "hello alex!" ) def dataReceived( self , data): "As soon as any data is received, write it back." print "Server said:" , data self .transport.loseConnection() def connectionLost( self , reason): print "connection lost" class EchoFactory(protocol.ClientFactory): protocol = EchoClient def clientConnectionFailed( self , connector, reason): print "Connection failed - goodbye!" reactor.stop() def clientConnectionLost( self , connector, reason): print "Connection lost - goodbye!" reactor.stop() # this connects the protocol to a server running on port 8000 def main(): f = EchoFactory() reactor.connectTCP( "localhost" , 1234 , f) reactor.run() # this only runs if the module was *not* imported if __name__ = = '__main__' : main() |
运行服务器端脚本将启动一个TCP服务器,监听端口1234上的连接。服务器采用的是Echo协议,数据经TCP transport对象写出。运行客户端脚本将对服务器发起一个TCP连接,回显服务器端的回应然后终止连接并停止reactor事件循环。这里的Factory用来对连接的双方生成protocol对象实例。两端的通信是异步的,connectTCP负责注册回调函数到reactor事件循环中,当socket上有数据可读时通知回调处理。
Redis
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
一、Redis安装和基本使用
1
2
3
4
|
wget http: / / download.redis.io / releases / redis - 3.0 . 6.tar .gz tar xzf redis - 3.0 . 6.tar .gz cd redis - 3.0 . 6 make |
启动服务端
1
|
src / redis - server |
启动客户端
1
2
3
4
5
|
src / redis - cli redis> set foo bar OK redis> get foo "bar" |
二、Python操作Redis
1
2
3
4
5
6
7
|
sudo pip install redis or sudo easy_install redis or 源码安装 详见:https: / / github.com / WoLpH / redis - py |
API使用
redis-py 的API的使用可以分类为:
- 连接方式
- 连接池
- 操作
- String 操作
- Hash 操作
- List 操作
- Set 操作
- Sort Set 操作
- 管道
- 发布订阅
1、操作模式
redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。
1
2
3
4
5
6
7
8
|
#!/usr/bin/env python # -*- coding:utf-8 -*- import redis r = redis.Redis(host = '10.211.55.4' , port = 6379 ) r. set ( 'foo' , 'Bar' ) print r.get( 'foo' ) |
2、连接池
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
1
2
3
4
5
6
7
8
9
10
|
#!/usr/bin/env python # -*- coding:utf-8 -*- 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' ) |
3、操作
String操作,redis中的String在在内存中按照一个name对应一个value来存储。如图:
set(name, value, ex=None, px=None, nx=False, xx=False)
123456在Redis中设置值,默认,不存在则创建,存在则修改
参数:
ex,过期时间(秒)
px,过期时间(毫秒)
nx,如果设置为True,则只有name不存在时,当前set操作才执行
xx,如果设置为True,则只有name存在时,岗前set操作才执行
setnx(name, value)
1设置值,只有name不存在时,执行设置操作(添加)
setex(name, value, time)
123# 设置值
# 参数:
# time,过期时间(数字秒 或 timedelta对象)
psetex(name, time_ms, value)
123# 设置值
# 参数:
# time_ms,过期时间(数字毫秒 或 timedelta对象)
mset(*args, **kwargs)
12345批量设置值
如:
mset(k1=
'v1'
, k2=
'v2'
)
或
mget({
'k1'
:
'v1'
,
'k2'
:
'v2'
})
get(name)
1获取值
mget(keys, *args)
12345批量获取
如:
mget(
'ylr'
,
'wupeiqi'
)
或
r.mget([
'ylr'
,
'wupeiqi'
])
getset(name, value)
1设置新值并获取原来的值
getrange(key, start, end)
123456# 获取子序列(根据字节获取,非字符)
# 参数:
# name,Redis 的 name
# start,起始位置(字节)
# end,结束位置(字节)
# 如: "武沛齐" ,0-3表示 "武"
setrange(name, offset, value)
1234# 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
# 参数:
# offset,字符串的索引,字节(一个汉字三个字节)
# value,要设置的值
setbit(name, offset, value)
123456789101112131415161718192021222324252627# 对name对应值的二进制表示的位进行操作
# 参数:
# name,redis的name
# offset,位的索引(将值变换成二进制后再进行索引)
# value,值只能是 1 或 0
# 注:如果在Redis中有一个对应: n1 = "foo",
那么字符串foo的二进制表示为:
01100110
01101111
01101111
所以,如果执行 setbit(
'n1'
,
7
,
1
),则就会将第
7
位设置为
1
,
那么最终二进制则变成
01100111
01101111
01101111
,即:
"goo"
# 扩展,转换二进制表示:
# source = "武沛齐"
source
=
"foo"
for
i
in
source:
num
=
ord
(i)
bin
(num).replace(
'b'
,'')
特别的,如果source是汉字
"武沛齐"
怎么办?
答:对于utf
-
8
,每一个汉字占
3
个字节,那么
"武沛齐"
则有
9
个字节
对于汉字,
for
循环时候会按照 字节 迭代,那么在迭代时,将每一个字节转换 十进制数,然后再将十进制数转换成二进制
11100110
10101101
10100110
11100110
10110010
10011011
11101001
10111101
10010000
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
武 沛 齐
getbit(name, offset)
1# 获取name对应的值的二进制表示中的某位的值 (0或1)
bitcount(key, start=None, end=None)
12345# 获取name对应的值的二进制表示中 1 的个数
# 参数:
# key,Redis的name
# start,位起始位置
# end,位结束位置
bitop(operation, dest, *keys)
12345678910# 获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
# 参数:
# operation,AND(并) 、 OR(或) 、 NOT(非) 、 XOR(异或)
# dest, 新的Redis的name
# *keys,要查找的Redis的name
# 如:
bitop(
"AND"
,
'new_name'
,
'n1'
,
'n2'
,
'n3'
)
# 获取Redis中n1,n2,n3对应的值,然后讲所有的值做位运算(求并集),然后将结果保存 new_name 对应的值中
strlen(name)
1# 返回name对应值的字节长度(一个汉字3个字节)
incr(self, name, amount=1)
1234567# 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
# 参数:
# name,Redis的name
# amount,自增数(必须是整数)
# 注:同incrby
incrbyfloat(self, name, amount=1.0)
12345# 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
# 参数:
# name,Redis的name
# amount,自增数(浮点型)
decr(self, name, amount=1)
12345# 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。
# 参数:
# name,Redis的name
# amount,自减数(整数)
append(key, value)
12345# 在redis name对应的值后面追加内容
# 参数:
key, redis的name
value, 要追加的字符串
Hash操作,redis中Hash在内存中的存储格式如下图:
hset(name, key, value)
123456789# name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
# 参数:
# name,redis的name
# key,name对应的hash中的key
# value,name对应的hash中的value
# 注:
# hsetnx(name, key, value),当name对应的hash中不存在当前key时则创建(相当于添加)
hmset(name, mapping)
12345678# 在name对应的hash中批量设置键值对
# 参数:
# name,redis的name
# mapping,字典,如:{'k1':'v1', 'k2': 'v2'}
# 如:
# r.hmset('xx', {'k1':'v1', 'k2': 'v2'})
hget(name,key)
1# 在name对应的hash中获取根据key获取value
hmget(name, keys, *args)
1234567891011# 在name对应的hash中获取多个key的值
# 参数:
# name,reids对应的name
# keys,要获取key集合,如:['k1', 'k2', 'k3']
# *args,要获取的key,如:k1,k2,k3
# 如:
# r.mget('xx', ['k1', 'k2'])
# 或
# print r.hmget('xx', 'k1', 'k2')
hgetall(name)
1获取name对应
hash
的所有键值
hlen(name)
1# 获取name对应的hash中键值对的个数
hkeys(name)
1# 获取name对应的hash中所有的key的值
hvals(name)
1# 获取name对应的hash中所有的value的值
hexists(name, key)
1# 检查name对应的hash是否存在当前传入的key
hdel(name,*keys)
1# 将name对应的hash中指定key的键值对删除
hincrby(name, key, amount=1)
12345# 自增name对应的hash中的指定key的值,不存在则创建key=amount
# 参数:
# name,redis中的name
# key, hash对应的key
# amount,自增数(整数)
hincrbyfloat(name, key, amount=1.0)
12345678# 自增name对应的hash中的指定key的值,不存在则创建key=amount
# 参数:
# name,redis中的name
# key, hash对应的key
# amount,自增数(浮点数)
# 自增name对应的hash中的指定key的值,不存在则创建key=amount
hscan(name, cursor=0, match=None, count=None)
12345678910111213# 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆
# 参数:
# name,redis的name
# cursor,游标(基于游标分批取获取数据)
# match,匹配指定key,默认None 表示所有的key
# count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
# 如:
# 第一次:cursor1, data1 = r.hscan('xx', cursor=0, match=None, count=None)
# 第二次:cursor2, data1 = r.hscan('xx', cursor=cursor1, match=None, count=None)
# ...
# 直到返回值cursor的值为0时,表示数据已经通过分片获取完毕
hscan_iter(name, match=None, count=None)
123456789# 利用yield封装hscan创建生成器,实现分批去redis中获取数据
# 参数:
# match,匹配指定key,默认None 表示所有的key
# count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
# 如:
# for item in r.hscan_iter('xx'):
# print item
List操作,redis中的List在在内存中按照一个name对应一个List来存储。如图:
lpush(name,values)
12345678# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边
# 如:
# r.lpush('oo', 11,22,33)
# 保存顺序为: 33,22,11
# 扩展:
# rpush(name, values) 表示从右向左操作
lpushx(name,value)
1234# 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
# 更多:
# rpushx(name, value) 表示从右向左操作
llen(name)
1# name对应的list元素的个数
linsert(name, where, refvalue, value))
1234567# 在name对应的列表的某一个值前或后插入一个新值
# 参数:
# name,redis的name
# where,BEFORE或AFTER
# refvalue,标杆值,即:在它前后插入数据
# value,要插入的数据
r.lset(name, index, value)
123456# 对name对应的list中的某一个索引位置重新赋值
# 参数:
# name,redis的name
# index,list的索引位置
# value,要设置的值
r.lrem(name, value, num)
12345678# 在name对应的list中删除指定的值
# 参数:
# name,redis的name
# value,要删除的值
# num, num=0,删除列表中所有的指定值;
# num=2,从前到后,删除2个;
# num=-2,从后向前,删除2个
lpop(name)
1234# 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
# 更多:
# rpop(name) 表示从右向左操作
lindex(name, index)
1在name对应的列表中根据索引获取列表元素
lrange(name, start, end)
12345# 在name对应的列表分片获取数据
# 参数:
# name,redis的name
# start,索引的起始位置
# end,索引结束位置
ltrim(name, start, end)
12345# 在name对应的列表中移除没有在start-end索引之间的值
# 参数:
# name,redis的name
# start,索引的起始位置
# end,索引结束位置
rpoplpush(src, dst)
1234# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
# 参数:
# src,要取数据的列表的name
# dst,要添加数据的列表的name
blpop(keys, timeout)
12345678# 将多个列表排列,按照从左到右去pop对应列表的元素
# 参数:
# keys,redis的name的集合
# timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
# 更多:
# r.brpop(keys, timeout),从右向左获取数据
brpoplpush(src, dst, timeout=0)
123456# 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
# 参数:
# src,取出并要移除元素的列表对应的name
# dst,要插入元素的列表对应的name
# timeout,当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞
自定义增量迭代
123456789101112131415161718# 由于redis类库中没有提供对列表元素的增量迭代,如果想要循环name对应的列表的所有元素,那么就需要:
# 1、获取name对应的所有列表
# 2、循环列表
# 但是,如果列表非常大,那么就有可能在第一步时就将程序的内容撑爆,所有有必要自定义一个增量迭代的功能:
def
list_iter(name):
"""
自定义redis列表增量迭代
:param name: redis中的name,即:迭代name对应的列表
:return: yield 返回 列表元素
"""
list_count
=
r.llen(name)
for
index
in
xrange
(list_count):
yield
r.lindex(name, index)
# 使用
for
item
in
list_iter(
'pp'
):
item
Set操作,Set集合就是不允许重复的列表
sadd(name,values)
1# name对应的集合中添加元素
scard(name)
1获取name对应的集合中元素个数
sdiff(keys, *args)
1在第一个name对应的集合中且不在其他name对应的集合的元素集合
sdiffstore(dest, keys, *args)
1# 获取第一个name对应的集合中且不在其他name对应的集合,再将其新加入到dest对应的集合中
sinter(keys, *args)
1# 获取多一个name对应集合的并集
sinterstore(dest, keys, *args)
1# 获取多一个name对应集合的并集,再讲其加入到dest对应的集合中
sismember(name, value)
1# 检查value是否是name对应的集合的成员
smembers(name)
1# 获取name对应的集合的所有成员
smove(src, dst, value)
1# 将某个成员从一个集合中移动到另外一个集合
spop(name)
1# 从集合的右侧(尾部)移除一个成员,并将其返回
srandmember(name, numbers)
1# 从name对应的集合中随机获取 numbers 个元素
srem(name, values)
1# 在name对应的集合中删除某些值
sunion(keys, *args)
1# 获取多一个name对应的集合的并集
sunionstore(dest,keys, *args)
1# 获取多一个name对应的集合的并集,并将结果保存到dest对应的集合中
sscan(name, cursor=0, match=None, count=None)
sscan_iter(name, match=None, count=None)
1# 同字符串的操作,用于增量迭代分批获取元素,避免内存消耗太大
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。
zadd(name, *args, **kwargs)
12345# 在name对应的有序集合中添加元素
# 如:
# zadd('zz', 'n1', 1, 'n2', 2)
# 或
# zadd('zz', n1=11, n2=22)
zcard(name)
1# 获取name对应的有序集合元素的数量
zcount(name, min, max)
1# 获取name对应的有序集合中分数 在 [min,max] 之间的个数
zincrby(name, value, amount)
1# 自增name对应的有序集合的 name 对应的分数
r.zrange( name, start, end, desc=False, withscores=False, score_cast_func=float)
123456789101112131415161718# 按照索引范围获取name对应的有序集合的元素
# 参数:
# name,redis的name
# start,有序集合索引起始位置(非分数)
# end,有序集合索引结束位置(非分数)
# desc,排序规则,默认按照分数从小到大排序
# withscores,是否获取元素的分数,默认只获取元素的值
# score_cast_func,对分数进行数据转换的函数
# 更多:
# 从大到小排序
# zrevrange(name, start, end, withscores=False, score_cast_func=float)
# 按照分数范围获取name对应的有序集合的元素
# zrangebyscore(name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
# 从大到小排序
# zrevrangebyscore(name, max, min, start=None, num=None, withscores=False, score_cast_func=float)
zrank(name, value)
1234# 获取某个值在 name对应的有序集合中的排行(从 0 开始)
# 更多:
# zrevrank(name, value),从大到小排序
zrangebylex(name, min, max, start=None, num=None)
1234567891011121314151617# 当有序集合的所有成员都具有相同的分值时,有序集合的元素会根据成员的 值 (lexicographical ordering)来进行排序,而这个命令则可以返回给定的有序集合键 key 中, 元素的值介于 min 和 max 之间的成员
# 对集合中的每个成员进行逐个字节的对比(byte-by-byte compare), 并按照从低到高的顺序, 返回排序后的集合成员。 如果两个字符串有一部分内容是相同的话, 那么命令会认为较长的字符串比较短的字符串要大
# 参数:
# name,redis的name
# min,左区间(值)。 + 表示正无限; - 表示负无限; ( 表示开区间; [ 则表示闭区间
# min,右区间(值)
# start,对结果进行分片处理,索引位置
# num,对结果进行分片处理,索引后面的num个元素
# 如:
# ZADD myzset 0 aa 0 ba 0 ca 0 da 0 ea 0 fa 0 ga
# r.zrangebylex('myzset', "-", "[ca") 结果为:['aa', 'ba', 'ca']
# 更多:
# 从大到小排序
# zrevrangebylex(name, max, min, start=None, num=None)
zrem(name, values)
123# 删除name对应的有序集合中值是values的成员
# 如:zrem('zz', ['s1', 's2'])
zremrangebyrank(name, min, max)
1# 根据排行范围删除
zremrangebyscore(name, min, max)
1# 根据分数范围删除
zremrangebylex(name, min, max)
1# 根据值返回删除
zscore(name, value)
1# 获取name对应有序集合中 value 对应的分数
zinterstore(dest, keys, aggregate=None)
12# 获取两个有序集合的交集,如果遇到相同值不同分数,则按照aggregate进行操作
# aggregate的值为: SUM MIN MAX
zunionstore(dest, keys, aggregate=None)
12# 获取两个有序集合的并集,如果遇到相同值不同分数,则按照aggregate进行操作
# aggregate的值为: SUM MIN MAX
zscan(name, cursor=0, match=None, count=None, score_cast_func=float)
zscan_iter(name, match=None, count=None,score_cast_func=float)
1# 同字符串相似,相较于字符串新增score_cast_func,用来对分数进行操作
其他常用操作
delete(*names)
1# 根据删除redis中的任意数据类型
exists(name)
1# 检测redis的name是否存在
keys(pattern='*')
1234567# 根据模型获取redis的name
# 更多:
# KEYS * 匹配数据库中所有 key 。
# KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
# KEYS h*llo 匹配 hllo 和 heeeeello 等。
# KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo
expire(name ,time)
1# 为某个redis的某个name设置超时时间
rename(src, dst)
1# 对redis的name重命名为
move(name, db))
1# 将redis的某个值移动到指定的db下
randomkey()
1# 随机获取一个redis的name(不删除)
type(name)
1# 获取name对应值的类型
scan(cursor=0, match=None, count=None)
scan_iter(match=None, count=None)
1# 同字符串操作,用于增量迭代获取key
4、管道
redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#!/usr/bin/env python # -*- coding:utf-8 -*- import redis pool = redis.ConnectionPool(host = '10.211.55.4' , port = 6379 ) r = redis.Redis(connection_pool = pool) # pipe = r.pipeline(transaction=False) pipe = r.pipeline(transaction = True ) r. set ( 'name' , 'alex' ) r. set ( 'role' , 'sb' ) pipe.execute() |
5、发布订阅
发布者:服务器
订阅者:Dashboad和数据处理
Demo如下:
订阅者:
1
2
3
4
5
6
7
8
9
10
11
|
#!/usr/bin/env python # -*- coding:utf-8 -*- from monitor.RedisHelper import RedisHelper obj = RedisHelper() redis_sub = obj.subscribe() while True : msg = redis_sub.parse_response() print msg |
发布者:
1
2
3
4
5
6
7
|
#!/usr/bin/env python # -*- coding:utf-8 -*- from monitor.RedisHelper import RedisHelper obj = RedisHelper() obj.public( 'hello' ) |
RabbitMQ
RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消 息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。
RabbitMQ安装
1
2
3
4
5
6
7
8
|
安装配置epel源 $ rpm - ivh http: / / dl.fedoraproject.org / pub / epel / 6 / i386 / epel - release - 6 - 8.noarch .rpm 安装erlang $ yum - y install erlang 安装RabbitMQ $ yum - y install rabbitmq - server |
注意:service rabbitmq-server start/stop
安装API
1
2
3
4
5
6
7
|
pip install pika or easy_install pika or 源码 https: / / pypi.python.org / pypi / pika |
使用API操作RabbitMQ
应用场景1-“Hello Word”
一个P向queue发送一个message,一个C从该queue接收message并打印。
send端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#!/usr/bin/env python import pika connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = connection.channel() #声明queue channel.queue_declare(queue = 'hello' ) #n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange. channel.basic_publish(exchange = '', routing_key = 'hello' , body = 'Hello World!' ) print ( " [x] Sent 'Hello World!'" ) connection.close() |
receive端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#_*_coding:utf-8_*_ __author__ = 'Alex Li' import pika connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = connection.channel() #You may ask why we declare the queue again ‒ we have already declared it in our previous code. # We could avoid that if we were sure that the queue already exists. For example if send.py program #was run before. But we're not yet sure which program to run first. In such cases it's a good # practice to repeat declaring the queue in both programs. channel.queue_declare(queue = 'hello' ) def callback(ch, method, properties, body): print ( " [x] Received %r" % body) channel.basic_consume(callback, queue = 'hello' , no_ack = True ) print ( ' [*] Waiting for messages. To exit press CTRL+C' ) channel.start_consuming() |
应用场景2-work queues
将耗时的消息处理通过队列分配给多个consumer来处理,我们称此处的consumer为worker,我们将此处的queue称为Task Queue,其目的是为了避免资源密集型的task的同步处理,也即立即处理task并等待完成。相反,调度task使其稍后被处理。也即把task封装进message并发送到task queue,worker进程在后台运行,从task queue取出task并执行job,若运行了多个worker,则task可在多个worker间分配。
在这种模式下,RabbitMQ会默认把p发的消息依次分发给各个消费者(c),跟负载均衡差不多
消息提供者代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import pika connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = connection.channel() #声明queue channel.queue_declare(queue = 'task_queue' ) #n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange. import sys message = ' ' .join(sys.argv[ 1 :]) or "Hello World!" channel.basic_publish(exchange = '', routing_key = 'task_queue' , body = message, properties = pika.BasicProperties( delivery_mode = 2 , # make message persistent )) print ( " [x] Sent %r" % message) connection.close() |
消费者代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import pika,time connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost' )) channel = connection.channel() def callback(ch, method, properties, body): print ( " [x] Received %r" % body) time.sleep(body.count(b '.' )) print ( " [x] Done" ) ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_consume(callback, queue = 'task_queue' , ) print ( ' [*] Waiting for messages. To exit press CTRL+C' ) channel.start_consuming() |
应用场景3-Publish/Subscribe
在应用场景2中一个message(task)仅被传递给了一个comsumer(worker)。现在我们设法将一个message传递给多个consumer。这种模式被称为publish/subscribe。此处以一个简单的日志系统为例进行说明。该系统包含一个log发送程序和一个log接收并打印的程序。由log发送者发送到queue的消息可以被所有运行的log接收者接收。因此,我们可以运行一个log接收者直接在屏幕上显示log,同时运行另一个log接收者将log写入磁盘文件。
消息publisher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() channel.exchange_declare(exchange = 'logs' , type = 'fanout' ) message = ' ' .join(sys.argv[ 1 :]) or "info: Hello World!" channel.basic_publish(exchange = 'logs' , routing_key = '', body = message) print ( " [x] Sent %r" % message) connection.close() |
消息subscriber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#_*_coding:utf-8_*_ __author__ = 'Alex Li' import pika connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() channel.exchange_declare(exchange = 'logs' , type = 'fanout' ) result = channel.queue_declare(exclusive = True ) #不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除 queue_name = result.method.queue channel.queue_bind(exchange = 'logs' , queue = queue_name) print ( ' [*] Waiting for logs. To exit press CTRL+C' ) def callback(ch, method, properties, body): print ( " [x] %r" % body) channel.basic_consume(callback, queue = queue_name, no_ack = True ) channel.start_consuming() |
应用场景4-Routing
应用场景3中构建了简单的log系统,可以将log message广播至多个receiver。现在我们将考虑只把指定的message类型发送给其subscriber,比如,只把error message写到log file而将所有log message显示在控制台。
publisher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() channel.exchange_declare(exchange = 'direct_logs' , type = 'direct' ) severity = sys.argv[ 1 ] if len (sys.argv) > 1 else 'info' message = ' ' .join(sys.argv[ 2 :]) or 'Hello World!' channel.basic_publish(exchange = 'direct_logs' , routing_key = severity, body = message) print ( " [x] Sent %r:%r" % (severity, message)) connection.close() |
subscriber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() channel.exchange_declare(exchange = 'direct_logs' , type = 'direct' ) result = channel.queue_declare(exclusive = True ) queue_name = result.method.queue severities = sys.argv[ 1 :] if not severities: sys.stderr.write( "Usage: %s [info] [warning] [error]\n" % sys.argv[ 0 ]) sys.exit( 1 ) for severity in severities: channel.queue_bind(exchange = 'direct_logs' , queue = queue_name, routing_key = severity) print ( ' [*] Waiting for logs. To exit press CTRL+C' ) def callback(ch, method, properties, body): print ( " [x] %r:%r" % (method.routing_key, body)) channel.basic_consume(callback, queue = queue_name, no_ack = True ) channel.start_consuming() |
应用场景5-topic
应用场景4中改进的log系统中用direct类型的exchange替换应用场景3中的fanout类型exchange实现将不同的log message发送给不同的subscriber(也即分别通过不同的routing_key将queue绑定到exchange,这样exchange便可将不同的message根据message内容路由至不同的queue)。但仍然存在限制,不能根据多个规则路由消息,比如接收者要么只能收error类型的log message要么只能收info类型的message。如果我们不仅想根据log的重要级别如info、warning、error等来进行log message路由还想同时根据log message的来源如auth、cron、kern来进行路由。为了达到此目的,需要topic类型的exchange。topic类型的exchange中routing_key中可以包含两个特殊字符:“*”用于替代一个词,“#”用于0个或多个词。
publisher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() channel.exchange_declare(exchange = 'topic_logs' , type = 'topic' ) routing_key = sys.argv[ 1 ] if len (sys.argv) > 1 else 'anonymous.info' message = ' ' .join(sys.argv[ 2 :]) or 'Hello World!' channel.basic_publish(exchange = 'topic_logs' , routing_key = routing_key, body = message) print ( " [x] Sent %r:%r" % (routing_key, message)) connection.close() |
subscriber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import pika import sys connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() channel.exchange_declare(exchange = 'topic_logs' , type = 'topic' ) result = channel.queue_declare(exclusive = True ) queue_name = result.method.queue binding_keys = sys.argv[ 1 :] if not binding_keys: sys.stderr.write( "Usage: %s [binding_key]...\n" % sys.argv[ 0 ]) sys.exit( 1 ) for binding_key in binding_keys: channel.queue_bind(exchange = 'topic_logs' , queue = queue_name, routing_key = binding_key) print ( ' [*] Waiting for logs. To exit press CTRL+C' ) def callback(ch, method, properties, body): print ( " [x] %r:%r" % (method.routing_key, body)) channel.basic_consume(callback, queue = queue_name, no_ack = True ) channel.start_consuming() |
应用场景6-PRC
在应用场景2中描述了如何使用work queue将耗时的task分配到不同的worker中。但是,如果我们task是想在远程的计算机上运行一个函数并等待返回结果呢。这根场景2中的描述是一个完全不同的故事。这一模式被称为远程过程调用。现在,我们将构建一个RPC系统,包含一个client和可扩展的RPC server,通过返回斐波那契数来模拟RPC service。
RPC server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#_*_coding:utf-8_*_ __author__ = 'Alex Li' import pika import time connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() channel.queue_declare(queue = 'rpc_queue' ) def fib(n): if n = = 0 : return 0 elif n = = 1 : return 1 else : return fib(n - 1 ) + fib(n - 2 ) def on_request(ch, method, props, body): n = int (body) print ( " [.] fib(%s)" % n) response = fib(n) ch.basic_publish(exchange = '', routing_key = props.reply_to, properties = pika.BasicProperties(correlation_id = \ props.correlation_id), body = str (response)) ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_qos(prefetch_count = 1 ) channel.basic_consume(on_request, queue = 'rpc_queue' ) print ( " [x] Awaiting RPC requests" ) channel.start_consuming() |
RPC client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
import pika import uuid class FibonacciRpcClient( object ): def __init__( self ): self .connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) self .channel = self .connection.channel() result = self .channel.queue_declare(exclusive = True ) self .callback_queue = result.method.queue self .channel.basic_consume( self .on_response, no_ack = True , queue = self .callback_queue) def on_response( self , ch, method, props, body): if self .corr_id = = props.correlation_id: self .response = body def call( self , n): self .response = None self .corr_id = str (uuid.uuid4()) self .channel.basic_publish(exchange = '', routing_key = 'rpc_queue' , properties = pika.BasicProperties( reply_to = self .callback_queue, correlation_id = self .corr_id, ), body = str (n)) while self .response is None : self .connection.process_data_events() return int ( self .response) fibonacci_rpc = FibonacciRpcClient() print ( " [x] Requesting fib(30)" ) response = fibonacci_rpc.call( 30 ) print ( " [.] Got %r" % response) |