❶ python分布式进程中你会遇到的坑
写在前面
小惊大怪
你是不是在用Python3或者在windows系统上编程?最重要的是你对进程和线程不是很清楚?那么恭喜你,在python分布式进程中,会有坑等着你去挖。。。(hahahaha,此处允许我吓唬一下你)开玩笑的啦,不过,如果你知道序列中不支持匿名函数,那这个坑就和你say byebye了。好了话不多数,直接进入正题。
分布式进程
正如大家所知道的Process比Thread更稳定,而且Process可以分布到多台机器上,而Thread最多只能分布到同一陆坦咐台机器的多个CPU上。Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个进程中,依靠网络通信。由于managers模块封装很好,不必了解网络通信的细节,就可以很容易地编写分布式多进程程序。
代码记录
举个例子
如果我们已经有一个通过Queue通信的多进程程序在同一台机器上运行,现在,由于处理任务的进程任务繁重,希望把发送任务的进程和处理任务的进程分布到两台机器上,这应该怎么用分布式进程来实现呢?你已经知道了原有的Queue可以继续使用,而且通过managers模块把Queue通过网络暴露出去,就可以让其他机器的进程来访问Queue了。好,那我们就这么干!
写个task_master.py
我们先看服务进程。服务进程负责启动Queue,把Queue注册到网络上,然后往Queue里面写入任务。
请注意,当我们在一台机器上写多进程程序时,创建的Queue可以直接拿来用,但是,在分布式多进程环境下,添加任务到Queue不可以直接对原始的task_queue进行操作,那样就绕过了QueueManager的封装,必须通过manager.get_task_queue()获得的Queue接口添加。然后,在另一台机器上启动任务进程(本机上启动也可以)
写个task_worker.py
任务进程要通过网络连接到服务进程,所以要指定服务进程的IP。
运行结果
现在,可信没以试试分布式进程的工作效果了。先启动task_master.py服务进程:
task_master.py进程发送完任务后,开始等待result队列的结果。现在启动task_worker.py进程:
看到没,结果都出错了,我们好好分析一下到底哪出错了。。。
错误分析
在task_master.py的报错提示中,我们知道它说lambda错误,这是因为序列化不支持匿名函数,所以我们得修改代码,重新对queue用QueueManager进行封装放到网络中。
其中task_queue和result_queue是两个队列,分别存放任务和结果。它们用来进行进程间通信,交换对象。
因为是分布式的环境,放入queue中的数据需要等待Workers机器运算处理后再进行读取,这样就需要对queue用QueueManager进行封装放到网络中,这是通过上面的2行代码来实现的。我们给return_task_queue的网络调用接口取了一个名早纯get_task_queue,而return_result_queue的名字是get_result_queue,方便区分对哪个queue进行操作。task.put(n)即是对task_queue进行写入数据,相当于分配任务。而result.get()即是等待workers机器处理后返回的结果。
值得注意 在windows系统中你必须要写IP地址,而其他操作系统比如linux操作系统则就不要了。
修改后的代码
在task_master.py中修改如下:
在task_worker.py中修改如下:
先运行task_master.py,然后再运行task_worker.py
(1)task_master.py运行结果如下
(2)task_worker.py运行结果如下
知识补充
这个简单的Master/Worker模型有什么用?其实这就是一个简单但真正的分布式计算,把代码稍加改造,启动多个worker,就可以把任务分布到几台甚至几十台机器上,比如把计算n*n的代码换成发送邮件,就实现了邮件队列的异步发送。
Queue对象存储在哪?注意到task_worker.py中根本没有创建Queue的代码,所以,Queue对象存储在task_master.py进程中:
而Queue之所以能通过网络访问,就是通过QueueManager实现的。由于QueueManager管理的不止一个Queue,所以,要给每个Queue的网络调用接口起个名字,比如get_task_queue。task_worker这里的QueueManager注册的名字必须和task_manager中的一样。对比上面的例子,可以看出Queue对象从另一个进程通过网络传递了过来。只不过这里的传递和网络通信由QueueManager完成。
authkey有什么用?这是为了保证两台机器正常通信,不被其他机器恶意干扰。如果task_worker.py的authkey和task_master.py的authkey不一致,肯定连接不上。
❷ python面试之分布式
主要用于分散压力,所以分布式的服务都是部署在不同的服务器上的,再将服务做集群
根据“分层”的思想进行拆分。
例如,可以将一个项目根据“三层架构” 拆分
然后再分开部署 :
根据业务进行拆分。
例如,可以根据业务逻辑,将“电商项目”拆分成 “订单项目”、“用户项目”和“秒杀项目” 。显然这三个拆分后的项目,仍然可以作为独立的项目使用。像这种拆分的方法,就成为垂直拆分
主要用于分散能力,主要是将服务的颗粒度尽量细化,且自成一脉,压力这块并不是其关注的点,所以多个微服务是可以部署在同一台服务器上的
微服务可以理解为一种 非常细粒度的垂直拆分 。例如,以上“订单项目”本来就是垂直拆分后的子项目,但实际上“订单项目”还能进一步拆分为“购物项目”、“结算项目”和“售后项目”,如图
现在看图中的“订单项目”,它完全可以作为一个分布式项目的组成元素,但就不适合作为微服务的组成元素了(因为它还能再拆,而微服务应该是不能再拆的“微小”服务,类似于“原子性”)
分布式服务需要提供给别的分布式服务去调用,单独拆出来 未必外部可用
微服务自成一脉,可以系统内部调用,也可以单独提供服务
为什么需要用分布式锁,见下图
变量A存在三个服务器内存中(这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象),如果不加任何控制的话,变量A同时都会在分配一块内存,三个请求发过来同时对这个变量操作,显然结果是不对的!即使不是同时发过来,三个请求分别操作三个不同内存区域的数据,变量A之间不存在共享,也不具有可见性,处理的结果也是不对的。
分布式锁应该具备哪些条件:
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
Redis性能高
命令简单,实现方便
使用setnx加锁,key为锁名,value随意不重复就行(一般用uuid)
给锁添加expire时间,超过该时间redis过期(即自动释放锁)
设置获取锁的超时时间,若超过时间,则放弃获取锁
通过锁名获取锁值
比较锁值和当前uuid是否一致,一致则释放锁(通过delete命令删除redis键值对)
2PC:two phase commit protocol,二阶段提交协议,是一种强一致性设计。
同步阻塞(导致长久的资源锁定) ,只有第一阶段全部正常完成(返回失败,回字返回超时都会返回 “准备失败” ),才会进入第二阶段
因为协调者可能会在任意一个时间点(发送准备命令之前,发送准备命令之后,发送回滚事务命令之前,发送回滚事务命令之后,发送提交事务命令之前,发送提交事务命令之后)故障,导致资源阻塞。
T:try,指的是预留,即资源的预留和锁定,注意是预留
C:confirm,指的是确认操作,这一步其实就是真正的执行了
C:cancel,指的是撤销操作,可以理解为把预留阶段的动作撤销了
从思想上看和 2PC 差不多,都是先试探性的执行,如果都可以那就真正的执行,如果不行就回滚。
适用于对实时性要求没那么高的业务场景,如:短信通知