场景:
主进程中初始化了一个对象(C++模块的对象,占用内存比较大,因为加载了一个很大的词表),然后启动子进程,并在子进程中使用这个对象,子进程对这个对象只读,不写
这个对象需要周期性的更新,因为要在对象中重新加载一些数据
如何不重启服务来更新这个对象呢?
首先:
子进程因为不会对这个对象写,所以它们使用的其实跟主进程是同一个对象,它不会复制一份,只有在子进程要写这个对象的时候才会复制一个对象,这是Copy-On-Write
策略一:
在主进程中初始化好这个对象后,通过Python的多进程共享变量的方式来让子进程和主进程共享这个对象
在主进程中开一个线程,启动一个timer来定时更新这个对象,当然这里要用双buffer,先初始化一个新的对象,然后切换过去,主进程和它的线程是共享内存的
这样子进程中就可以无缝使用到新的对象了,而且不会影响正常的服务
首先我们知道Python的多进程间可以通过这几种方式来共享数据:
a、共享数值型:multiprocessing.Value
b、共享数组型:multiprocessing.Array
c、共享dict类型,multiprocessing.Manager().dict()
d、共享list类型,multiprocessing.Manager().list()
注意哈:
比如multiprocessing.Manager().dict()类型,不同的进程可以直接使用dict['key']来获取对应的value,那么当一个进程中修改了它,另外一个进程中获取到的就直接是最新的了,这里不需要感知变化。
其中Value是共享数值型,另外三种是集合,那是否可以通过这几个集合的方式,在集合中传入自定义的对象来共享呢?
结论:简单的自定义对象可以(因为可以序列化和反序列化),复杂的对象不可以
比如这样一个简单的对象就可以在多进程间通过multiprocessing.Manager().dict()共享:
class TestObj:
def __init__(self, age):
self.age = age
但是更复杂一点的就不行了,比如上面说的这个从C++的so文件中加载的一个对象就无法通过multiprocessing.Manager().dict()共享, 会报如下错误:
File "/search/odin/software/anaconda2/lib/python2.7/multiprocessing/managers.py", line 758, in _callmethod
conn.send((self._id, methodname, args, kwds))
RuntimeError: Pickling of "baike_matcher.BaikeMatcher" instances is not enabled (http://www.boost.org/libs/python/doc/v2/pickle.html)
可以看到是在conn.send的时候报错,说我自定义的这个对象不支持。
其实这个错误涉及到Pickling关键词,这个其实是序列化相关的,问题其实就是pikcle无法对BaikeMatcher对象序列化, 可以尝试直接用pickle来序列化:
import pickle
print pickle.dumps(BaikeMatcher(10), 1)
发现报错和上面一样
问题:
这个C++的对象没有办法序列化,即使在C++代码中给这个对象定义了序列化和反序列化的方法,pickle应该也无法调用它,所以原则上来说,它是无法序列化的。
既然无法序列化,那么通过共享的方式来共享这个对象的方法看样子也是行不通了。
后面也尝试了使用multiprocessing.managers.BaseManager对象来尝试共享变量,但是其实遇到的问题和上面是一样的,BaikeMatcher对象无法序列化。
策略二:
直接在每个子进程中分别起一个线程,由每个子进程的线程自己来更新这个对象,这样就不会涉及到不同的进程间共享变量的事情了
问题:
这个对象占用内存太大,如果每个子进程自己加载,就会导致每个进程维护一个不同的对象了,而每个对象都会占用一部分内存,导致内存严重浪费。
这跟在主进程中更新这个对象大有不同,在主进程中更新只会占用一份内存
策略三:
将这个对象提供的功能部署成一个服务,并部署到本地docker容器
因为我们的服务最终都会部署到kubernetes上去,而kubernetes中用到了docker容器,并且提供了通过localhost来调用本地依赖容器中服务的功能,最终主服务部署的容器和依赖服务部署的容器将会一起打包发布到一台宿主机上,这样主服务就可以通过localhost的方式来调用其他容器中的服务了,依赖服务中我们采用thrift来部署服务的。
具体策略:
. 修改该C++服务的代码,让它自己单独起一个线程去定时加载需要的数据并更新相关变量,C++的多线程可以真正的实现并发,而这些线程是共享内存的,所以不涉及Python的多进程共享变量的问题。
. 将该对象提供的服务部署到一个单独的docker容器中,并通过thrift提供服务