Python分布式应用实践

本文重点探讨以下几个内容:

  • python自身对分布式进程的支持
    managers子模块
    主进程task_master.py
    子进程task_worker.py
  • celery分布式框架实践
    celery简介
    celery+redis环境安装
    celery实现简单计算

python自身对分布式进程的支持

Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。实现起来还是很方便的,接下来,我们进行一项测试,实现一个简单的分布式计算。

主进程task_master.py

主进程task_master.py代码步骤:

  • 定义发送/接受队列、队列manager
  • 网络注册队列,启动队列manager
  • 通过网络放入任务和获取任务结果
  • 结束队列manager

定义发送/接受队列、队列manager

import random
import queue
from multiprocessing.managers import BaseManager

# 发送任务的队列:
task_queue = queue.Queue()
# 接收结果的队列:
result_queue = queue.Queue()


# 从BaseManager继承的QueueManager:
class QueueManager(BaseManager):
    pass

网络注册队列,启动队列manager


# 把两个Queue都注册到网络上, callable参数关联了Queue对象:
QueueManager.register('get_task_queue', callable=lambda: task_queue)
QueueManager.register('get_result_queue', callable=lambda: result_queue)
# 绑定端口5000, 设置验证码'abc':
manager = QueueManager(address=('', 5000), authkey=b'abc')
# 启动Queue:
manager.start()

注意:这里的address为空,即是本机地址

通过网络放入任务和获取任务结果

# 获得通过网络访问的Queue对象:
task = manager.get_task_queue()
result = manager.get_result_queue()
# 放几个任务进去:
for i in range(10):
    n = random.randint(0, 10000)
    print('Put task %d...' % n)
    task.put(n)
# 从result队列读取结果:
print('Try get results...')
for i in range(10):
    r = result.get(timeout=10)
    print('Result: %s' % r)

这里task队列长度为10,放入的是10000内的随机数。当然也可以是其他object,是待处理的数据,result再从网络队列获取结果。

结束队列manager

manager.shutdown()
当然实际使用时可以使用try或with关键字实现上下文管理

工作进程task_master.py

主进程task_master.py代码步骤:

  • 从网络获取任务/结果队列
  • 计算并写入结果

从网络获取任务/结果队列

import time, sys, queue
from multiprocessing.managers import BaseManager

# 创建类似的QueueManager:
class QueueManager(BaseManager):
    pass

# 由于这个QueueManager只从网络上获取Queue,所以注册时只提供名字:
QueueManager.register('get_task_queue')
QueueManager.register('get_result_queue')

# 连接到服务器,也就是运行task_master.py的机器:
server_addr = '127.0.0.1'
print('Connect to server %s...' % server_addr)
# 端口和验证码注意保持与task_master.py设置的完全一致:
m = QueueManager(address=(server_addr, 5000), authkey=b'abc')
# 从网络连接:
m.connect()
# 获取Queue的对象:
task = m.get_task_queue()
result = m.get_result_queue()

注意:同样需要定义manager用来从网络上获取队列

计算并写入结果

这里从task队列中获取待计算的数据,经过平方计算后,格式化成字符串放入result队列中。注意,处理结束后,无需shutdown

# 从task队列取任务,并把结果写入result队列:
for i in range(10):
    try:
        n = task.get(timeout=1)
        print('run task %d * %d...' % (n, n))
        r = '%d * %d = %d' % (n, n, n * n)
        time.sleep(1)
        result.put(r)
    except queue.Queue.Empty:
        print('task queue is empty.')
print('worker exit.')

先运行主进程,然后运行工作进程

1 首先运行task_master.py,主进程发送待处理数据到task队列中,并阻塞等待result队列的结果。
2 然后运行工作进程task_worker.py,工作进程从task队列获取数据,并进行计算,将结果发送到result队列。
3 主进程result队列收到结果,并打印显示(计算和显示交替进行)
主进程显示如下:
Put task 6792…
Put task 6251…
Put task 6774…
Put task 7543…
Put task 9954…
Put task 6949…
Put task 6421…
Put task 6683…
Put task 6562…
Put task 1352…
Try get results…
Result: 6792 * 6792 = 46131264
Result: 6251 * 6251 = 39075001
Result: 6774 * 6774 = 45887076
Result: 7543 * 7543 = 56896849
Result: 9954 * 9954 = 99082116
Result: 6949 * 6949 = 48288601
Result: 6421 * 6421 = 41229241
Result: 6683 * 6683 = 44662489
Result: 6562 * 6562 = 43059844
Result: 1352 * 1352 = 1827904
master exit.
子进程显示如下:
Connect to server 127.0.0.1…
run task 6792 * 6792…
run task 6251 * 6251…
run task 6774 * 6774…
run task 7543 * 7543…
run task 9954 * 9954…
run task 6949 * 6949…
run task 6421 * 6421…
run task 6683 * 6683…
run task 6562 * 6562…
run task 1352 * 1352…

总结:从这个例子来看,使用起来还是非常方便的,需要注意的是,需要处理相关异常,如主进程没有启动时,子进程启动会连接失败。

celery分布式框架实践

celery简介

Celery 是一个强大的 分布式任务队列 的 异步处理框架,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)。
Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis,memcached, mongodb,SQLAlchemy, Django ORM,Apache Cassandra, IronCache 等。
本例中broker和backend都选择redis。
celery的架构如下:

celery和redis安装

celery安装

pip install -U Celery

redis安装

$ wget http://download.redis.io/releases/redis-2.8.17.tar.gz
$ tar xzf redis-2.8.17.tar.gz
$ cd redis-2.8.17
$ make

make完后 redis-2.8.17目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli,两个程序位于安装目录 src 目录下:

下面启动redis服务.

$ cd src
$ ./redis-server

注意这种方式启动redis 使用的是默认配置。也可以通过启动参数告诉redis使用指定配置文件使用下面命令启动。

$ cd src
$ ./redis-server ../redis.conf

redis.conf 是一个默认的配置文件。我们可以根据需要使用自己的配置文件。

启动redis服务进程后,就可以使用测试客户端程序redis-cli和redis服务交互了。 比如:

$ cd src
$ ./redis-cli
redis> set foo bar
OK
redis> get foo
"bar"

最后安装python中的redis库,否则cellery提示会报错

pip install redis

celery实现简单计算

启动redis server服务
在redis/src目录下按以上步骤启动即可
编写任务代码tasks.py
backend和broker都用redis

from celery import Celery

# 我们这里案例使用redis作为broker和backend
app = Celery('demo',
             backend='redis://127.0.0.1:6379/2',
             broker='redis://127.0.0.1:6379/1')

# 创建任务函数
@app.task
def my_task(a, b):
    print("任务函数正在执行....")
    return a + b

注意:
这里没有使用redis密码,因为redis server默认启动是不带密码的,否则会出现Client sent AUTH, but no password is set的错误
将tasks模块加入worker

celery -A tasks worker --loglevel=info

可以看到打印以下信息,任务成功加入

- ** ---------- .> transport:   redis://127.0.0.1:6379/1
- ** ---------- .> results:     redis://127.0.0.1:6379/2
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . tasks.my_task

[2019-11-19 23:35:35,537: INFO/MainProcess] Connected to redis://127.0.0.1:6379/1
[2019-11-19 23:35:35,544: INFO/MainProcess] mingle: searching for neighbors
[2019-11-19 23:35:36,559: INFO/MainProcess] mingle: all alone
[2019-11-19 23:35:36,574: INFO/MainProcess] celery@huzhenghui-desktop ready.

执行任务,并将任务结果写入redis中:

>>> from tasks import my_task
>>> ret = my_task.delay(1,2)
>>> ret.result
3
>>> 
发布了127 篇原创文章 · 获赞 10 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/u012599545/article/details/103096511