s12-20160319-day10
pytho自动化开发 day10
Date:2016.03.19
@南非波波
课程大纲:
day09
http://www.cnblogs.com/alex3714/articles/5248247.html
day10
http://www.cnblogs.com/alex3714/articles/5286889.html
一、回顾
-
队列
- 队列的作业就是实现多个线程之间数据安全的交互
- 队列类型:先进先出、后进先出、优先级
- queue的数据必须按照顺序进行取出-->处理-->放回。主要作用就是不同进程之间数据的交换,manager可以进行多个进程之间的数据的共享,而且是数据安全的。
- 生产者-消费者模型:实现程序的松耦合
-
gevent模块:对Greenlet模块的一次封装
- gevent里面的socket本身可以实现IO阻塞变成非阻塞
- monkey.path_all()可以帮助我们实现阻塞变成非阻塞
-
协程
- 实现单个线程里面的并发
- 无需线程上下文切换的开销,无需原子操作锁定及同步的开销,方便切换控制流,高并发+高扩展性+低成本
- 无法利用多核资源,但是可以实现单个进程下面起一个线程,然后一个线程下面实现多个协程并发
-
select
-
select 与poll的区别
select有一个最大文件数的限制1024,文件扫描一个列表是非常低效的;poll没有这个限制 内核态到用户态的数据copy;Epoll直接调用C语言进行内核态的数据nat到用户态
-
select代码注释
__auther__ = 'Victor' import select import socket import sys import queue # 创建一个TCP/IP socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setblocking(False) # 绑定socket到指定端口 server_address = ('localhost', 10000) print(sys.stderr, 'starting up on %s port %s' % server_address) server.bind(server_address) # 监听连接的地址 server.listen(5) inputs = [server] # Socket的读操作 outputs = [] # socket的写操作 message_queues = {} while inputs: # Wait for at least one of the sockets to be ready for processing print( ' waiting for the next event') readable, writable, exceptional = select.select(inputs, outputs, inputs) # 监听句柄序列,如果某个发生变化,select的第一个rLest会拿到数据,output只要有数据wLest就能获取到,select的第三个参数inputs用来监测异常,并赋值给exceptional。 # 监听inputs,outputs,inputs 如果他们的值有变化,就将分别赋值给readable,writable,exceptional。 for s in readable: # 遍历readable的值。 if s is server: connection, client_address = s.accept() # 如果s 是server,那么server socket将接收连接。 print('new connection from', client_address) # 打印出连接客户端的地址。 connection.setblocking(False) # 设置socket 为非阻塞模式。 inputs.append(connection) # 因为有读操作发生,所以将此连接加入inputs message_queues[connection] = queue.Queue() # 为每个连接创建一个queue队列。使得每个连接接收到正确的数据。 else: data = s.recv(1024) # 如果s不是server,说明客户端连接来了,那么就接受客户端的数据。 if data: # 如果接收到客户端的数据 print(sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) ) message_queues[s].put(data) # 将收到的数据放入队列中 if s not in outputs: outputs.append(s) # 将socket客户端的连接加入select的output中,并且用来返回给客户端数据。 else: print('closing', client_address, 'after reading no data') # 如果没有收到客户端发来的空消息,则说明客户端已经断开连接。 if s in outputs: outputs.remove(s) # 既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉 inputs.remove(s) # inputs中也删除掉 s.close() # 把这个连接关闭掉 del message_queues[s] # 删除此客户端的消息队列 for s in writable: # 遍历output的数据 try: next_msg = message_queues[s].get_nowait() except queue.Empty: # 获取对应客户端消息队列中的数据,如果队列中的数据为空,从消息队列中移除此客户端连接。 print('output queue for', s.getpeername(), 'is empty') outputs.remove(s) else: print( 'sending "%s" to %s' % (next_msg, s.getpeername())) s.send(next_msg) # 如果消息队列有数据,则发送给客户端。 for s in exceptional: # 处理 "exceptional conditions" print('handling exceptional condition for', s.getpeername() ) inputs.remove(s) # 取消对出现异常的客户端的监听 if s in outputs: outputs.remove(s) # 移除客户端的连接对象。 s.close() # 关闭此socket连接 del message_queues[s] # 删除此消息队列。 ''' 在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生, 轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。 epoll的设计和实现与select完全不同。epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用什么数据结构实现?B+树)。把原先的select/poll调用分成了3个部分: 1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源) 2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字 3)调用epoll_wait收集发生的事件的连接 '''
-
epoll代码注释
__auther__ = 'Victor' #--------------这是一个epoll的例子-------------- import socket, select # 'windows'下不支持'epoll' EOL1 = b' ' EOL2 = b' ' response = b'HTTP/1.0 200 OK Date: Mon, 1 Jan 1996 01:01:01 GMT ' response += b'Content-Type: text/plain Content-Length: 13 ' response += b'Hello, world!' serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serversocket.bind(('0.0.0.0', 8080)) serversocket.listen(1) # 建立socket连接。 serversocket.setblocking(0) # 因为socket本身是阻塞的,setblocking(0)使得socket不阻塞 epoll = select.epoll() # 创建一个eopll对象 epoll.register(serversocket.fileno(), select.EPOLLIN) # 在服务器端socket上面注册对读event的关注,一个读event随时会触发服务器端socket去接收一个socket连接。 try: connections = {}; requests = {}; responses = {} # 生成3个字典,connection字典是存储文件描述符映射到他们相应的网络连接对象 while True: events = epoll.poll(1) # 查询epoll对象,看是否有任何关注的event被触发,参数‘1’表示,会等待一秒来看是否有event发生,如果有任何感兴趣的event发生在这次查询之前,这个查询就会带着这些event的列表立即返回 for fileno, event in events: # event作为一个序列(fileno,event code)的元组返回,fileno是文件描述符的代名词,始终是一个整数。 if fileno == serversocket.fileno(): # 如果一个读event在服务器端socket发生,就会有一个新的socket连接可能被创建。 connection, address = serversocket.accept() # 服务器端开始接收连接和客户端地址 connection.setblocking(0) # 设置新的socket为非阻塞模式 epoll.register(connection.fileno(), select.EPOLLIN) # 为新的socket注册对读(EPOLLIN)event的关注 connections[connection.fileno()] = connection requests[connection.fileno()] = b'' responses[connection.fileno()] = response elif event & select.EPOLLIN: requests[fileno] += connections[fileno].recv(1024) # 如果发生一个读event,就读取从客户端发过来的数据。 if EOL1 in requests[fileno] or EOL2 in requests[fileno]: epoll.modify(fileno, select.EPOLLOUT) # 一旦完成请求已经收到,就注销对读event的关注,注册对写(EPOLLOUT)event的关注,写event发生的时候,会回复数据给客户端。 print('-'*40 + ' ' + requests[fileno].decode()[:-2]) # 打印完整的请求,证明虽然与客户端的通信是交错进行的,但是数据可以作为一个整体来组装和处理。 elif event & select.EPOLLOUT: # 如果一个写event在一个客户端socket上面发生,他会接受新的数据以便发送到客户端。 byteswritten = connections[fileno].send(responses[fileno]) responses[fileno] = responses[fileno][byteswritten:] if len(responses[fileno]) == 0: # 每次发送一部分响应数据,直到完整的响应数据都已经发送给操作系统等待传输给客户端。 epoll.modify(fileno, 0) # 一旦完整的响应数据发送完成,就不再关注读或者写event。 connections[fileno].shutdown(socket.SHUT_RDWR) # 如果一个连接显式关闭,那么socket shutdown是可选的,在这里这样使用,是为了让客户端首先关闭。 # shutdown调用会通知客户端socket没有更多的数据应该被发送或者接收,并会让功能正常的客户端关闭自己的socket连接。 elif event & select.EPOLLHUP: # HUP挂起event表明客户端socket已经断开(即关闭),所以服务器端也需要关闭,没有必要注册对HUP event的关注,在socket上面,他们总是会被epoll对象注册。 epoll.unregister(fileno) # 注销对此socket连接的关注。 connections[fileno].close() # 关闭socket连接。 del connections[fileno] finally: epoll.unregister(serversocket.fileno()) # 去掉已经注册的文件句柄 epoll.close() # 关闭epoll对象 serversocket.close() # 关闭服务器连接 # 打开的socket连接不需要关闭,因为Python会在程序结束时关闭, 这里的显示关闭是个好的习惯。 ''' 首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。 不管是文件,还是套接字,还是管道,我们都可以把他们看作流。 之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据;通过write,我们可以往流写入数据。现在假定一个情形, 我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来), 这时候该怎么办? 阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做); 那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。 非阻塞忙轮询:接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?” 很明显一般人不会用第二种做法,不仅显很无脑,浪费话费不说,还占用了快递员大量的时间。 大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。 为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用(你知道它很慢的), 当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。 假设有一个管道,进程A为管道的写入方,B为管道的读出方。 假设一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了, 这个事件姑且称之为“缓冲区非空”。 但是“缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了, B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。 假设后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满” 也许事件Y1已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。 这四个情形涵盖了四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满(注都是说的内核缓冲区,且这四个术语都是我生造的,仅为解释其原理而造)。 这四个I/O事件是进行阻塞同步的根本。(如果不能理解“同步”是什么概念,请学习操作系统的锁,信号量,条件变量等任务同步方面的相关知识)。 然后我们来说说阻塞I/O的缺点。但是阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create), 很不幸这两种方法效率都不高。 于是再来考虑非阻塞忙轮询的I/O方式,我们发现我们可以同时处理多个流了(把一个流从阻塞模式切换到非阻塞模式再此不予讨论): while true { for i in stream[]; { if i has data read until unavailable } } 我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。 这里要补充一点,阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象(后文介绍的select以及epoll)处理甚至直接忽略。 为了避免CPU空转,可以引进了一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。这个代理比较厉害, 可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流 (于是我们可以把“忙”字去掉了)。代码长这样: while true { select(streams[]) for i in streams[] { if i has data read until unavailable } } 于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流 (可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。 但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,没一次无差别轮询时间就越长。再次 说了这么多,终于能好好解释epoll了 epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。 (复杂度降低到了O(1)) 在讨论epoll的实现细节之前,先把epoll的相关操作列出: epoll_create 创建一个epoll对象,一般epollfd = epoll_create() epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件 比如 epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入 epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入 epoll_wait(epollfd,...)等待直到注册的事件发生 (注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。 一个epoll模式的代码大概的样子是: while true { active_stream[] = epoll_wait(epollfd) for i in active_stream[] { read or write till } } '''
-
二、Twsited异步网络框架
-
事件驱动
将自定义的类和函数注册到事件列表中,事件驱动框架就会自行去列表中获取事件并执行。
第一,注册事件;第二,触发事件
示例代码:
#event_drive.py
#!/usr/local/env python3
'''
Author:@南非波波
Blog:http://www.cnblogs.com/songqingbo/
E-mail:qingbo.song@gmail.com
'''
'''
模拟twsited异步网络框架的流程
'''
#创建一个事件列表
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')
#event_run.py
#!/usr/local/env python3
'''
Author:@南非波波
Blog:http://www.cnblogs.com/songqingbo/
E-mail:qingbo.song@gmail.com
'''
import event_drive
#自定义事件,继承事件驱动自定义类
class MyHandler(event_drive.BaseHandler):
#重写执行函数
def execute(self):
print('event-drive execute MyHandler')
class YourHandler(event_drive.BaseHandler):
def execute(self):
print('event-drive ezecute YourHandler')
event_drive.event_list.append(MyHandler) #将事件注册到事件列表中
event_drive.event_list.append(YourHandler)
event_drive.run()
- Twisted框架
Echo_server
#!/usr/local/env python3
'''
Author:@南非波波
Blog:http://www.cnblogs.com/songqingbo/
E-mail:qingbo.song@gmail.com
'''
from twisted.internet import protocol
from twisted.internet import reactor
class Echo(protocol.Protocol):
'''
定义一个类,处理客户端传递的数据
'''
def dataReceived(self, data):
'''
一旦接收到客户端传递的数据就要调用该方法
:param data: 客户端传递过来的数据,python3版本传递的数据需要转换成bytes
:return: 返回的数据是将客户端传递过来的数据返回给客户端
'''
print("Client said:",data)
self.transport.write(data)
def main():
'''
主函数,程序执行时直接从该函数调用事件类
:return:
'''
factory = protocol.ServerFactory() #定义基础工厂类
factory.protocol = Echo #相当于socketserver中的Handler方法,工厂协议直接引用自定义的Echo类
reactor.listenTCP(5000,factory) #reactor自动重复去做一件事情。使用listenTCP监听端口
reactor.run() #运行
if __name__ == '__main__':
main()
Echo_client:
#!/usr/local/env python3
'''
Author:@南非波波
Blog:http://www.cnblogs.com/songqingbo/
E-mail:qingbo.song@gmail.com
'''
from twisted.internet import reactor, protocol
# a client protocol
class EchoClient(protocol.Protocol):
'''
客户端Echo事件
'''
def connectionMade(self):
'''
连接建立执行该方法,客户端发送数据
:return:
'''
self.transport.write(b"hello alex!")
def dataReceived(self, data):
'''
客户端接收服务端的数据
:param data:
:return:
'''
print("Server said:", data)
self.transport.loseConnection()
def connectionLost(self, reason):
'''
客户端接收完数据断开连接,主动执行该方法断开连接
:param reason:
:return:
'''
print("connection lost")
class EchoFactory(protocol.ClientFactory):
'''
自定义工厂类,继承prorocol.ClientFactory类
'''
protocol = EchoClient #hanld。自己重写了protocol类
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", 5000, f)
reactor.run()
# this only runs if the module was *not* imported
if __name__ == '__main__':
main()
- 升入学习
http://blog.csdn.net/hanhuili/article/details/9389433
http://krondo.com/an-introduction-to-asynchronous-programming-and-twisted/
三、非关系型数据库
-
Redis
参考:http://www.cnblogs.com/wupeiqi/articles/5132791.html
数据(键值对)存储在内存中,一个独立的内存管理器,可以使多个程序共享数据
默认是非持久化的,但是可以在配置文件中进行设置
-
redis基础使用
cli>keys * #查看所有的键 cli>set name swht ex 5 #设置一个键值对,其有效时间为5秒 cli>get name #获取键值
-
redis连接
import redis redis_cli = redis.Redis("localhost") print(redis_cli.get('name')) #b'swht' get方法只能获取字符
-
redis连接池
import redis pool = redis.ConnectionPool(host = 'localhost',port = 6379) redis_cli = redis.Redis(connection_pool=pool) redis_cli.set('age',56) print(redis_cli.get('age')) #b'56'
-
操作
set(name, value, ex=None, px=None, nx=False, xx=False)
在Redis中设置值,默认,不存在则创建,存在则修改 参数: ex,过期时间(秒) px,过期时间(毫秒) nx,如果设置为True,则只有name不存在时,当前set操作才执行 xx,如果设置为True,则只有name存在时,岗前set操作才执行
setnx(name, value)
设置值,只有name不存在时,执行设置操作(添加)
setex(name, value, time)
# 设置值 # 参数: # time,过期时间(数字秒 或 timedelta对象)
psetex(name, time_ms, value)
# 设置值 # 参数: # time_ms,过期时间(数字毫秒 或 timedelta对象)
mset(*args, **kwargs)
批量设置值 如: mset(k1='v1', k2='v2') 或 mget({'k1': 'v1', 'k2': 'v2'})
get(name)
获取值
mget(keys, *args)
批量获取 如: mget('ylr', 'wupeiqi') 或 r.mget(['ylr', 'wupeiqi'])
getset(name, value)
设置新值并获取原来的值
getrange(key, start, end)
# 获取子序列(根据字节获取,非字符) # 参数: # name,Redis 的 name # start,起始位置(字节) # end,结束位置(字节) # 如: "武沛齐" ,0-3表示 "武"
setrange(name, offset, value)
# 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加) # 参数: # offset,字符串的索引,字节(一个汉字三个字节) # value,要设置的值
setbit(name, offset, value) # 对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) print bin(num).replace('b','') 特别的,如果source是汉字 "武沛齐"怎么办? 答:对于utf-8,每一个汉字占 3 个字节,那么 "武沛齐" 则有 9个字节 对于汉字,for循环时候会按照 字节 迭代,那么在迭代时,将每一个字节转换 十进制数,然后再将十进制数转换成二进制
假定统计UV,使用setbit可以进行相应UV数统计。
#!/usr/local/env python3 ''' Author:@南非波波 Blog:http://www.cnblogs.com/songqingbo/ E-mail:qingbo.song@gmail.com ''' import redis pool = redis.ConnectionPool(host = 'localhost',port = 6379) redis_cli = redis.Redis(connection_pool=pool) redis_cli.setbit('ip',5,1) redis_cli.setbit('ip',45,1) redis_cli.setbit('ip',15,1) redis_cli.setbit('ip',45,1) print("uv_count:",redis_cli.bitcount('ip'))
getbit(name, offset)
# 获取name对应的值的二进制表示中的某位的值 (0或1) bitcount(key, start=None, end=None) # 获取name对应的值的二进制表示中 1 的个数 # 参数: # key,Redis的name # start,位起始位置 # end,位结束位置
bitop(operation, dest, *keys)
# 获取多个值,并将值做位运算,将最后的结果保存至新的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)
# 返回name对应值的字节长度(一个汉字3个字节)
incr(self, name, amount=1)
做pv统计比较有用 # 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。 # 参数: # name,Redis的name # amount,自增数(必须是整数) # 注:同incrby
incrbyfloat(self, name, amount=1.0)
# 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。 # 参数: # name,Redis的name # amount,自增数(浮点型)
decr(self, name, amount=1)
# 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。 # 参数: # name,Redis的name # amount,自减数(整数)
append(key, value)
# 在redis name对应的值后面追加内容 # 参数: key, redis的name value, 要追加的字符串
-
Hash操作
hset(name, key, value)
# name对应的hash中设置一个键值对(不存在,则创建;否则,修改) # 参数: # name,redis的name # key,name对应的hash中的key # value,name对应的hash中的value # 注: # hsetnx(name, key, value),当name对应的hash中不存在当前key时则创建(相当于添加)
hmset(name, mapping)
# 在name对应的hash中批量设置键值对 # 参数: # name,redis的name # mapping,字典,如:{'k1':'v1', 'k2': 'v2'} # 如: # r.hmset('xx', {'k1':'v1', 'k2': 'v2'})
hget(name,key)
# 在name对应的hash中获取根据key获取value hmget(name, keys, *args) # 在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)
获取name对应hash的所有键值
hlen(name)
# 获取name对应的hash中键值对的个数
hkeys(name)
# 获取name对应的hash中所有的key的值
hvals(name)
# 获取name对应的hash中所有的value的值
hexists(name, key)
# 检查name对应的hash是否存在当前传入的key
hdel(name,*keys)
# 将name对应的hash中指定key的键值对删除
hincrby(name, key, amount=1)
# 自增name对应的hash中的指定key的值,不存在则创建key=amount # 参数: # name,redis中的name # key, hash对应的key # amount,自增数(整数)
hincrbyfloat(name, key, amount=1.0)
# 自增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)
# 增量式迭代获取,对于数据大的数据非常有用,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)
# 利用yield封装hscan创建生成器,实现分批去redis中获取数据 # 参数: # match,匹配指定key,默认None 表示所有的key # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数 # 如: # for item in r.hscan_iter('xx'): # print item
-
List操作
lpush(name,values)
# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边 # 如: # r.lpush('oo', 11,22,33) # 保存顺序为: 33,22,11 # 扩展: # rpush(name, values) 表示从右向左操作
lpushx(name,value)
# 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边 # 更多: # rpushx(name, value) 表示从右向左操作
llen(name)
# name对应的list元素的个数
linsert(name, where, refvalue, value))
# 在name对应的列表的某一个值前或后插入一个新值 # 参数: # name,redis的name # where,BEFORE或AFTER # refvalue,标杆值,即:在它前后插入数据 # value,要插入的数据
r.lset(name, index, value)
# 对name对应的list中的某一个索引位置重新赋值 # 参数: # name,redis的name # index,list的索引位置 # value,要设置的值
r.lrem(name, value, num)
# 在name对应的list中删除指定的值 # 参数: # name,redis的name # value,要删除的值 # num, num=0,删除列表中所有的指定值; # num=2,从前到后,删除2个; # num=-2,从后向前,删除2个
lpop(name)
# 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素 # 更多: # rpop(name) 表示从右向左操作
lindex(name, index)
在name对应的列表中根据索引获取列表元素
lrange(name, start, end)
# 在name对应的列表分片获取数据 # 参数: # name,redis的name # start,索引的起始位置 # end,索引结束位置
ltrim(name, start, end)
# 在name对应的列表中移除没有在start-end索引之间的值 # 参数: # name,redis的name # start,索引的起始位置 # end,索引结束位置
rpoplpush(src, dst)
# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边 # 参数: # src,要取数据的列表的name # dst,要添加数据的列表的name
blpop(keys, timeout)
# 将多个列表排列,按照从左到右去pop对应列表的元素 # 参数: # keys,redis的name的集合 # timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞 # 更多: # r.brpop(keys, timeout),从右向左获取数据
brpoplpush(src, dst, timeout=0)
# 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧 # 参数: # src,取出并要移除元素的列表对应的name # dst,要插入元素的列表对应的name # timeout,当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞
自定义增量迭代
# 由于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'): print item
-
Set操作,Set集合就是不允许重复的列表
sadd(name,values)
# name对应的集合中添加元素
scard(name)
获取name对应的集合中元素个数
sdiff(keys, *args)
在第一个name对应的集合中且不在其他name对应的集合的元素集合
sdiffstore(dest, keys, *args)
# 获取第一个name对应的集合中且不在其他name对应的集合,再将其新加入到dest对应的集合中
sinter(keys, *args)
# 获取多一个name对应集合的并集
sinterstore(dest, keys, *args)
# 获取多一个name对应集合的并集,再讲其加入到dest对应的集合中
sismember(name, value)
# 检查value是否是name对应的集合的成员
smembers(name)
# 获取name对应的集合的所有成员
smove(src, dst, value)
# 将某个成员从一个集合中移动到另外一个集合
spop(name)
# 从集合的右侧(尾部)移除一个成员,并将其返回
srandmember(name, numbers)
# 从name对应的集合中随机获取 numbers 个元素
srem(name, values)
# 在name对应的集合中删除某些值
sunion(keys, *args)
# 获取多一个name对应的集合的并集
sunionstore(dest,keys, *args)
# 获取多一个name对应的集合的并集,并将结果保存到dest对应的集合中
sscan(name, cursor=0, match=None, count=None) sscan_iter(name, match=None, count=None)
# 同字符串的操作,用于增量迭代分批获取元素,避免内存消耗太大
-
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。
zadd(name, *args, **kwargs)
# 在name对应的有序集合中添加元素 # 如: # zadd('zz', 'n1', 1, 'n2', 2) # 或 # zadd('zz', n1=11, n2=22)
zcard(name)
# 获取name对应的有序集合元素的数量
zcount(name, min, max)
# 获取name对应的有序集合中分数 在 [min,max] 之间的个数
zincrby(name, value, amount)
# 自增name对应的有序集合的 name 对应的分数
r.zrange( name, start, end, desc=False, withscores=False, scorecastfunc=float)
# 按照索引范围获取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)
# 获取某个值在 name对应的有序集合中的排行(从 0 开始) # 更多: # zrevrank(name, value),从大到小排序
zrangebylex(name, min, max, start=None, num=None)
# 当有序集合的所有成员都具有相同的分值时,有序集合的元素会根据成员的 值 (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)
# 删除name对应的有序集合中值是values的成员 # 如:zrem('zz', ['s1', 's2'])
zremrangebyrank(name, min, max)
# 根据排行范围删除
zremrangebyscore(name, min, max)
# 根据分数范围删除
zremrangebylex(name, min, max)
# 根据值返回删除
zscore(name, value)
# 获取name对应有序集合中 value 对应的分数
zinterstore(dest, keys, aggregate=None)
# 获取两个有序集合的交集,如果遇到相同值不同分数,则按照aggregate进行操作 # aggregate的值为: SUM MIN MAX
zunionstore(dest, keys, aggregate=None)
# 获取两个有序集合的并集,如果遇到相同值不同分数,则按照aggregate进行操作 # aggregate的值为: SUM MIN MAX
zscan(name, cursor=0, match=None, count=None, scorecastfunc=float) zscan_iter(name, match=None, count=None,scorecastfunc=float)
# 同字符串相似,相较于字符串新增score_cast_func,用来对分数进行操作
-
9. redis的发布与订阅
基础类:
#!/usr/local/env python3
'''
Author:@南非波波
Blog:http://www.cnblogs.com/songqingbo/
E-mail:qingbo.song@gmail.com
'''
import redis
class RedisHelper:
def __init__(self):
self.__conn = redis.Redis(host='localhost',port=6379)
self.chan_sub = 'fm104.5'
self.chan_pub = 'fm104.5'
def public(self, msg):
self.__conn.publish(self.chan_pub, msg)
return True
def subscribe(self):
pub = self.__conn.pubsub()
pub.subscribe(self.chan_sub)
pub.parse_response()
return pub
redis_sub.py
#!/usr/local/env python3
'''
Author:@南非波波
Blog:http://www.cnblogs.com/songqingbo/
E-mail:qingbo.song@gmail.com
'''
from RedisHelper import RedisHelper
obj = RedisHelper()
redis_sub = obj.subscribe()
while True:
msg= redis_sub.parse_response()
print(msg)
redis_pub.py
#!/usr/local/env python3
'''
Author:@南非波波
Blog:http://www.cnblogs.com/songqingbo/
E-mail:qingbo.song@gmail.com
'''
from RedisHelper import RedisHelper
obj = RedisHelper()
obj.public('hello')
-
memcached
非持久化轻量级缓存,使用第三方工具可以实现数据的持久化存储
-
mongodb
天生的数据持久化,默认将数据持久化存储在本地磁盘。
四、消息队列rabbitmq
通信模式:
-
简单生产者消费者模型
rabbit_send
#!/usr/local/env python3 ''' Author:@南非波波 Blog:http://www.cnblogs.com/songqingbo/ E-mail:qingbo.song@gmail.com ''' import pika #与消息队列建立一个连接 connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost')) #创建一个管道 channel = connection.channel() #在管道中声明一个名称为'name'的队列 channel.queue_declare(queue='name') #一个消息不能直接发送给消息队列,需要通过一个路由器进行转发,这个路由器就是由exchange进行设置 channel.basic_publish(exchange='', #路由器 routing_key='name', #队列名称 body='swht') #消息 print(" [swht] Sent a message") connection.close()
rabbit_recive
#!/usr/local/env python3 ''' Author:@南非波波 Blog:http://www.cnblogs.com/songqingbo/ E-mail:qingbo.song@gmail.com ''' import pika #与消息队列服务器建立连接 connection = pika.BlockingConnection(pika.ConnectionParameters( 'localhost')) #创建一个管道 channel = connection.channel() #消费者声明一个队列,为了防止生产者还没有启动没有完成创建队列时代码出错的问题。如果队列已存在,则忽略该操作,否则则创建队列 channel.queue_declare(queue='name') def callback(ch, method, properties, body): print(" [x] Received %r" % body) channel.basic_consume(callback, queue='name', no_ack=True) #接收消息不进行确认 print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming()
-
消息持久化
channel.queue_declare(queue='name',durable=True)
已经存在的队列是不能再进行持久化设置的,所以在只有创建队列的时候设置持久化选项 bascack = (deliverytag= method.delivry_tag)
查看当前所有的queue XX
-
消息公平分发
只在消费者添加 channel.basicqos(prefetchcount=1)
示例代码:
rabbit_slb_send.py #!/usr/local/env python3 ''' Author:@南非波波 Blog:http://www.cnblogs.com/songqingbo/ E-mail:qingbo.song@gmail.com ''' import pika connection = pika.BlockingConnection(pika.ConnectionParameters( '192.168.137.6')) 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()
-
exchange路由
代码:
publisher.py #!/usr/local/env python3 ''' Author:@南非波波 Blog:http://www.cnblogs.com/songqingbo/ E-mail:qingbo.song@gmail.com ''' 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.py 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()