笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值,找寻数据的秘密,笔者认为,数据的价值不仅仅只体现在企业中,个人也可以体会到数据的魅力,用技术力量探索行为密码,让大数据助跑每一个人,欢迎直筒们关注我的公众号,大家一起讨论数据中的那些有趣的事情。
我的公众号为:livandata
到目前为止,我们已经学了网络并发编程的2个套路,多进程,多线程,这哥俩的优势和劣势都非常的明显,我们一起来回顾下
协程:在单线程下实现并发
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
Yield:多次调用同一个函数时,程序能记住上一次调用的位置,下次直接从上一次的位置继续执行。Yield会将数据存储在另一个地方,等下一次调用。
协程的好处:
· 无需线程上下文切换的开销
· 无需原子操作锁定及同步的开销
· "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
· 方便切换控制流,简化编程模型
· 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
Nigex:一个线程下可以实现上万个并发。
缺点:
· 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
· 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
使用yield实现协程操作例子
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 |
import time import queue def consumer(name): print("--->starting eating baozi...") while True: new_baozi = yield print("[%s] is eating baozi %s" %(name,new_baozi)) #time.sleep(1)
def producer():
r = con.__next__() r = con2.__next__() n = 0 while n < 5: n +=1 con.send(n) con2.send(n) print("\033[32;1m[producer]\033[0m is making baozi %s" %n )
if __name__ == '__main__': con = consumer("c1") con2 = consumer("c2") p = producer() |
看楼上的例子,我问你这算不算做是协程呢?你说,我他妈哪知道呀,你前面说了一堆废话,但是并没告诉我协程的标准形态呀,我腚眼一想,觉得你说也对,那好,我们先给协程一个标准定义,即符合什么条件就能称之为协程:
1. 必须在只有一个单线程里实现并发
2. 修改共享数据不需加锁
3. 用户程序里自己保存多个控制流的上下文栈
4. 一个协程遇到IO操作自动切换到其它协程
基于上面这4点定义,我们刚才用yield实现的程并不能算是合格的线程,因为它有一点功能没实现,哪一点呢?
Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
from greenletimport greenlet
# greenlet手动挡, gevent是自动挡
def test1():
print(12)
gr2.switch() # A 切换到test2()中从头开始运行
print(34)
gr2.switch() # C 切换到test1()中从上次运行的B点开始运行
def test2():
print(56)
gr1.switch() # B 切换到test1()中从上次运行的点(A)开始运行
print(78)
gr1 = greenlet(test1) #启动一个协程,
gr2 = greenlet(test2)
gr1.switch() #切换到test1()中从头开始运行
#运行的顺序为:12——56——34——78
感觉确实用着比generator还简单了呢,但好像还没有解决一个问题,就是遇到IO操作,自动切换,对不对?
百度如何安装gevent。
Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
import gevent
def foo():
print('\033[31;1m李闯在跟海涛搞...\033[0m')
gevent.sleep(2) # B 运行到此开始切换到bar。
print('\033[31;1m李闯又回去跟继续跟海涛搞...\033[0m')
def bar():
print('\033[32;1m李闯切换到了跟海龙搞...\033[0m')
gevent.sleep(1) # C 运行到此开始切换到foo
print('\033[32;1m李闯搞完了海涛,回来继续跟海龙搞...\033[0m')
def func3():
print("running func 3")
gevent.sleep(0) # D 从C的位置顺序执行,执行完成后再到B中,进行下一次循环。
print("running func 3 again")
gevent.joinall([
gevent.spawn(foo), # A 启动一个协程,首先运行foo
gevent.spawn(bar),
])
# 当运行到B时会跳到bar下,然后运行到C,然后从C跳到B,此时B还在sleep,
# 线程会再次的跳回到C,如此往复,直到其中一个停止sleep,程序才往下继续运行。
输出:
李闯在跟海涛搞...
李闯切换到了跟海龙搞...
李闯搞完了海涛,回来继续跟海龙搞...
李闯又回去跟继续跟海涛搞...
同步与异步的性能区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import gevent
def task(pid): """ Some non-deterministic task """ gevent.sleep(0.5) print('Task %s done' % pid)
def synchronous(): for i in range(1,10): task(i)
def asynchronous(): threads = [gevent.spawn(task, i) for i in range(10)] gevent.joinall(threads)
print('Synchronous:') synchronous()
print('Asynchronous:') asynchronous() |
上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在所有greenlet执行完后才会继续向下走。
遇到IO阻塞时会自动切换任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from gevent import monkey; monkey.patch_all() import gevent from urllib.request import urlopen
def f(url): print('GET: %s' % url) resp = urlopen(url) data = resp.read() print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([ gevent.spawn(f, 'https://www.python.org/'), gevent.spawn(f, 'https://www.yahoo.com/'), gevent.spawn(f, 'https://github.com/'), ]) |
用协程实现爬虫:
from gevent import monkey;
monkey.patch_all() #把当前程序的所有IO操作单独的做上标记,用来实现并行。
import gevent
from urllib.request import urlopen
def f(url):
print('GET: %s' % url)
resp = urlopen(url)
data = resp.read()
f = open("url.html", "wb")
f.write(data)
f.close()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
通过gevent实现单线程下的多socket并发:
server side:
import gevent
from gevent import socket,monkey
monkey.patch_all()
def server(port):
s = socket.socket()
s.bind(('0.0.0.0', port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli)
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print("recv:", data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == '__main__':
server(8001)
client side:
import socket
HOST = 'localhost' # The remote host
PORT = 8001 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>:"),encoding="utf8")
s.sendall(msg)
data = s.recv(1024)
print('Received', repr(data)) # repr:格式化输出。
s.close()
import socket
import threading
def sock_conn():
client =socket.socket()
client.connect(("localhost",8001))
count = 0
while True:
#msg =input(">>:").strip()
#if len(msg)== 0:continue
client.send( ("hello%s" %count).encode("utf-8"))
data =client.recv(1024)
print("[%s]recvfrom server:" % threading.get_ident(),data.decode()) #结果
count +=1
client.close()
for i in range(100):
t = threading.Thread(target=sock_conn)
t.start()
论事件驱动与异步IO
通常,我们写服务器处理模型的程序时,有以下几种模型:
(1)每收到一个请求,创建一个新的进程,来处理该请求;
(2)每收到一个请求,创建一个新的线程,来处理该请求;
(3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求
上面的几种方式,各有千秋,
第(1)中方法,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。
第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。
第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。
综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式
看图说话讲事件驱动模型
在UI编程中,常常要对鼠标点击进行响应,首先如何获得鼠标点击呢?
方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点:
1. CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?
2. 如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;
3. 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;
所以,该方式是非常不好的。
方式二:就是事件驱动模型
目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
1. 有一个事件(消息)队列;
2. 鼠标按下时,往这个队列中增加一个点击事件(消息);
3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
事件驱动模型:如果有鼠标事件、键盘事件等,程序会将事件放到队列中,然后由其他的线程处理,当事件处理完后建议执行一个回调函数,已确定任务执行完毕。
事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。
让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。
在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。
在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。
在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。
当我们面对如下的环境时,事件驱动模型通常是一个好的选择:
1. 程序中有许多任务,而且…
2. 任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且…
3. 在等待事件到来时,某些任务会阻塞。
当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。
网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。
此处要提出一个问题,就是,上面的事件驱动模型中,只要一遇到IO就注册一个事件,然后主程序就可以继续干其它的事情了,只到io处理完毕后,继续恢复之前中断的任务,这本质上是怎么实现的呢?哈哈,下面我们就来一起揭开这神秘的面纱。。。。
Select\Poll\Epoll异步IO
番外篇http://www.cnblogs.com/alex3714/articles/5876749.html
同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本文的上下文。
本文讨论的背景是Linux环境下的network IO。
一概念说明
在进行解释之前,首先要说明几个概念:
- 用户空间和内核空间
- 进程切换
- 进程的阻塞
- 文件描述符
- 缓存 I/O
用户空间与内核空间
现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间(操作系统运行时使用的空间),一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间(供各个进程使用)。
进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
1. 保存处理机上下文,包括程序计数器和其他寄存器。
2. 更新PCB信息。
3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
4. 选择另一个进程执行,并更新其PCB。
5. 更新内存管理的数据结构。
6. 恢复处理机上下文。
总而言之就是很耗资源,具体的可以参考这篇文章:进程切换
注:进程控制块(Processing Control Block),是操作系统核心中一种数据结构,主要表示进程状态。其作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位或与其它进程并发执行的进程。或者说,OS是根据PCB来对并发执行的进程进行控制和管理的。 PCB通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息
进程的阻塞
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
文件描述符fd
文件描述符(Filedescriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
缓存 I/O
缓存I/O 又被称作标准 I/O,大多数文件系统的默认I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
缓存I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核的地址空间中进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。
二 IO模式
刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblockingIO)
- I/O 多路复用( IO multiplexing)
- 信号驱动 I/O( signaldriven IO)
- 异步 I/O(asynchronousIO)
注:由于signaldriven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。
阻塞 I/O(blockingIO)
在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blockingIO的特点就是在IO执行的两个阶段都被block了。
非阻塞 I/O(nonblockingIO)
linux下,可以通过设置socket使其变为non-blocking。当对一个non-blockingsocket执行读操作时,流程是这个样子:
当用户进程在nonblocking情况下发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,nonblockingIO的特点是用户进程需要不断的主动询问kernel数据好了没有。
I/O 多路复用( IO multiplexing):操作多个socket,任何一个有数据socket就会返回。
Select中包含多个socket,然后由系统进行监控,只要有一个socket有返回值,select就会返回。
IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event drivenIO。select/epoll的好处就在于单个进程就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
所以,I/O多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。
这个图和blockingIO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和recvfrom),而blocking IO只调用了一个systemcall (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
所以,如果处理的连接数不是很高的话,使用select/epoll的webserver不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
在IOmultiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
异步 I/O(asynchronousIO)
linux下的asynchronousIO其实用得很少。先看一下它的流程:
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronousread之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
总结
Blocking(阻塞)和non-blocking(非阻塞)的区别
调用blockingIO会一直block住对应的进程直到操作完成,而non-blockingIO在kernel还准备数据的情况下会立刻返回。
synchronous IO(多路复用同步)和asynchronous IO(异步)的区别
在说明synchronousIO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:
- A synchronous I/O operation causes the requesting process to be blocked untilthat I/O operation completes;
- An asynchronous I/O operation does not cause the requesting process to beblocked;
两者的区别就在于synchronousIO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blockingIO,IO multiplexing都属于synchronousIO。
有人会说,non-blockingIO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IOoperation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blockingIO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。
而asynchronousIO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。
各个IOModel的比较如图所示:
通过上面的图片,可以发现non-blocking IO和asynchronousIO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。
三 I/O 多路复用之select、poll、epoll详解
select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。(这里啰嗦下)
select:
1 |
select(rlist, wlist, xlist, timeout=None) |
select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
poll
1 |
intpoll (struct pollfd *fds, unsigned intnfds, inttimeout); |
不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。
structpollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
epoll
epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
一epoll操作过程
epoll操作过程需要三个接口,分别如下:
1 2 3 |
intepoll_create(intsize);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 intepoll_ctl(intepfd, intop, intfd, structepoll_event *event); intepoll_wait(intepfd, structepoll_event * events, intmaxevents, inttimeout); |
1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。
当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2. int epoll_ctl(int epfd, int op, int fd, structepoll_event *event);
函数是对指定描述符fd执行op操作。
- epfd:是epoll_create()的返回值。
- op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
- fd:是需要监听的fd(文件描述符)
- epoll_event:是告诉内核需要监听什么事
3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待epfd上的io事件,最多返回maxevents个事件。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
#_*_coding:utf-8_*_
__author__= 'Alex Li'
importsocket, logging
importselect, errno
logger = logging.getLogger("network-server")
defInitLog():
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler("network-server.log")
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
formatter = logging.Formatter("%(asctime)s- %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
if__name__ == "__main__":
InitLog()
try:
#创建 TCP socket 作为监听 socket
listen_fd =socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
except socket.error as msg:
logger.error("createsocket failed")
try:
#设置 SO_REUSEADDR 选项
listen_fd.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
except socket.error as msg:
logger.error("setsocketoptSO_REUSEADDR failed")
try:
#进行 bind -- 此处未指定 ip 地址,即bind 了全部网卡 ip 上
listen_fd.bind(('', 2003))
except socket.error as msg:
logger.error("bindfailed")
try:
#设置 listen 的 backlog 数
listen_fd.listen(10)
except socket.error as msg:
logger.error(msg)
try:
#创建 epoll 句柄
epoll_fd = select.epoll()
#向 epoll 句柄中注册监听 socket 的可读事件
epoll_fd.register(listen_fd.fileno(),select.EPOLLIN)
except select.error as msg:
logger.error(msg)
connections = {}
addresses = {}
datalist = {}
while True:
#epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
epoll_list = epoll_fd.poll()
for fd, events in epoll_list:
#若为监听 fd 被激活
if fd == listen_fd.fileno():
#进行 accept -- 获得连接上来 client 的 ip 和 port,以及 socket 句柄
conn, addr = listen_fd.accept()
logger.debug("accept connection from %s, %d, fd = %d" % (addr[0], addr[1], conn.fileno()))
#将连接 socket 设置为非阻塞
conn.setblocking(0)
#向 epoll 句柄中注册连接 socket 的可读事件
epoll_fd.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
#将 conn 和 addr 信息分别保存起来
connections[conn.fileno()] =conn
addresses[conn.fileno()] = addr
elif select.EPOLLIN & events:
#有可读事件激活
datas = ''
while True:
try:
# 从激活 fd 上 recv 10 字节数据
data =connections[fd].recv(10)
# 若当前没有接收到数据,并且之前的累计数据也没有
ifnot data andnot datas:
# 从 epoll 句柄中移除该连接 fd
epoll_fd.unregister(fd)
# server 侧主动关闭该连接 fd
connections[fd].close()
logger.debug("%s, %d closed"% (addresses[fd][0], addresses[fd][1]))
break
else:
# 将接收到的数据拼接保存在 datas 中
datas += data
except socket.error as msg:
# 在非阻塞 socket 上进行 recv 需要处理读穿的情况
# 这里实际上是利用读穿出异常的方式跳到这里进行后续处理
if msg.errno == errno.EAGAIN:
logger.debug("%s receive %s"% (fd, datas))
# 将已接收数据保存起来
datalist[fd] =datas
# 更新 epoll 句柄中连接d 注册事件为可写
epoll_fd.modify(fd,select.EPOLLET | select.EPOLLOUT)
break
else:
# 出错处理
epoll_fd.unregister(fd)
connections[fd].close()
logger.error(msg)
break
elif select.EPOLLHUP & events:
#有 HUP 事件激活
epoll_fd.unregister(fd)
connections[fd].close()
logger.debug("%s, %d closed"% (addresses[fd][0], addresses[fd][1]))
elif select.EPOLLOUT & events:
#有可写事件激活
sendLen = 0
#通过 while 循环确保将 buf 中的数据全部发送出去
while True:
#将之前收到的数据发回 client -- 通过 sendLen 来控制发送位置
sendLen +=connections[fd].send(datalist[fd][sendLen:])
#在全部发送完毕后退出 while 循环
if sendLen == len(datalist[fd]):
break
#更新 epoll 句柄中连接 fd 注册事件为可读
epoll_fd.modify(fd,select.EPOLLIN | select.EPOLLET)
else:
#其他 epoll 事件不进行处理
continue
epoll socketecho server
参考:
https://segmentfault.com/a/1190000003063859
https://my.oschina.net/moooofly/blog/147297
番外2:
http://www.cnblogs.com/alex3714/p/4372426.html:
首先列一下,sellect、poll、epoll三者的区别
select
select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
poll
poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(LevelTriggered)。
epoll
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(EdgeTriggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
Python select :
Python的select()方法直接调用操作系统的IO接口,它监控sockets,open files, and pipes(所有带fileno()方法的文件句柄)何时变成readable和writeable, 或者通信错误,select()使得同时监控多个连接变的简单,并且这比写一个长循环来等待和监控多客户端连接要高效,因为select直接通过操作系统提供的C的网络接口进行操作,而不是通过Python的解释器。
常规server的案例:
#!/usr/bin/env python
# _*_ UTF-8 _*_
import select
import socket
import queue
server = socket.socket()
server.bind(('localhost', 9000))
server.listen(1000)
server.setblocking(False) #不阻塞
msg_dic = {}
inputs = [server,] #想要检测的连接
outputs = []
readable, writeable, exceptional = select.select(inputs, outputs, inputs)
#select用来将数据交给内核,inputs为想要让内核帮忙检测的连接;outputs是???。
while True:
for r in readable: #readable是内核返回的数据。
if r is server: #来了一个新链接,则创建新链接conn,如果返回的是conn,则接受数据。。
conn, addr =server.accept() #accept是指从客户端发来的连接。
print(conn, addr)
inputs.append(conn) #因为这个新建立的连接还没有发数据过
#来,现在就接收的话程序就报错了。所
#以想要实现这个客户端发数据来时server端
#能知道就需要让select在检测这个conn,
msg_dic[conn]= queue.Queue() #初始化一个队列,后面存要返回给客户端的数据。
else:
data = r.recv(1024) #接收来自内核的数据。
print("收到数据", data)
#r.send(data) #返回到客户端。
#将返回的值放在队列中。
msg_dic[r].put(data)
#实现下次循环时发送数据到客户端,即前面select中的outputs,outputs的意思为:
#你把什么东西放到outputs中,下次循环outputs就会返回哪个值。
outputs.append(r)#放入返回的链接队列中。
for w in writeable:#要返回给客户端的连接列表。
data_to_client =msg_dic[w].get()
w.send(data_to_client) #返回给客户端的源数据。
outputs.remove(w) #确保下次循环的时候writeable不返回旧的连接。
for e in exceptional:
if e in outputs:
outputs.remove(e)
inputs.remove(e)
del msg_dic[e]
server.accept()
此时客户端对应的应该接收数据。
注意:Using Python’s file objectswith select() worksfor Unix, but is not supported under Windows.
接下来通过echoserver例子要以了解select 是如何通过单进程实现同时处理多个非阻塞的socket连接的
import select
import socket
import sys
import Queue
# Create a TCP/IP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)
# Bind the socket to the port
server_address = ('localhost', 10000)
print >>sys.stderr, 'startingup on %s port %s' % server_address
server.bind(server_address)
# Listen for incoming connections
server.listen(5)
select()方法接收并监控3个通信列表,第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接收所有要发出去的data(outgoing data),第3个监控错误信息,接下来我们需要创建2个列表来包含输入和输出信息来传给select().
# Sockets from which we expect to read
inputs = [ server ]
# Sockets to which we expect to write
outputs = [ ]
所有客户端的进来的连接和数据将会被server的主循环程序放在上面的list中处理,我们现在的server端需要等待连接可写(writable)之后才能过来,然后接收数据并返回(因此不是在接收到数据之后就立刻返回),因为每个连接要把输入或输出的数据先缓存到queue里,然后再由select取出来再发出去。
Connections are added to and removed fromthese lists by the server main loop. Since this version of the server is goingto wait for a socket to become writable before sending any data (instead ofimmediately sending the reply), each output connection needs a queue to act asa buffer for the data to be sent through it.
# Outgoing message queues (socket:Queue)
message_queues = {}
Themain portion of the server program loops, calling select() toblock and wait for network activity.
下面是此程序的主循环,调用select()时会阻塞和等待直到新的连接和数据进来
while inputs:
# Wait for at least one of the sockets to be ready for processing
print >>sys.stderr, '\nwaiting for the next event'
readable, writable, exceptional = select.select(inputs, outputs, inputs)
当你把inputs,outputs,exceptional(这里跟inputs共用)传给select()后,它返回3个新的list,我们上面将他们分别赋值为readable,writable,exceptional,所有在readable list中的socket连接代表有数据可接收(recv),所有在writable list中的存放着你可以对其进行发送(send)操作的socket连接,当连接通信出现error时会把error写到exceptional列表中。
select() returnsthree new lists, containing subsets of the contents of the lists passed in. Allof the sockets in the readable list have incomingdata buffered and available to be read. All of the sockets in the writable listhave free space in their buffer and can be written to. The sockets returnedin exceptional havehad an error (the actual definition of “exceptional condition” depends on theplatform).
Readable list 中的socket 可以有3种可能状态,第一种是如果这个socket是main "server" socket,它负责监听客户端的连接,如果这个mainserver socket出现在readable里,那代表这是server端已经ready来接收一个新的连接进来了,为了让这个mainserver能同时处理多个连接,在下面的代码里,我们把这个mainserver的socket设置为非阻塞模式。
The “readable” sockets represent threepossible cases. If the socket is the main “server” socket, the one being usedto listen for connections, then the “readable” condition means it is ready toaccept another incoming connection. In addition to adding the new connection tothe list of inputs to monitor, this section sets the client socket to notblock.
# Handle inputs
for s in readable:
if s is server:
# A"readable" server socket is ready to accept a connection
connection, client_address= s.accept()
print >>sys.stderr, 'newconnection from', client_address
connection.setblocking(0)
inputs.append(connection)
# Give the connectiona queue for data we want to send
message_queues[connection]= Queue.Queue()
第二种情况是这个socket是已经建立了的连接,它把数据发了过来,这个时候你就可以通过recv()来接收它发过来的数据,然后把接收到的数据放到queue里,这样你就可以把接收到的数据再传回给客户端了。
The next case is an established connection with a client that has sent data. The data is read with recv(), then placed on the queue so it can be sent through the socket and back to the client. else:
data = s.recv(1024)
if data:
# A readable client socket has data
print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername())
message_queues[s].put(data)
# Add output channel for response
if s not in outputs:
outputs.append(s)
第三种情况就是这个客户端已经断开了,所以你再通过recv()接收到的数据就为空了,所以这个时候你就可以把这个跟客户端的连接关闭了。
Areadable socket without data available is from aclient that has disconnected, and the stream is ready to be closed.
else:
# Interpret empty result as closed connection
print >>sys.stderr, 'closing', client_address, 'after reading no data'
# Stop listening for input on the connection
if s in outputs:
outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
inputs.remove(s) #inputs中也删除掉
s.close() #把这个连接关闭掉
# Remove message queue
del message_queues[s]
对于writablelist中的socket,也有几种状态,如果这个客户端连接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,否则就把这个连接从outputlist中移除,这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态
Thereare fewer cases for the writable connections. If there is data in the queue fora connection, the next message is sent. Otherwise, the connection is removedfrom the list of output connections so that the next time through theloop select() doesnot indicate that the socket is ready to send data.
# Handle outputs
for s in writable:
try:
next_msg = message_queues[s].get_nowait()
except Queue.Empty:
# No messages waiting so stop checking for writability.
print >>sys.stderr, 'output queue for', s.getpeername(), 'is empty'
outputs.remove(s)
else:
print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername())
s.send(next_msg)
最后,如果在跟某个socket连接通信过程中出了错误,就把这个连接对象在inputs\outputs\message_queue中都删除,再把连接关闭掉:
# Handle "exceptional conditions"
for s in exceptional:
print >>sys.stderr, 'handling exceptional condition for', s.getpeername()
# Stop listening for input on the connection
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
# Remove message queue
del message_queues[s]
客户端
下面的这个是客户端程序展示了如何通过select()对socket进行管理并与多个连接同时进行交互,
Theexample client program uses two sockets to demonstrate how the serverwith select() managesmultiple connections at the same time. The client starts by connecting eachTCP/IP socket to the server.
import socket
import sys
messages = [ 'This is the message. ',
'It will be sent ',
'in parts.',
]
server_address = ('localhost', 10000)
# Create a TCP/IP socket
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
]
# Connect the socket to the port where the server is listening
print >>sys.stderr, 'connecting to %s port %s' % server_address
for s in socks:
s.connect(server_address)
接下来通过循环通过每个socket连接给server发送和接收数据。
Then it sends one pieces of the message ata time via each socket, and reads all responses available after writing newdata.
for message in messages:
# Send messages on both sockets
for s in socks:
print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message)
s.send(message)
# Read responses on both sockets
for s in socks:
data = s.recv(1024)
print >>sys.stderr, '%s: received "%s"' % (s.getsockname(), data)
if not data:
print >>sys.stderr, 'closing socket', s.getsockname()
最后服务器端的完整代码如下:
#_*_coding:utf-8_*_
__author__ = 'Alex Li'
import select
import socket
import sys
import queue
# Create a TCP/IP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
# Bind the socket to the port
server_address = ('localhost', 10000)
print(sys.stderr, 'starting up on %s port %s' % server_address)
server.bind(server_address)
# Listen for incoming connections
server.listen(5)
# Sockets from which we expect to read
inputs = [ server ]
# Sockets to which we expect to write
outputs = [ ]
message_queues = {}
while inputs:
# Wait for at least one of the sockets to be ready for processing
print( '\nwaiting for the next event')
readable, writable, exceptional = select.select(inputs, outputs, inputs)
# Handle inputs
for s in readable:
if s is server:
# A "readable" server socket is ready to accept a connection
connection, client_address = s.accept()
print('new connection from', client_address)
connection.setblocking(False)
inputs.append(connection)
# Give the connection a queue for data we want to send
message_queues[connection] = queue.Queue()
else:
data = s.recv(1024)
if data:
# A readable client socket has data
print(sys.stderr, 'received "%s" from %s' % (data, s.getpeername()) )
message_queues[s].put(data)
# Add output channel for response
if s not in outputs:
outputs.append(s)
else:
# Interpret empty result as closed connection
print('closing', client_address, 'after reading no data')
# Stop listening for input on the connection
if s in outputs:
outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
inputs.remove(s) #inputs中也删除掉
s.close() #把这个连接关闭掉
# Remove message queue
del message_queues[s]
# Handle outputs
for s in writable:
try:
next_msg = message_queues[s].get_nowait()
except queue.Empty:
# No messages waiting so stop checking for writability.
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)
# Handle "exceptional conditions"
for s in exceptional:
print('handling exceptional condition for', s.getpeername() )
# Stop listening for input on the connection
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
# Remove message queue
del message_queues[s]
__author__ = 'jieli'
import socket
import sys
messages = [ 'This is the message. ',
'It will be sent ',
'in parts.',
]
server_address = ('localhost', 10000)
# Create a TCP/IP socket
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
]
# Connect the socket to the port where the server is listening
print >>sys.stderr, 'connecting to %s port %s' % server_address
for s in socks:
s.connect(server_address)
for message in messages:
# Send messages on both sockets
for s in socks:
print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message)
s.send(message)
# Read responses on both sockets
for s in socks:
data = s.recv(1024)
print >>sys.stderr, '%s: received "%s"' % (s.getsockname(), data)
if not data:
print >>sys.stderr, 'closing socket', s.getsockname()
s.close()
Run the server in one window and the clientin another. The output will look like this, with different port numbers.
$ python ./select_echo_server.py
starting up on localhost port 10000
waiting for the next event
new connection from ('127.0.0.1', 55821)
waiting for the next event
new connection from ('127.0.0.1', 55822)
received "This is the message. " from ('127.0.0.1', 55821)
waiting for the next event
sending "This is the message. " to ('127.0.0.1', 55821)
waiting for the next event
output queue for ('127.0.0.1', 55821) is empty
waiting for the next event
received "This is the message. " from ('127.0.0.1', 55822)
waiting for the next event
sending "This is the message. " to ('127.0.0.1', 55822)
waiting for the next event
output queue for ('127.0.0.1', 55822) is empty
waiting for the next event
received "It will be sent " from ('127.0.0.1', 55821)
received "It will be sent " from ('127.0.0.1', 55822)
waiting for the next event
sending "It will be sent " to ('127.0.0.1', 55821)
sending "It will be sent " to ('127.0.0.1', 55822)
waiting for the next event
output queue for ('127.0.0.1', 55821) is empty
output queue for ('127.0.0.1', 55822) is empty
waiting for the next event
received "in parts." from ('127.0.0.1', 55821)
received "in parts." from ('127.0.0.1', 55822)
waiting for the next event
sending "in parts." to ('127.0.0.1', 55821)
sending "in parts." to ('127.0.0.1', 55822)
waiting for the next event
output queue for ('127.0.0.1', 55821) is empty
output queue for ('127.0.0.1', 55822) is empty
waiting for the next event
closing ('127.0.0.1', 55822) after reading no data
closing ('127.0.0.1', 55822) after reading no data
waiting for the next event
The client output shows the data being sentand received using both sockets.
$ python ./select_echo_multiclient.py
connecting to localhost port 10000
('127.0.0.1', 55821): sending "This is the message. "
('127.0.0.1', 55822): sending "This is the message. "
('127.0.0.1', 55821): received "This is the message. "
('127.0.0.1', 55822): received "This is the message. "
('127.0.0.1', 55821): sending "It will be sent "
('127.0.0.1', 55822): sending "It will be sent "
('127.0.0.1', 55821): received "It will be sent "
('127.0.0.1', 55822): received "It will be sent "
('127.0.0.1', 55821): sending "in parts."
('127.0.0.1', 55822): sending "in parts."
('127.0.0.1', 55821): received "in parts."
('127.0.0.1', 55822): received "in parts."
在实现事件驱动的情况下,如何实现异步IO的切换。
select 多并发socket 例子:
#_*_coding:utf-8_*_
__author__ = 'Alex Li'
import select
import socket
import sys
import queue
server = socket.socket()
server.setblocking(0)
server_addr = ('localhost',10000)
print('starting up on %s port %s' % server_addr)
server.bind(server_addr)
server.listen(5)
inputs = [server, ] #自己也要监测呀,因为server本身也是个fd
outputs = []
message_queues = {}
while True:
print("waiting for next event...")
readable, writeable, exeptional = select.select(inputs,outputs,inputs) #如果没有任何fd就绪,那程序就会一直阻塞在这里
for s in readable: #每个s就是一个socket
if s is server: #别忘记,上面我们server自己也当做一个fd放在了inputs列表里,传给了select,如果这个s是server,代表server这个fd就绪了,
#就是有活动了, 什么情况下它才有活动? 当然 是有新连接进来的时候 呀
#新连接进来了,接受这个连接
conn, client_addr = s.accept()
print("new connection from",client_addr)
conn.setblocking(0)
inputs.append(conn) #为了不阻塞整个程序,我们不会立刻在这里开始接收客户端发来的数据, 把它放到inputs里, 下一次loop时,这个新连接
#就会被交给select去监听,如果这个连接的客户端发来了数据 ,那这个连接的fd在server端就会变成就续的,select就会把这个连接返回,返回到
#readable 列表里,然后你就可以loop readable列表,取出这个连接,开始接收数据了, 下面就是这么干 的
message_queues[conn] = queue.Queue() #接收到客户端的数据后,不立刻返回 ,暂存在队列里,以后发送
else: #s不是server的话,那就只能是一个 与客户端建立的连接的fd了
#客户端的数据过来了,在这接收
data = s.recv(1024)
if data:
print("收到来自[%s]的数据:" % s.getpeername()[0], data)
message_queues[s].put(data) #收到的数据先放到queue里,一会返回给客户端
if s not in outputs:
outputs.append(s) #为了不影响处理与其它客户端的连接 , 这里不立刻返回数据给客户端
else:#如果收不到data代表什么呢? 代表客户端断开了呀
print("客户端断开了",s)
if s in outputs:
outputs.remove(s) #清理已断开的连接
inputs.remove(s) #清理已断开的连接
del message_queues[s] ##清理已断开的连接
for s in writeable:
try :
next_msg = message_queues[s].get_nowait()
except queue.Empty:
print("client [%s]" %s.getpeername()[0], "queue is empty..")
outputs.remove(s)
else:
print("sending msg to [%s]"%s.getpeername()[0], next_msg)
s.send(next_msg.upper())
for s in exeptional:
print("handling exception for ",s.getpeername())
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
del message_queues[s]
#_*_coding:utf-8_*_
__author__ = 'Alex Li'
import socket
import sys
messages = [ b'This is the message. ',
b'It will be sent ',
b'in parts.',
]
server_address = ('localhost', 10000)
# Create a TCP/IP socket
socks = [socket.socket(socket.AF_INET, socket.SOCK_STREAM),
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
]
# Connect the socket to the port where the server is listening
print('connecting to %s port %s' % server_address)
for s in socks:
s.connect(server_address)
for message in messages:
# Send messages on both sockets
for s in socks:
print('%s: sending "%s"' % (s.getsockname(), message) )
s.send(message)
# Read responses on both sockets
for s in socks:
data = s.recv(1024)
print( '%s: received "%s"' % (s.getsockname(), data) )
if not data:
print(sys.stderr, 'closing socket', s.getsockname() )
selectors模块
This module allows high-level and efficient I/O multiplexing,built upon the select module primitives. Users are encouraged to use this moduleinstead, unless they want precise control over the OS-level primitives used.
import selectors
import socket
sel = selectors.DefaultSelector()
def accept(sock, mask):
conn, addr = sock.accept() # Should be ready
print('accepted', conn, 'from', addr)
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read)
#新链接注册read回调函数。
def read(conn, mask):
data = conn.recv(1024) # Should be ready
if data:
print('echoing', repr(data), 'to', conn)
conn.send(data) # Hope it won't block
else:
print('closing', conn)
sel.unregister(conn)
conn.close()
sock = socket.socket()
sock.bind(('localhost', 10000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
while True:
events = sel.select() #默认是阻塞,有活动的链接列表就往下走。
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
数据库操作与Paramiko模块
http://www.cnblogs.com/wupeiqi/articles/5095821.html
数据库操作
Python 操作Mysql 模块的安装
linux:
yum install MySQL-python
window:
http://files.cnblogs.com/files/wupeiqi/py-mysql-win.zip
SQL基本使用
1、数据库操作
show databases;
use [databasename];
create database [name];
2、数据表操作
show tables;
create table students
(
id int not null auto_increment primary key,
name char(8) not null,
sex char(4) not null,
age tinyint unsigned not null,
tel char(13) null default"-"
);
CREATE TABLE `wb_blog` (
`id` smallint(8)unsigned NOT NULL,
`catid` smallint(5)unsigned NOT NULL DEFAULT '0',
`title` varchar(80)NOT NULL DEFAULT '',
`content` text NOTNULL,
PRIMARY KEY (`id`),
UNIQUE KEY`catename` (`catid`)
) ;
3、数据操作
insert into students(name,sex,age,tel)values('alex','man',18,'151515151')
delete from students where id =2;
update students set name = 'sb' where id=1;
select * from students
4、其他
主键
外键
左右连接
PythonMySQL API
一、插入数据
importMySQLdb
conn= MySQLdb.connect(host='127.0.0.1',user='root',passwd='1234',db='mydb')
cur =conn.cursor()
reCount= cur.execute('insert into UserInfo(Name,Address)values(%s,%s)',('alex','usa'))
#reCount = cur.execute('insert into UserInfo(Name,Address) values(%(id)s,%(name)s)',{'id':12345,'name':'wupeiqi'})
conn.commit()
cur.close()
conn.close()
printreCount
import MySQLdb
conn = MySQLdb.connect(host='127.0.0.1',user='root',passwd='1234',db='mydb')
cur = conn.cursor()
li =[
('alex','usa'),
('sb','usa'),
]
reCount = cur.executemany('insert intoUserInfo(Name,Address) values(%s,%s)',li)
conn.commit()
cur.close()
conn.close()
print reCount
注意:cur.lastrowid
二、删除数据
importMySQLdb
conn= MySQLdb.connect(host='127.0.0.1',user='root',passwd='1234',db='mydb')
cur =conn.cursor()
reCount= cur.execute('delete from UserInfo')
conn.commit()
cur.close()
conn.close()
printreCount
三、修改数据
importMySQLdb
conn= MySQLdb.connect(host='127.0.0.1',user='root',passwd='1234',db='mydb')
cur =conn.cursor()
reCount= cur.execute('update UserInfo set Name = %s',('alin',))
conn.commit()
cur.close()
conn.close()
printreCount
四、查数据
############################### fetchone/fetchmany(num) ##############################
importMySQLdb
conn= MySQLdb.connect(host='127.0.0.1',user='root',passwd='1234',db='mydb')
cur =conn.cursor()
reCount= cur.execute('select * from UserInfo')
printcur.fetchone()
printcur.fetchone()
cur.scroll(-1,mode='relative')
printcur.fetchone()
printcur.fetchone()
cur.scroll(0,mode='absolute')
printcur.fetchone()
printcur.fetchone()
cur.close()
conn.close()
printreCount
############################### fetchall ##############################
importMySQLdb
conn= MySQLdb.connect(host='127.0.0.1',user='root',passwd='1234',db='mydb')
#cur= conn.cursor(cursorclass = MySQLdb.cursors.DictCursor)
cur =conn.cursor()
reCount= cur.execute('select Name,Address from UserInfo')
nRet= cur.fetchall()
cur.close()
conn.close()
printreCount
printnRet
for iin nRet:
print i[0],i[1]
RabbitMQ队列
安装 http://www.rabbitmq.com/install-standalone-mac.html
安装python rabbitMQmodule
1 2 3 4 5 6 7 |
pip install pika or easy_install pika or 源码
https://pypi.python.org/pypi/pika |
实现最简单的队列通信
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 25 26 27 |
#_*_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() |
在这种模式下,RabbitMQ会默认把p发的消息依次分发给各个消费者(c),跟负载均衡差不多。
消息提供者代码:
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 |
import pika import time 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! %s" %time.time() 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 22 23 24 |
#_*_coding:utf-8_*_
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(20) print(" [x] Done") print("method.delivery_tag",method.delivery_tag) ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(callback, queue='task_queue', no_ack=True )
print(' [*] Waiting for messages. To exit press CTRL+C') channel.start_consuming() |
此时,先启动消息生产者,然后再分别启动3个消费者,通过生产者多发送几条消息,你会发现,这几条消息会被依次分配到各个消费者身上
Doing a task can take a few seconds. You may wonder what happensif one of the consumers starts a long task and dies with it only partly done.With our current code once RabbitMQ delivers message to the customer itimmediately removes it from memory. In this case, if you kill a worker we willlose the message it was just processing. We'll also lose all the messages thatwere dispatched to this particular worker but were not yet handled.
But we don't want to lose any tasks. If a worker dies, we'd likethe task to be delivered to another worker.
In order to make sure a message is never lost, RabbitMQ supportsmessage acknowledgments. An ack(nowledgement) is sent back from theconsumer to tell RabbitMQ that a particular message had been received,processed and that RabbitMQ is free to delete it.
If a consumer dies (its channel is closed, connection is closed,or TCP connection is lost) without sending an ack, RabbitMQ will understandthat a message wasn't processed fully and will re-queue it. If there are otherconsumers online at the same time, it will then quickly redeliver it to anotherconsumer. That way you can be sure that no message is lost, even if the workersoccasionally die.
There aren't any message timeouts; RabbitMQ will redeliver themessage when the consumer dies. It's fine even if processing a message takes avery, very long time.
Message acknowledgments are turned on by default. In previousexamples we explicitly turned them off via the no_ack=True flag. It'stime to remove this flag and send a proper acknowledgment from the worker, oncewe're done with a task.
1 2 3 4 5 6 7 8 |
def callback(ch, method, properties, body): print " [x] Received %r" % (body,) time.sleep( body.count('.') ) print " [x] Done" ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(callback, queue='hello') |
Using this code we canbe sure that even if you kill a worker using CTRL+C while it was processing amessage, nothing will be lost. Soon after the worker dies all unacknowledgedmessages will be redelivered
消息持久化
We have learned how to make sure that even if the consumer dies,the task isn't lost(by default, if wanna disable use no_ack=True). Butour tasks will still be lost if RabbitMQ server stops.
When RabbitMQ quits or crashes it will forget the queues andmessages unless you tell it not to. Two things are required to make sure thatmessages aren't lost: we need to mark both the queue and messages as durable.
First, we need to make sure that RabbitMQ will never lose ourqueue. In order to do so, we need to declare it as durable:
1 |
channel.queue_declare(queue='hello', durable=True) |
Although this command is correct by itself, it won't work in oursetup. That's because we've already defined a queuecalled hello which is not durable. RabbitMQ doesn't allow you toredefine an existing queue with different parameters and will return an errorto any program that tries to do that. But there is a quick workaround - let'sdeclare a queue with different name, for exampletask_queue:
1 |
channel.queue_declare(queue='task_queue', durable=True) |
This queue_declare change needs to be applied to boththe producer and consumer code.
At that point we're sure that the task_queue queuewon't be lost even if RabbitMQ restarts. Now we need to mark our messages aspersistent - by supplying a delivery_mode property with avalue 2.
1 2 3 4 5 6 7 |
channel.basic_publish(exchange='', routing_key="task_queue", body=message, properties=pika.BasicProperties( delivery_mode = 2, # make message persistent )) |
消息公平分发
如果Rabbit只管按顺序把消息发到各个消费者身上,不考虑消费者负载的话,很可能出现,一个机器配置不高的消费者那里堆积了很多消息处理不完,同时配置高的消费者却一直很轻松。为解决此问题,可以在各个消费者端,配置perfetch=1,意思就是告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了。
1 |
channel.basic_qos(prefetch_count=1) |
带消息持久化+公平分发的完整代码
生产者端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/usr/bin/env python import pika import sys
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True)
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 22 |
#!/usr/bin/env python import pika import time
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel()
channel.queue_declare(queue='task_queue', durable=True) print(' [*] Waiting for messages. To exit press CTRL+C')
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_qos(prefetch_count=1) channel.basic_consume(callback, queue='task_queue')
channel.start_consuming() |
Publish\Subscribe(消息发布\订阅)
之前的例子都基本都是1对1的消息发送和接收,即消息只能发送到指定的queue里,但有些时候你想让你的消息被所有的Queue收到,类似广播的效果,这时候就要用到exchange了,
An exchange is a very simple thing. On one side it receivesmessages from producers and the other side it pushes them to queues. The exchangemust know exactly what to do with a message it receives. Should it be appendedto a particular queue? Should it be appended to many queues? Or should it getdiscarded. The rules for that are defined by the exchange type.
Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息
fanout: 所有bind到此exchange的queue都可以接收消息
direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息
表达式符号说明:#代表一个或多个字符,*代表任何字符
例:#.a会匹配a.a,aa.a,aaa.a等
*.a会匹配a.a,b.a,c.a等
注:使用RoutingKey为#,ExchangeType为topic的时候相当于使用fanout
headers: 通过headers 来决定把消息发给哪些queue
消息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() |
有选择的接收消息(exchange type=direct)
RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据关键字判定应该将数据发送至指定队列。
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() |
更细致的消息过滤
Although using the direct exchange improved oursystem, it still has limitations - it can't do routing based on multiplecriteria.
In our logging system we might want to subscribe to not onlylogs based on severity, but also based on the source which emitted the log. Youmight know this concept from the syslog unix tool, whichroutes logs based on both severity (info/warn/crit...) and facility(auth/cron/kern...).
That would give us a lot of flexibility - we may want to listento just critical errors coming from 'cron' but also all logs from 'kern'.
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 |
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() |
To receive all the logs run:
python receive_logs_topic.py "#"
To receive all logs from the facility "kern":
python receive_logs_topic.py "kern.*"
Or if you want to hear only about "critical" logs:
python receive_logs_topic.py "*.critical"
You can create multiple bindings:
python receive_logs_topic.py "kern.*""*.critical"
And to emit a log with a routing key "kern.critical"type:
pythonemit_log_topic.py "kern.critical" "A critical kernel error"
Remote procedure call (RPC)
To illustrate how an RPC service could be used we're going tocreate a simple client class. It's going to expose a method named call whichsends an RPC request and blocks until the answer is received:
1 2 3 |
fibonacci_rpc = FibonacciRpcClient() result = fibonacci_rpc.call(4) print("fib(4) is %r" % result) |
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( 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) |
Memcached & Redis使用
memcached
http://www.cnblogs.com/wupeiqi/articles/5132791.html
Memcached
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。
Memcached安装和基本使用
Memcached安装:
1 2 3 4 5 6 7 8 |
wget http://memcached.org/latest tar -zxvf memcached-1.x.x.tar.gz cd memcached-1.x.x ./configure && make && make test && sudo make install
PS:依赖libevent yum install libevent-devel apt-get install libevent-dev |
启动Memcached
1 2 3 4 5 6 7 8 9 10 |
memcached -d -m 10 -u root -l 10.211.55.4 -p 12000 -c 256 -P /tmp/memcached.pid
参数说明: -d 是启动一个守护进程 -m 是分配给Memcache使用的内存数量,单位是MB -u 是运行Memcache的用户 -l 是监听的服务器IP地址 -p 是设置Memcache监听的端口,最好是1024以上的端口 -c 选项是最大运行的并发连接数,默认是1024, 按照你服务器的负载量来设定 -P 是设置保存Memcache的pid文件 |
Memcached命令
1 2 3 |
存储命令: set/add/replace/append/prepend/cas 获取命令: get/gets 其他命令: delete/stats.. |
Python操作Memcached
安装API
1 2 |
python操作Memcached使用Python-memcached模块 下载安装:https://pypi.python.org/pypi/python-memcached |
1、第一次操作
1 2 3 4 5 6 |
import memcache
mc = memcache.Client(['10.211.55.4:12000'], debug=True) mc.set("foo", "bar") ret = mc.get('foo') print ret |
Ps:debug = True 表示运行出现错误时,现实错误信息,上线后移除该参数。
2、天生支持集群
python-memcached模块原生支持集群操作,其原理是在内存维护一个主机列表,且集群中主机的权重值和主机在列表中重复出现的次数成正比
1 2 3 4 5 6 7 |
主机 权重 1.1.1.1 1 1.1.1.2 2 1.1.1.3 1
那么在内存中主机列表为: host_list = ["1.1.1.1", "1.1.1.2", "1.1.1.2", "1.1.1.3", ] |
如果用户根据如果要在内存中创建一个键值对(如:k1= "v1"),那么要执行一下步骤:
· 根据算法将 k1 转换成一个数字
· 将数字和主机列表长度求余数,得到一个值 N(0 <= N < 列表长度)
· 在主机列表中根据第2步得到的值为索引获取主机,例如:host_list[N]
· 连接将第3步中获取的主机,将 k1 = "v1" 放置在该服务器的内存中
代码实现如下:
1 2 3 |
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') |
3、add
添加一条键值对,如果已经存在的 key,重复执行add操作异常
1 2 3 4 5 6 7 |
#!/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重复添加,失败!!! |
4、replace
replace 修改某个key的值,如果key不存在,则异常
1 2 3 4 5 6 7 |
#!/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') |
5、set 和 set_multi
set 设置一个键值对,如果key不存在,则创建,如果key存在,则修改
set_multi 设置多个键值对,如果key不存在,则创建,如果key存在,则修改
1 2 3 4 5 6 7 8 9 |
#!/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'}) |
6、delete 和 delete_multi
delete 在Memcached中删除指定的一个键值对
delete_multi 在Memcached中删除指定的多个键值对
1 2 3 4 5 6 7 8 |
#!/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']) |
7、get 和 get_multi
get 获取一个键值对
get_multi 获取多一个键值对
1 2 3 4 5 6 7 8 |
#!/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"]) |
8、append 和 prepend
append 修改指定key的值,在该值后面追加内容
prepend 修改指定key的值,在该值前面插入内容
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/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" |
9、decr 和 incr
incr 自增,将Memcached中的某一个值增加 N (N默认为1)
decr 自减,将Memcached中的某一个值减少 N ( N默认为1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/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 |
10、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 即可,如:
1 2 3 4 5 6 7 8 9 |
#!/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(获取了缓冲的指定值),如此一来有可能出现非正常数据,则不允许修改。
Memcached 真的过时了吗?
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)
1 2 3 4 5 6 |
在Redis中设置值,默认,不存在则创建,存在则修改 参数: ex,过期时间(秒) px,过期时间(毫秒) nx,如果设置为True,则只有name不存在时,当前set操作才执行 xx,如果设置为True,则只有name存在时,岗前set操作才执行 |
setnx(name, value)
1 |
设置值,只有name不存在时,执行设置操作(添加) |
setex(name, value, time)
1 2 3 |
# 设置值 # 参数: # time,过期时间(数字秒 或 timedelta对象) |
psetex(name, time_ms, value)
1 2 3 |
# 设置值 # 参数: # time_ms,过期时间(数字毫秒 或 timedelta对象) |
mset(*args, **kwargs)
1 2 3 4 5 |
批量设置值 如: mset(k1='v1', k2='v2') 或 mget({'k1': 'v1', 'k2': 'v2'}) |
get(name)
1 |
获取值 |
mget(keys, *args)
1 2 3 4 5 |
批量获取 如: mget('ylr', 'wupeiqi') 或 r.mget(['ylr', 'wupeiqi']) |
getset(name, value)
1 |
设置新值并获取原来的值 |
getrange(key, start, end)
1 2 3 4 5 6 |
# 获取子序列(根据字节获取,非字符) # 参数: # name,Redis 的 name # start,起始位置(字节) # end,结束位置(字节) # 如: "武沛齐" ,0-3表示 "武" |
setrange(name, offset, value)
1 2 3 4 |
# 修改字符串内容,从指定字符串索引开始向后替换 (新值太长时,则向后添加) # 参数: # offset,字符串的索引,字节(一个汉字三个字节) # value,要设置的值 |
setbit(name, offset, value)
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 |
# 对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循环时候会按照 字节 迭代, 那么在迭代时,将每一个字节转换 十进制数,然后再将十进制数转换成二进制 11100110 10101101 10100110 11100110 10110010 10011011 11101001 10111101 10010000 -------------------------- ----------------------------- ----------------------------- 武沛齐 |
getbit(name, offset)
1 |
# 获取name对应的值的二进制表示中的某位的值 (0或1) |
bitcount(key, start=None, end=None)
1 2 3 4 5 |
# 获取name对应的值的二进制表示中 1 的个数 # 参数: # key,Redis的name # start,位起始位置 # end,位结束位置 |
bitop(operation, dest, *keys)
1 2 3 4 5 6 7 8 9 10 |
# 获取多个值,并将值做位运算,将最后的结果保存至新的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)
1 2 3 4 5 6 |
# 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。 # 参数: # name,Redis的name # amount,自增数(必须是整数)
# 注:同incrby |
incrbyfloat(self, name, amount=1.0)
1 2 3 4 |
# 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。 # 参数: # name,Redis的name # amount,自增数(浮点型) |
decr(self, name, amount=1)
1 2 3 4 5 |
# 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。
# 参数: # name,Redis的name # amount,自减数(整数) |
append(key, value)
1 2 3 4 5 |
# 在redis name对应的值后面追加内容
# 参数: key, redis的name value, 要追加的字符串 |
Hash操作,redis中Hash在内存中的存储格式如下图:
hset(name, key, value)
1 2 3 4 5 6 7 8 9 10 |
# name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
# 参数: # name,redis的name # key,name对应的hash中的key # value,name对应的hash中的value
# 注: # hsetnx(name, key, value),当name对应的hash中不存在 当前key时则创建(相当于添加) |
hmset(name, mapping)
1 2 3 4 5 6 7 8 |
# 在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)
1 2 3 4 5 6 7 8 9 10 11 |
# 在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)
1 2 3 4 5 |
# 自增name对应的hash中的指定key的值,不存在则创建key=amount # 参数: # name,redis中的name # key, hash对应的key # amount,自增数(整数) |
hincrbyfloat(name, key, amount=1.0)
1 2 3 4 5 6 7 8 |
# 自增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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 增量式迭代获取,对于数据大的数据非常有用,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)
1 2 3 4 5 6 7 8 9 10 |
# 利用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)
1 2 3 4 5 6 7 8 |
# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边
# 如: # r.lpush('oo', 11,22,33) # 保存顺序为: 33,22,11
# 扩展: # rpush(name, values) 表示从右向左操作 |
lpushx(name,value)
1 2 3 4 5 |
# 在name对应的list中添加元素,只有name已经存在时, 值添加到列表的最左边
# 更多: # rpushx(name, value) 表示从右向左操作 |
llen(name)
1 |
# name对应的list元素的个数 |
linsert(name, where, refvalue, value))
1 2 3 4 5 6 7 |
# 在name对应的列表的某一个值前或后插入一个新值
# 参数: # name,redis的name # where,BEFORE或AFTER # refvalue,标杆值,即:在它前后插入数据 # value,要插入的数据 |
r.lset(name, index, value)
1 2 3 4 5 6 |
# 对name对应的list中的某一个索引位置重新赋值
# 参数: # name,redis的name # index,list的索引位置 # value,要设置的值 |
r.lrem(name, value, num)
1 2 3 4 5 6 7 8 |
# 在name对应的list中删除指定的值
# 参数: # name,redis的name # value,要删除的值 # num, num=0,删除列表中所有的指定值; # num=2,从前到后,删除2个; # num=-2,从后向前,删除2个 |
lpop(name)
1 2 3 4 5 |
# 在name对应的列表的左侧获取第一个元素并在列表中移除, 返回值则是第一个元素
# 更多: # rpop(name) 表示从右向左操作 |
lindex(name, index)
1 |
在name对应的列表中根据索引获取列表元素 |
lrange(name, start, end)
1 2 3 4 5 |
# 在name对应的列表分片获取数据 # 参数: # name,redis的name # start,索引的起始位置 # end,索引结束位置 |
ltrim(name, start, end)
1 2 3 4 5 |
# 在name对应的列表中移除没有在start-end索引之间的值 # 参数: # name,redis的name # start,索引的起始位置 # end,索引结束位置 |
rpoplpush(src, dst)
1 2 3 4 |
# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边 # 参数: # src,要取数据的列表的name # dst,要添加数据的列表的name |
blpop(keys, timeout)
1 2 3 4 5 6 7 8 |
# 将多个列表排列,按照从左到右去pop对应列表的元素
# 参数: # keys,redis的name的集合 # timeout,超时时间,当元素所有列表的元素获取完之后, 阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
# 更多: # r.brpop(keys, timeout),从右向左获取数据 |
brpoplpush(src, dst, timeout=0)
1 2 3 4 5 6 |
# 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
# 参数: # src,取出并要移除元素的列表对应的name # dst,要插入元素的列表对应的name # timeout,当src对应的列表中没有数据时, 阻塞等待其有数据的超时时间(秒),0 表示永远阻塞 |
自定义增量迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 由于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)
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 2 |
# 获取多一个name对应的集合的并集, 并将结果保存到dest对应的集合中 |
sscan(name, cursor=0, match=None, count=None)
sscan_iter(name, match=None, count=None)
1 |
# 同字符串的操作,用于增量迭代分批获取元素,避免内存消耗太大 |
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。
zadd(name, *args, **kwargs)
1 2 3 4 5 |
# 在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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 20 21 22 |
# 按照索引范围获取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)
1 2 3 4 |
# 获取某个值在 name对应的有序集合中的排行(从 0 开始)
# 更多: # zrevrank(name, value),从大到小排序 |
zrangebylex(name, min, max, start=None, num=None)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 当有序集合的所有成员都具有相同的分值时,有序集合的元 素会根据成员的 值 (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)
1 2 3 |
# 删除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)
1 2 3 |
# 获取两个有序集合的交集,如果遇到相同值不同分数, 则按照aggregate进行操作 # aggregate的值为: SUM MIN MAX |
zunionstore(dest, keys, aggregate=None)
1 2 3 |
# 获取两个有序集合的并集,如果遇到相同值不同分数, 则按照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='*')
1 2 3 4 5 6 7 |
# 根据模型获取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)
pipe.set('name', 'alex') pipe.set('role', 'sb')
pipe.execute() |
5、发布订阅
发布者:服务器
订阅者:Dashboad和数据处理
Demo如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
class RedisHelper:
def__init__(self):
self.__conn =redis.Redis(host='10.211.55.4')
self.chan_sub ='fm104.5'
self.chan_pub ='fm104.5'
def public(self,msg):
self.__conn.publish(self.chan_pub,msg)
return True
defsubscribe(self):
pub = self.__conn.pubsub()
pub.subscribe(self.chan_sub)
pub.parse_response()
return pub
订阅者:
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') |
更多参见:https://github.com/andymccurdy/redis-py/
http://doc.redisfans.com/
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-serverstart/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
基于Queue实现生产者消费者模型
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import Queue
import threading
message = Queue.Queue(10)
def producer(i):
while True:
message.put(i)
def consumer(i):
while True:
msg =message.get()
for i in range(12):
t =threading.Thread(target=producer, args=(i,))
t.start()
for i in range(10):
t =threading.Thread(target=consumer, args=(i,))
t.start()
对于RabbitMQ来说,生产和消费不再针对内存里的一个Queue对象,而是某台服务器上的RabbitMQ Server实现的消息队列。
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( host='localhost')) channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') print(" [x] Sent 'Hello World!'") connection.close() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/usr/bin/env python import pika
# ########################## 消费者 ##########################
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) channel = connection.channel()
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() |
1、acknowledgment 消息不丢失
no-ack = False,如果消费者遇到情况(its channel is closed,connection is closed, or TCP connection is lost)挂掉了,那么,RabbitMQ会重新将该任务添加到队列中。
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='10.211.55.4'))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
import time
time.sleep(10)
print 'ok'
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(callback,
queue='hello',
no_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
2、durable 消息不丢失
#!/usr/bin/env python
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
channel = connection.channel()
# make message persistent
channel.queue_declare(queue='hello', durable=True)
channel.basic_publish(exchange='',
routing_key='hello',
body='Hello World!',
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent
))
print(" [x] Sent 'Hello World!'")
connection.close()
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
channel = connection.channel()
# make message persistent
channel.queue_declare(queue='hello', durable=True)
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
import time
time.sleep(10)
print 'ok'
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(callback,
queue='hello',
no_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
3、消息获取顺序
默认消息队列里的数据是按照顺序被消费者拿走,例如:消费者1去队列中获取奇数序列的任务,消费者1去队列中获取偶数序列的任务。
channel.basic_qos(prefetch_count=1)表示谁来谁取,不再按照奇偶数排列
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(host='10.211.55.4'))
channel = connection.channel()
# make message persistent
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(" [x] Received %r" % body)
import time
time.sleep(10)
print 'ok'
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
queue='hello',
no_ack=False)
print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
4、发布订阅
发布订阅和简单的消息队列区别在于,发布订阅会将消息发送给所有的订阅者,而消息队列中的数据被消费一次便消失。所以,RabbitMQ实现发布和订阅时,会为每一个订阅者创建一个队列,而发布者发布消息时,会将消息放置在所有相关队列中。
exchangetype = fanout
#!/usr/bin/env python
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()
#!/usr/bin/env python
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_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()
5、关键字发送
exchange,exchange type = direct
之前事例,发送消息时明确指定某个队列并向其中发送消息,RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据关键字判定应该将数据发送至指定队列。
#!/usr/bin/env python
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()
#!/usr/bin/env python
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()
6、模糊匹配
exchange type = topic
在topic类型下,可以让队列绑定几个模糊的关键字,之后发送者将数据发送到exchange,exchange将传入”路由值“和 ”关键字“进行匹配,匹配成功,则将数据发送到指定队列。
· # 表示可以匹配 0 个或多个单词
· * 表示只能匹配一个单词
1 2 3 |
发送者路由值 队列中 old.boy.python old.* -- 不匹配 old.boy.python old.# -- 匹配 |
#!/usr/bin/env python
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:]
ifnot 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()
#!/usr/bin/env python
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()
注意:
sudo rabbitmqctl add_user alex 123
# 设置用户为administrator角色
sudo rabbitmqctl set_user_tags alex administrator
# 设置权限
sudo rabbitmqctl set_permissions -p "/" alex '.''.''.'
# 然后重启rabbiMQ服务
sudo /etc/init.d/rabbitmq-server restart
# 然后可以使用刚才的用户远程连接rabbitmq server了。
------------------------------
credentials = pika.PlainCredentials("alex","123")
connection =pika.BlockingConnection(pika.ConnectionParameters('192.168.14.47',credentials=credentials))
SQLAlchemy
SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
MySQL-Python mysql+mysqldb:// [:
pymysql mysql+pymysql:// [?
MySQL-Connector mysql+mysqlconnector:// [:
cx_Oracle oracle+cx_oracle://user:pass@host:port/dbname [?key=value&key=value...]
更多详见: http://docs.sqlalchemy.org/en/latest/dialects/index.html |
步骤一:
使用Engine/ConnectionPooling/Dialect 进行数据库操作,Engine使用ConnectionPooling连接数据库,然后再通过Dialect执行SQL语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/usr/bin/env python # -*- coding:utf-8 -*-
from sqlalchemy import create_engine
engine = create_engine("mysql+mysqldb:// root:[email protected]:3306/s11", max_overflow=5)
engine.execute( "INSERT INTO ts_test (a, b) VALUES ('2', 'v1')" )
engine.execute( "INSERT INTO ts_test (a, b) VALUES (%s, %s)", ((555, "v1"),(666, "v1"),) ) engine.execute( "INSERT INTO ts_test (a, b) VALUES (%(id)s, %(name)s)", id=999, name="v1" )
result = engine.execute('select * from ts_test') result.fetchall() |
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from sqlalchemy import create_engine
engine = create_engine("mysql+mysqldb://root:[email protected]:3306/s11",max_overflow=5)
# 事务操作
with engine.begin() as conn:
conn.execute("insertinto table (x, y, z) values (1, 2, 3)")
conn.execute("my_special_procedure(5)")
conn = engine.connect()
# 事务操作
with conn.begin():
conn.execute("somestatement", {'x':5, 'y':10})
注:查看数据库连接:show statuslike 'Threads%';
步骤二:
使用 Schema Type/SQLExpression Language/Engine/ConnectionPooling/Dialect 进行数据库操作。Engine使用Schema Type创建一个特定的结构对象,之后通过SQLExpression Language将该对象转换成SQL语句,然后通过 ConnectionPooling 连接数据库,再然后通过 Dialect 执行SQL,并获取结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/usr/bin/env python # -*- coding:utf-8 -*-
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey
metadata = MetaData()
user = Table('user', metadata, Column('id', Integer, primary_key=True), Column('name', String(20)), )
color = Table('color', metadata, Column('id', Integer, primary_key=True), Column('name', String(20)), ) engine = create_engine("mysql+mysqldb: //root:[email protected]:3306/s11", max_overflow=5)
metadata.create_all(engine) # metadata.clear() # metadata.remove() |
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from sqlalchemy importcreate_engine, Table, Column, Integer, String, MetaData, ForeignKey
metadata = MetaData()
user = Table('user', metadata,
Column('id', Integer,primary_key=True),
Column('name', String(20)),
)
color = Table('color', metadata,
Column('id', Integer,primary_key=True),
Column('name', String(20)),
)
engine = create_engine("mysql+mysqldb://root:[email protected]:3306/s11",max_overflow=5)
conn = engine.connect()
# 创建SQL语句,INSERT INTO"user" (id, name) VALUES (:id, :name)
conn.execute(user.insert(),{'id':7,'name':'seven'})
conn.close()
# sql = user.insert().values(id=123, name='wu')
# conn.execute(sql)
# conn.close()
# sql = user.delete().where(user.c.id > 1)
# sql = user.update().values(fullname=user.c.name)
# sql = user.update().where(user.c.name =='jack').values(name='ed')
# sql = select([user, ])
# sql = select([user.c.id, ])
# sql = select([user.c.name,color.c.name]).where(user.c.id==color.c.id)
# sql = select([user.c.name]).order_by(user.c.name)
# sql = select([user]).group_by(user.c.name)
# result = conn.execute(sql)
# print result.fetchall()
# conn.close()
更多内容详见:
http://www.jianshu.com/p/e6bba189fcbd
http://docs.sqlalchemy.org/en/latest/core/expression_api.html
注:SQLAlchemy无法修改表结构,如果需要可以使用SQLAlchemy开发者开源的另外一个软件Alembic来完成。
步骤三:
使用 ORM/Schema Type/SQLExpression Language/Engine/ConnectionPooling/Dialect 所有组件对数据进行操作。根据类创建对象,对象转换成SQL,执行SQL。
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#!/usr/bin/env python # -*- coding:utf-8 -*-
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine
engine = create_engine("mysql+mysqldb:// root:[email protected]:3306/s11", max_overflow=5)
Base = declarative_base()
class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(50))
# 寻找Base的所有子类,按照子类的结构在数据库中生成对应的数据表信息 # Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine) session = Session()
# ########## 增 ########## # u = User(id=2, name='sb') # session.add(u) # session.add_all([ # User(id=3, name='sb'), # User(id=4, name='sb') # ]) # session.commit()
# ########## 删除 ########## # session.query(User).filter(User.id > 2).delete() # session.commit()
# ########## 修改 ########## # session.query(User).filter(User.id > 2).update ({'cluster_id' : 0}) # session.commit() # ########## 查 ########## # ret = session.query(User).filter_by(name='sb').first()
# ret = session.query(User).filter_by(name='sb').all() # print ret
# ret = session.query(User). filter(User.name.in_(['sb','bb'])).all() # print ret
# ret = session.query(User.name.label('name_label')).all() # print ret,type(ret)
# ret = session.query(User).order_by(User.id).all() # print ret
# ret = session.query(User).order_by(User.id)[1:3] # print ret # session.commit() |
更多功能参见文档,猛击这里下载PDF
redis 使用
http://www.cnblogs.com/alex3714/articles/6217453.html
Twsited异步网络框架
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上有数据可读时通知回调处理。
一个传送文件的例子
server side
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#_*_coding:utf-8_*_ # This is the Twisted Fast Poetry Server, version 1.0
import optparse, os
from twisted.internet.protocol import ServerFactory, Protocol
def parse_args(): usage = """usage: %prog [options] poetry-file
This is the Fast Poetry Server, Twisted edition. Run it like this:
python fastpoetry.py
If you are in the base directory of the twisted-intro package, you could run it like this:
python twisted-server-1/fastpoetry.py poetry/ecstasy.txt
to serve up John Donne's Ecstasy, which I know you want to do. """
parser = optparse.OptionParser(usage)
help = "The port to listen on. Default to a random available port." parser.add_option('--port', type='int', help=help)
help = "The interface to listen on. Default is localhost." parser.add_option('--iface', help=help, default='localhost')
options, args = parser.parse_args() print("--arg:",options,args)
if len(args) != 1: parser.error('Provide exactly one poetry file.')
poetry_file = args[0]
if not os.path.exists(args[0]): parser.error('No such file: %s' % poetry_file)
return options, poetry_file
class PoetryProtocol(Protocol):
def connectionMade(self): self.transport.write(self.factory.poem) self.transport.loseConnection()
class PoetryFactory(ServerFactory):
protocol = PoetryProtocol
def __init__(self, poem): self.poem = poem
def main(): options, poetry_file = parse_args()
poem = open(poetry_file).read()
factory = PoetryFactory(poem)
from twisted.internet import reactor
port = reactor.listenTCP(options.port or 9000, factory, interface=options.iface)
print 'Serving %s on %s.' % (poetry_file, port.getHost())
reactor.run()
if __name__ == '__main__': main() |
client side
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# This is the Twisted Get Poetry Now! client, version 3.0.
# NOTE: This should not be used as the basis for production code.
import optparse
from twisted.internet.protocol import Protocol, ClientFactory
def parse_args(): usage = """usage: %prog [options] [hostname]:port ...
This is the Get Poetry Now! client, Twisted version 3.0 Run it like this:
python get-poetry-1.py port1 port2 port3 ... """
parser = optparse.OptionParser(usage)
_, addresses = parser.parse_args()
if not addresses: print parser.format_help() parser.exit()
def parse_address(addr): if ':' not in addr: host = '127.0.0.1' port = addr else: host, port = addr.split(':', 1)
if not port.isdigit(): parser.error('Ports must be integers.')
return host, int(port)
return map(parse_address, addresses)
class PoetryProtocol(Protocol):
poem = ''
def dataReceived(self, data): self.poem += data
def connectionLost(self, reason): self.poemReceived(self.poem)
def poemReceived(self, poem): self.factory.poem_finished(poem)
class PoetryClientFactory(ClientFactory):
protocol = PoetryProtocol
def __init__(self, callback): self.callback = callback
def poem_finished(self, poem): self.callback(poem)
def get_poetry(host, port, callback): """ Download a poem from the given host and port and invoke
callback(poem)
when the poem is complete. """ from twisted.internet import reactor factory = PoetryClientFactory(callback) reactor.connectTCP(host, port, factory)
def poetry_main(): addresses = parse_args()
from twisted.internet import reactor
poems = []
def got_poem(poem): poems.append(poem) if len(poems) == len(addresses): reactor.stop()
for address in addresses: host, port = address get_poetry(host, port, got_poem)
reactor.run()
for poem in poems: print poem
if __name__ == '__main__': poetry_main() |
Twisted深入
http://krondo.com/an-introduction-to-asynchronous-programming-and-twisted/
http://blog.csdn.net/hanhuili/article/details/9389433
SqlAlchemy ORM
SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简言之便是:将对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
Dialect用于和数据API进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
MySQL-Python mysql+mysqldb://
pymysql mysql+pymysql://
MySQL-Connector mysql+mysqlconnector:// [:
cx_Oracle oracle+cx_oracle://user:pass@host:port/dbname [?key=value&key=value...]
更多详见:http://docs.sqlalchemy.org/en/latest/dialects/index.html |
步骤一:
使用Engine/ConnectionPooling/Dialect 进行数据库操作,Engine使用ConnectionPooling连接数据库,然后再通过Dialect执行SQL语句。
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 |
#!/usr/bin/env python # -*- coding:utf-8 -*-
from sqlalchemy import create_engine
engine = create_engine("mysql+mysqldb:// root:[email protected]:3306/s11", max_overflow=5)
engine.execute( "INSERT INTO ts_test (a, b) VALUES ('2', 'v1')" )
engine.execute( "INSERT INTO ts_test (a, b) VALUES (%s, %s)", ((555, "v1"),(666, "v1"),) ) engine.execute( "INSERT INTO ts_test (a, b) VALUES (%(id)s, %(name)s)", id=999, name="v1" )
result = engine.execute('select * from ts_test') result.fetchall() |
步骤二:
使用 Schema Type/SQLExpression Language/Engine/ConnectionPooling/Dialect 进行数据库操作。Engine使用Schema Type创建一个特定的结构对象,之后通过SQLExpression Language将该对象转换成SQL语句,然后通过 ConnectionPooling 连接数据库,再然后通过 Dialect 执行SQL,并获取结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/usr/bin/env python # -*- coding:utf-8 -*-
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey
metadata = MetaData()
user = Table('user', metadata, Column('id', Integer, primary_key=True), Column('name', String(20)), )
color = Table('color', metadata, Column('id', Integer, primary_key=True), Column('name', String(20)), ) engine = create_engine("mysql+mysqldb:// root@localhost:3306/test", max_overflow=5)
metadata.create_all(engine) |
增删改查
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 41 42 43 44 45 46 47 |
#!/usr/bin/env python # -*- coding:utf-8 -*-
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey
metadata = MetaData()
user = Table('user', metadata, Column('id', Integer, primary_key=True), Column('name', String(20)), )
color = Table('color', metadata, Column('id', Integer, primary_key=True), Column('name', String(20)), ) engine = create_engine("mysql+mysqldb:// root:[email protected]:3306/s11", max_overflow=5)
conn = engine.connect()
# 创建SQL语句,INSERT INTO "user" (id, name) VALUES (:id, :name) conn.execute(user.insert(),{'id':7,'name':'seven'}) conn.close()
# sql = user.insert().values(id=123, name='wu') # conn.execute(sql) # conn.close()
# sql = user.delete().where(user.c.id > 1)
# sql = user.update().values(fullname=user.c.name) # sql = user.update().where(user.c.name == 'jack'). values(name='ed')
# sql = select([user, ]) # sql = select([user.c.id, ]) # sql = select([user.c.name, color.c.name]).where (user.c.id==color.c.id) # sql = select([user.c.name]).order_by(user.c.name) # sql = select([user]).group_by(user.c.name)
# result = conn.execute(sql) # print result.fetchall() # conn.close() |
一个简单的完整例子
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 |
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String from sqlalchemy.orm import sessionmaker
Base = declarative_base() #生成一个SqlORM 基类
engine = create_engine("mysql+mysqldb:// root@localhost:3306/test",echo=False)
class Host(Base): __tablename__ = 'hosts' id = Column(Integer,primary_key=True,autoincrement=True) hostname = Column(String(64),unique=True,nullable=False) ip_addr = Column(String(128),unique=True,nullable=False) port = Column(Integer,default=22)
Base.metadata.create_all(engine) #创建所有表结构
if __name__ == '__main__': SessionCls = sessionmaker(bind=engine) #创建与数据库的会话session class ,注意, 这里返回给session的是个class,不是实例 session = SessionCls() #h1 = Host(hostname='localhost',ip_addr='127.0.0.1') #h2 = Host(hostname='ubuntu',ip_addr='192.168.2.243', port=20000) #h3 = Host(hostname='ubuntu2',ip_addr='192.168.2.244', port=20000) #session.add(h3) #session.add_all( [h1,h2]) #h2.hostname = 'ubuntu_test' #只要没提交,此时修改也没问题 #session.rollback() #session.commit() #提交 res = session.query(Host).filter(Host.hostname. in_(['ubuntu2','localhost'])).all() print(res) |
更多内容详见:
http://www.jianshu.com/p/e6bba189fcbd
http://docs.sqlalchemy.org/en/latest/core/expression_api.html
注:SQLAlchemy无法修改表结构,如果需要可以使用SQLAlchemy开发者开源的另外一个软件Alembic来完成。
步骤三:
使用 ORM/Schema Type/SQLExpression Language/Engine/ConnectionPooling/Dialect 所有组件对数据进行操作。根据类创建对象,对象转换成SQL,执行SQL。
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#!/usr/bin/env python # -*- coding:utf-8 -*-
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine
engine = create_engine("mysql+mysqldb:// root:[email protected]:3306/s11", max_overflow=5)
Base = declarative_base()
class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(50))
# 寻找Base的所有子类,按照子类的结构在数据库中生成对应的数据表信息 # Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine) session = Session()
# ########## 增 ########## # u = User(id=2, name='sb') # session.add(u) # session.add_all([ # User(id=3, name='sb'), # User(id=4, name='sb') # ]) # session.commit()
# ########## 删除 ########## # session.query(User).filter(User.id > 2).delete() # session.commit()
# ########## 修改 ########## # session.query(User).filter(User.id > 2).update({'cluster_id' : 0}) # session.commit() # ########## 查 ########## # ret = session.query(User).filter_by(name='sb').first()
# ret = session.query(User).filter_by(name='sb').all() # print ret
# ret = session.query(User).filter(User.name.in_(['sb','bb'])).all() # print ret
# ret = session.query(User.name.label('name_label')).all() # print ret,type(ret)
# ret = session.query(User).order_by(User.id).all() # print ret
# ret = session.query(User).order_by(User.id)[1:3] # print ret # session.commit() |
外键关联
A one to many relationship places a foreign key on the childtable referencing the parent.relationship() is then specified on the parent, as referencing acollection of items represented by the child
fromsqlalchemy import Table, Column, Integer, ForeignKey
fromsqlalchemy.orm import relationship
fromsqlalchemy.ext.declarative import declarative_base
Base= declarative_base()
1 2 3 4 5 6 7 8 9 |
__tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child")
class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) |
To establish a bidirectional relationship in one-to-many, wherethe “reverse” side is a many to one, specify an additional relationship() and connect the two using therelationship.back_populates parameter:
1 2 3 4 5 6 7 8 9 10 |
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child", back_populates="parent")
class Child(Base): __tablename__ = 'child' id = Column(Integer, primary_key=True) parent_id = Column(Integer, ForeignKey('parent.id')) parent = relationship("Parent", back_populates="children") |
Child willget a parent attribute with many-to-one semantics.
Alternatively, the backref option may be used on a single relationship() instead of usingback_populates:
1 2 3 4 |
class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True) children = relationship("Child", backref="parent") |
附,原生sql join查询
几个Join的区别 http://stackoverflow.com/questions/38549/difference-between-inner-and-outer-joins
· INNER JOIN: Returns all rows when there is at least one match in BOTHtables
· LEFT JOIN: Return all rows from the left table, and the matched rows fromthe right table
· RIGHT JOIN: Return all rows from the right table, and the matched rowsfrom the left table
1 |
select host.id,hostname,ip_addr,port,host_group.name from host right join host_group on host.id = host_group.host_id |
in SQLAchemy
1 |
session.query(Host).join(Host.host_groups). filter(HostGroup.name=='t1').group_by("Host").all() |
group by 查询
1 |
select name,count(host.id) as NumberOfHosts from host right join host_group on host.id= host_group.host_id group by name; |
in SQLAchemy
1 2 3 4 5 6 |
from sqlalchemy import func session.query(HostGroup, func.count(HostGroup.name )). group_by(HostGroup.name).all()
#another example session.query(func.count(User.name), User.name). group_by(User.name).all() SELECT count(users.name) AS count_1, users.name AS users_name FROM users GROUP BY users.name |
本节作业一
题目:IO多路复用版FTP
需求:
1. 实现文件上传及下载功能
2. 支持多连接并发传文件
3. 使用select or selectors
本节作业二
题目:rpc命令端
需求:
1. 可以异步的执行多个命令
2. 对多台机器
>>:run "df -h" --hosts 192.168.3.5510.4.3.4
task id: 45334
>>: check_task 45334
>>: