2020.10.6 分布式进程
学习总结自网站:
https://blog.csdn.net/u011318077/article/details/88094583
https://www.liaoxuefeng.com/wiki/1016959663602400/1017631559645600#0
1、模块
multiprocessing.managers、queue
使用multiprocessing.managers中的BaseManager类,创建分布式管理器
使用queue.Queue()创建队列,用于多个进程间的通信
#master和worker进程中都要有一个QueueManager作为分布式管理器 class QueueManager(BaseManager): pass
#Queue只在master进程中创建,并发布到网络上 #worker进程接收网络中的Queue #两个Queue,一个用来发布任务的task_queue,一个用来接收结果的result_queue task_queue=queue.Queue() result_queue=queue.Queue()
2、原理
①managers子模块支持把多个进程分布到多台机器上
②写一个服务进程作为调度者,将任务分布到其他多个进程中,然后通过网络通信进行管理
比如:爬取图片
一个进程负责抓取地址,并将地址放在Queue(容器)队列中;
另一个进程负责从Queue队列中取出链接地址进行图片下载和存储到本地。
上述过程就可以做成分布式,一台机器负责获取地址,另一台机器负责下载存储。
问题的核心:将Queue队列暴露在网络中,使其他机器可以访问
3、实现步骤
①建立Queue队列,负责进程间的通信,任务队列task_queue,结果队列result_queue
task_queue=queue.Queue()
result_queue=queue.Queue()
②把第一步中的两个队列在网络中注册,注册时将队列重新命名。
def return_task_queue(): global task_queue return task_queue def return_result_queue(): global result_queue return result_queue QueueManager.register('get_task_queue',callable=return_task_queue) QueueManager.register('get_result_queue',callable=return_result_queue)
注意:
1、Windows下的callable参数不支持lambda匿名函数(否则会报错EOFError: Ran out of input),必须是自写函数的函数名
2、register的第一个参数标定了发布到网上的Queue的名字,在worker和master中必须一致;第二个参数callable关联的函数必须在master中已经实现,且该函数必须返回一个队列queue,这个队列就是我们发布到网上的队列。
③创建一个Queuemanager(BaseManager)的实例manager,相当于一个服务器,给定IP、端口、验证码
其中验证码authkey必须写成b'abc'的形式,不能写成'abc'
manager=QueueManager(address=('127.0.0.1',8001),authkey=b'abc')
④启动实例manager
manager.start()
⑤访问Queue对象,即创建网络中暴露重命名后的Queue实例
task=manager.get_task_queue()
result=manager.get_result_queue()
注意,不能直接访问之前我们在①中声明的Queue ,必须通过manager.get_task_queue()获得的Queue接口添加!
⑥创建任务到本地队列中,自动上传任务到网络队列中,分配给任务进程进行处理
#放入的任务数为循环的次数,通过task.put(n)放入 for i in range(10): n=random.randint(0,10) print('Put task %s ...'%n) task.put(n) #将n放入task队列
以上从①到⑥的代码都在服务进程master中,⑦在任务进程worker中
⑦任务进程从网络中的任务队列中取出任务,然后执行,将执行结果放入网络中的结果队列中
class QueueManager(BaseManager): pass
#A QueueManager.register('get_task_queue') QueueManager.register('get_result_queue')
#B m=QueueManager(address=('127.0.0.1',8001),authkey=b'abc')
#C m.connect()
#D task=m.get_task_queue() result=m.get_result_queue() #任务进程 #从task队列获取任务,并把结果放入result队列 for i in range(10): try: #获取任务 n=task.get(timeout=1)#每次等待1s后取出任务 #接下来几行是具体的任务执行 print('run task %d *%d ...'%(n,n)) r='%d * %d =%d...'%(n,n,n*n) time.sleep(1) #放入result队列 result.put(r) except queue.Empty: print('task queue is empty')
有四处需要注意的地方,分别用A,B,C,D标注在了上文代码中
A,任务进程worker中,调用register将Queue暴露在网络上时,不需要写callable参数,但第一个参数(即发布的queue的名字)需要与master中的一致。
B,worker中绑定端口,验证码的设置与服务进程master中的一致
C,worker中连接网络,用m.connect()方法;master中启动Queue,用m.start()方法
D,task与result的获取,和master一样,都要通过m.get_task_queue()与m.get_result_queue()
⑧服务进程从结果队列中取出结果,直到执行完所有任务和取出所有的结果,任务进程关闭,然后服务进程关闭( ⑧在服务进程master中 )
for i in range(11): try: r=result.get(timeout=5)#每次等待5s,取结果队列 #对结果队列的处理 print('Result:%s'%r) except queue.Empty: print('result queue is empty')
#关闭任务进程和服务进程 #在master中 manager.shutdown()
注意:
①先创建服务进程,再创建任务进程
②执行时,先启动服务进程,再创建任务进程,启动任务进程不要超过服务进程取出结果的等待时间
4、分布式进程实例
Q:创建一个分布式进程,用来完成10次乘法运算
服务进程
#服务进程在Windows和Linux系统上有所不同 #创建一个分布式进程:包括服务进程和任务进程 #多进程间通信用Queue #该代码为服务进程 #注意:运行时先运行服务进程,再运行任务进程 #服务进程与任务进程都创建了相同的两个队列: # 一个用来放任务task_queue # 另一个用来放结果result_queue #任务执行顺序: ''' 1、服务进程运行,比如将数字2放入任务队列,任务进程从任务队列中取出数字2 2、取出数字2后,执行任务,即2*2=4,任务结束后,放入结果队列 3、服务进程从结果队列中取出结果 4、所有任务执行完毕,所有结果都已经取出,最终任务队列和结果队列都是空的了 ''' #coding:utf-8 import queue,random from multiprocessing.managers import BaseManager from multiprocessing import freeze_support #第一步 定义两个队列,一个用于发送任务,另一个用于接收结果 task_queue=queue.Queue() result_queue=queue.Queue() #创建类似的QueueManager,继承自BaseManager,用于后面创建管理器 class QueueManager(BaseManager): pass #定义两个函数,返回结果就是Queue队列 def return_task_queue(): global task_queue return task_queue def return_result_queue(): global result_queue return result_queue #第二步,把上边创建的两个队列注册到网络上,用register方法 #callable参数关联了Queue对象,将Queue对象在网络中暴露 #第一个参数是注册在网络中的队列的名称 def test(): QueueManager.register('get_task_queue',callable=return_task_queue) QueueManager.register('get_result_queue',callable=return_result_queue) #第三步 绑定端口8001,设置验证口令,这个相当于对象的初始化 #绑定端口并填写验证口令,Windows下需填写IP地址,Linux下默认为本地,地址为空 manager=QueueManager(address=('127.0.0.1',8001),authkey=b'abc') #口令必须写成类似b'abc'形式,只写'abc'运行错误 #第四步 启动管理器,启动Queue队列,监听信息通道 manager.start() #第五步 通过管理实例的方法访问网络中的Queue对象 #即通过网络访问获取任务队列和结果队列,创建了两个Queue实例 task=manager.get_task_queue() result=manager.get_result_queue() #第六步 添加任务,获取返回的结果 #将任务放到Queue队列中 for i in range(10): n=random.randint(0,10) #返回0~10间的随机数 print('Put task %s ...'%n) task.put(n) #将n放入任务队列中 #从结果队列中取出结果 print('Try get results...') for i in range(11): #之所以循环11次,是用最后一次的结果来判断队列是否已经空了 #总共循环10次,上面放入了10个数字作为任务 #加载一个异常捕获 try: r=result.get(timeout=5)#每次等待5s,取结果队列中的值 print('Result:%s'%r) except queue.Empty: print('result queue is empty.') #最后一定要关闭服务,不然会报 管道未关闭 的错误 manager.shutdown() print('master exit.') if __name__=='__main__': #Windows下多进程可能出现问题,添加以下代码可缓解 freeze_support() print('Start!') #运行服务进程 test()
任务进程
#coding:utf-8 #定义具体的任务进程,具体的工作任务是什么 import time,sys,queue from multiprocessing.managers import BaseManager #创建类似的QueueManager,继承自BaseManager,用于后边创建管理器 class QueueManager(BaseManager): pass #第一步:使用QueueManager注册用于获取Queue的方法名称 #前面服务进程已经将队列名称暴露于网络中 #该任务进程注册时只需要提供名称即可,与服务进程中的队列名称一致 QueueManager.register('get_task_queue') QueueManager.register('get_result_queue') #第二步:连接到服务器,也就是运行服务进程代码的机器 server_addr='127.0.0.1' print ('Connect to server %s ...'%server_addr) #创建一个管理器实例,端口和验证口令保持与服务进程中完全一致 m=QueueManager(address=(server_addr,8001),authkey=b'abc') #连接到网络服务器 m.connect() #第三步:从网络上获取Queue对象,并进行本地化,与服务进程是同一队列 task=m.get_task_queue() result=m.get_result_queue() #第四步:从task队列获取任务,并把结果写入result队列 for i in range(10): try: #前面服务进程向task队列放入了n,这里取出n #n和n相乘,并将相乘的算式和结果放入result队列中去 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.Empty: print('task queue is empty.') #任务处理结束 print('Worker exit.')
启动分布式进程
先运行①服务进程,再运行②任务进程(不能只单独运行其中一个,必须先①后②运行)
服务进程运行结果
Start! Put task 8 ... Put task 6 ... Put task 3 ... Put task 4 ... Put task 7 ... Put task 6 ... Put task 10 ... Put task 6 ... Put task 7 ... Put task 10 ... Try get results... Result:8 * 8 = 64 Result:6 * 6 = 36 Result:3 * 3 = 9 Result:4 * 4 = 16 Result:7 * 7 = 49 Result:6 * 6 = 36 Result:10 * 10 = 100 Result:6 * 6 = 36 Result:7 * 7 = 49 Result:10 * 10 = 100 result queue is empty. master exit.
任务进程运行结果
Connect to server 127.0.0.1 ... run task 8 * 8 ... run task 6 * 6 ... run task 3 * 3 ... run task 4 * 4 ... run task 7 * 7 ... run task 6 * 6 ... run task 10 * 10 ... run task 6 * 6 ... run task 7 * 7 ... run task 10 * 10 ... Worker exit.
补充:
1、当我们在一台机器上写多进程程序时,创建的Queue可以直接拿来用。但是在分布式多进程环境下,添加任务到Queue不可以直接对原始的task_queue进行操作,那样就绕开了QueueManager的封装,必须通过manager.get_task_queue()获得的Queue接口进行添加(见 实现步骤⑤)。然后在另一台机器上启动任务进程(本机启动也可以)。
2、task_queue与result_queue是两个队列,分别存放任务和结果。它们用来进行进程间的通信,交换对象。
由于是分布式的环境,放入Queue中的数据需要等待任务进程Workers机器运算处理后再进行读取,
QueueManager.register('get_task_queue',callable=return_task_queue) QueueManager.register('get_result_queue',callable=return_result_queue)
这样就需要对queue用QueueManager进行封装放到网络中,这是通过以上两行代码实现的。我们给return_task_queue的网络调用接口取了一个名get_task_queue,而return_result_queue的名字是get_result_queue,方便区分对哪个queue进行操作,task.put(n)即是对task_queue进行写入数据,相当于分配任务。而result.get()则是等待Workers机器处理数据后返回的结果。
3、这个简单的Master/Worker模型有什么用?其实这就是一个简单但真正的分布式计算,把代码稍加改造,启动多个Worker,就可以把任务分布到几台甚至几十台机器上,比如把计算n*n的代码换成发送邮件,就实现了邮件队列的异步发送。
Queue的对象储存在哪?可以看到Worker文件中并没有创建Queue的代码,所以,Queue对象储存在Master文件中:
而Queue之所以能够通过网络访问,就是通过QueueManager(继承自BaseManager)实现的。由于QueueManager管理的不止一个Queue,所以要给每个Queue的网络调用接口起一个名字,比如get_task_queue(即register函数中的第一个参数)。并且task_worker和task_manager中QueueManager注册的名字必须一样。从上面的例子中可以看出,Queue对象从另一个进程通过网络传递了进来,只不过这里的传递和网络通信均由QueueManager完成。
authkey的作用:为了保证两台机器的正常通信,不被其他机器恶意干扰。只有worker和mastet的authkey一致,才能相互连接。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性