day23-20180524笔记
笔记:python3 消息队列queue、Pipe模块,Celery异步分布式
一、queue模块
1、消息队列queue模块
注意:Python2的消息队列模块是Queue,而Python3的消息队列是queue
queue 就是对队列,它是线程安全的
举例来说,我们去肯德基吃饭。厨房是给我们做饭的地方,前台负责把厨房做好的饭卖给顾客,顾客则去前台领取做好的饭。这里的前台就相当于我们的队列。
这个模型也叫生产者-消费者模型。
import queue q = queue.Queue(maxsize=0) # 构造一个先进显出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。 q.join() # 等到队列为kong的时候,在执行别的操作 q.qsize() # 返回队列的大小 (不可靠) q.empty() # 当队列为空的时候,返回True 否则返回False (不可靠) q.full() # 当队列满的时候,返回True,否则返回False (不可靠) q.put(item, block=True, timeout=None) # 将item放入Queue尾部,item必须存在,可以参数block默认为True,表示当队列满时,会等待队列给出可用位置, 为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示 会阻塞设置的时间,过后, 如果队列无法给出放入item的位置,则引发 queue.Full 异常 q.get(block=True, timeout=None) # 移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞,为False时,不阻塞, 若此时队列为空,则引发 queue.Empty异常。 可选参数timeout,表示会阻塞设置的时候,过后,如果队列为空,则引发Empty异常。 q.put_nowait(item) # 等效于 put(item,block=False) q.get_nowait() # 等效于 get(item,block=False)
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/24 20:31 # @Author : yangyuanqiang # @File : demon1.py ''' 消息队列 一个是生产者producer 一个是消费者customer 创建一个队列 q = Queue() 发消息: q.put() 收消息: dat = q.get() ''' import time from multiprocessing import Queue, Pipe from threading import Thread def producer(q): print("start producer") for i in range(10): q.put(i) # print("producer has producer value {0}".format(i)) time.sleep(0.3) print("end producer") def customer(q): print("start customer") while 1: date = q.get() print("customer has get value {0}".format(date)) if __name__ == '__main__': q = Queue() p = Thread(target=producer, args=(q,)) c = Thread(target=customer, args=(q,)) p.start() c.start()
以上实例输出的结果
start producer start customer customer has get value 0 customer has get value 1 customer has get value 2 customer has get value 3 customer has get value 4 customer has get value 5 customer has get value 6 customer has get value 7 customer has get value 8 customer has get value 9 end producer
生产者--消费者:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/25 21:36 # @Author : yangyuanqiang # @File : demon5.py 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()
2、Pipe模块
返回2个连接对象(conn1, conn2),代表管道的两端,默认是双向通信.如果duplex=False,conn1只能用来接收消息,conn2只能用来发送消息.不同于os.open之处在于os.pipe()返回2个文件描述符(r, w),表示可读的和可写的
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/24 21:06 # @Author : yangyuanqiang # @File : demon2.py ''' Pipe的方法返回一个tuple (conn1, conn2) Pipe的方法还有一个参数duplex参数,如果deplex=True,叫双工模式 如果deplex=False conn1只负责接收,conn2只负责发送消息 发消息: send 收消息: recv 关闭管道: close ''' import time, multiprocessing from multiprocessing import Process, Pipe def proc1(pipe): for i in range(10): print("send {0}".format(i)) pipe.send(i) time.sleep(0.1) def proc2(pipe): n = 10 while n: print("proc2 recv: {0}".format(pipe.recv())) n -= 1 if __name__ == '__main__': (p1, p2) = multiprocessing.Pipe(duplex=False) pr = Process(target=proc1, args=(p2,)) #发消息 cu = Process(target=proc2, args=(p1,)) #收消息 pr.start() cu.start()
以上实例输出的结果
#发送一个,接收一个
send 0 proc2 recv: 0 send 1 proc2 recv: 1 send 2 proc2 recv: 2 send 3 proc2 recv: 3 send 4 proc2 recv: 4 send 5 proc2 recv: 5 send 6 proc2 recv: 6 send 7 proc2 recv: 7 send 8 proc2 recv: 8 send 9 proc2 recv: 9
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/25 21:36 # @Author : yangyuanqiang # @File : demon5.py import os from multiprocessing import Process, Pipe def send(pipe): pipe.send(['spam'] + [42, 'egg']) pipe.close() def talk(pipe): pipe.send(dict(name = 'Bob', spam = 42)) reply = pipe.recv() print('talker got:', reply) if __name__ == '__main__': (con1, con2) = Pipe() sender = Process(target = send, name = 'send', args = (con1, )) sender.start() print("con2 got: %s" % con2.recv())#从send收到消息 con2.close() (parentEnd, childEnd) = Pipe() child = Process(target = talk, name = 'talk', args = (childEnd, )) child.start() print('parent got:', parentEnd.recv()) parentEnd.send({x * 2 for x in 'spam'}) child.join() print('parent exit')
以上实例输出的结果
con2 got: ['spam', 42, 'egg'] parent got: {'name': 'Bob', 'spam': 42} talker got: {'mm', 'ss', 'pp', 'aa'} parent exit
二、Celery异步分布式
什么是celery
Celery是一个python开发的异步分布式任务调度模块。
Celery本身并不提供消息服务,使用第三方服务,也就是borker来传递任务,目前支持rebbimq,redis, 数据库等。
这里我们使用redis
连接url的格式为:
redis://:password@hostname:port/db_number
例如:
BROKER_URL = 'redis://localhost:6379/0'
安装celery
pip install celery
pip install redis
注意:linux服务器安装redis,请选择redis3.0版本,因为redis4.0版本要求密码认证才能连接redis
在服务器上安装redis服务器,并启动redis
第一个简单的例子:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/24 22:31 # @Author : yangyuanqiang # @File : ivan.py import time from celery import Celery broker = "redis://192.168.3.11:6379/5" #存生产者的信息 backend = "redis://192.168.3.11:6379/6" #存处理完后的消息 app = Celery("ivan", broker=broker, backend=backend) #ivan写的名字要跟创建py文件的名字保持一致
#以下内容写的是worker @app.task def add(x, y): time.sleep(5) return x+y
登录redis,操作以下步骤,因为以上代码操作的是5库和6库
ivandeMacBook-Pro:redis-3.0.0 ivan$ src/redis-cli 127.0.0.1:6379> select 5 OK 127.0.0.1:6379[5]> keys * (empty list or set) 127.0.0.1:6379[5]> select 6 OK 127.0.0.1:6379[6]> keys * (empty list or set) 127.0.0.1:6379[6]>
启动worker,消费者
# celery -A ivan worker -l info
注意:celery -A 启动一个app, 后面跟app的名字,app名字需要和上面的文件名字一致。
启动以后,显示如图一下信息,说明启动成功:
生产者
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/24 22:10 # @Author : yangyuanqiang # @File : demon4.py ''' pip install redis pip install celery 安装redis,启动redis ''' import time from ivan import add re = add.delay(10, 20) print(re.task_id) print(re) #传入ID print(re.status) #是否处理 time.sleep(8) print(re.status) #是否处理 print(re.get(timeout=1)) #获取结果 print(re.result) #获取结果
以上实例输出的结果
593e0af5-163c-4d8c-b4bf-57db4e793631 593e0af5-163c-4d8c-b4bf-57db4e793631 PENDING SUCCESS 30 30
查看linux服务器上的redis
[2018-05-26 10:20:07,810: INFO/MainProcess] Received task: ivan.add[593e0af5-163c-4d8c-b4bf-57db4e793631] [2018-05-26 10:20:12,831: INFO/ForkPoolWorker-1] Task ivan.add[593e0af5-163c-4d8c-b4bf-57db4e793631] succeeded in 5.016767148999861s: 30
ivandeMacBook-Pro:redis-3.0.0 ivan$ src/redis-cli 127.0.0.1:6379> keys * 1) "site" 2) "list1" 127.0.0.1:6379> select 5 OK 127.0.0.1:6379[5]> keys * 1) "_kombu.binding.celeryev" 2) "_kombu.binding.celery.pidbox" 3) "_kombu.binding.celery" 4) "unacked_mutex" 127.0.0.1:6379[5]> type _kombu.binding.celery set 127.0.0.1:6379[5]> smembers _kombu.binding.celery 1) "celery\x06\x16\x06\x16celery" 127.0.0.1:6379[5]> select 6 OK 127.0.0.1:6379[6]> keys * 1) "celery-task-meta-593e0af5-163c-4d8c-b4bf-57db4e793631" 127.0.0.1:6379[6]>
注意:在linux服务端启动消费者worker时,首先要查看下linux服务器下的python版本号,然后在pycharm上执行生产者程序时,必须跟linux服务器上的python版本保持一致,否则会报错,已踩坑里
Celery模块调用
既然celery是一个分布式的任务调度模块,那么celery是如何和分布式挂钩呢,celery可以支持多台不通的计算机执行不同的任务或者相同的任务。
如果要说celery的分布式应用的话,我觉得就要提到celery的消息路由机制,就要提一下AMQP协议。具体的可以查看AMQP的文档。简单地说就是可以有多个消息队列(Message Queue),不同的消息可以指定发送给不同的Message Queue,而这是通过Exchange来实现的。发送消息到Message Queue中时,可以指定routiing_key,Exchange通过routing_key来把消息路由(routes)到不同的Message Queue中去
多进程
多worker,多队列
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/26 10:31 # @Author : yangyuanqiang # @File : tasks.py from celery import Celery app = Celery() app.config_from_object("celeryconfig") @app.task def taskA(x,y): return x + y @app.task def taskB(x,y,z): return x + y + z @app.task def add(x,y,z): return x * y * z
上面的代码中,首先定义了一个Celery的对象,然后通过celeryconfig.py对celery对象进行设置。之后又分别定义了三个task,分别是taskA, taskB和add。接下来看看celeryconfig.py
注意:代码的缩进格式:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/26 10:32 # @Author : yangyuanqiang # @File : celeryconfig.py from kombu import Exchange,Queue BROKER_URL = "redis://192.168.3.11:6379/1" CELERY_RESULT_BACKEND = "redis://192.168.3.11:6379/2" CELERY_QUEUES = ( Queue("default",Exchange("default"),routing_key="default"), Queue("for_task_A",Exchange("for_task_A"),routing_key="for_task_A"), Queue("for_task_B",Exchange("for_task_B"),routing_key="for_task_B") ) CELERY_ROUTES = { 'tasks.taskA':{"queue":"for_task_A","routing_key":"for_task_A"}, 'tasks.taskB':{"queue":"for_task_B","routing_key":"for_task_B"} }
配置文件一般单独写在一个文件中。
启动一个worker来指定taskA
ivandeMacBook-Pro:celery ivan$ celery -A tasks worker -l info -n workerA.%h -Q for_task_A
启动一个worker来指定taskB
celery -A tasks worker -l info -n workerB.%h -Q for_task_B
执行以下程序
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/26 10:42 # @Author : yangyuanqiang # @File : demon6.py from tasks import * re1 = taskA.delay(100, 200) print(re1.result) re2 = taskB.delay(1, 2, 3) print(re2.result) re3 = add.delay(1, 2, 3) print(re3.status) #PENDING
以上实例输出的结果
None
None
PENDING
查看linux服务器上tasks_A的输出结果
[2018-05-26 10:53:30,586: INFO/MainProcess] Received task: tasks.taskA[f282b8f3-cf17-43e3-bdc9-95088816a15c] [2018-05-26 10:53:30,588: INFO/ForkPoolWorker-1] Task tasks.taskA[f282b8f3-cf17-43e3-bdc9-95088816a15c] succeeded in 0.0006585959999938495s: 300
tasks_B的输出结果
[2018-05-26 10:53:30,589: INFO/MainProcess] Received task: tasks.taskB[c645b2ec-ee1f-4dc0-9d3c-f7b9b428a798] [2018-05-26 10:53:30,591: INFO/ForkPoolWorker-1] Task tasks.taskB[c645b2ec-ee1f-4dc0-9d3c-f7b9b428a798] succeeded in 0.0005881699999008561s: 6