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

 

posted @ 2018-05-25 21:26  Ivan_yyq  阅读(184)  评论(0编辑  收藏  举报