bigchaindb源码分析(二)——pipeline
来源:互联网 发布:淘宝退款了货收到了 编辑:程序博客网 时间:2024/06/06 20:51
bigchaindb源码分析(一)分析了bigchaindb如何解析命令行参数与配置文件,并据此启动了日志publisher与subscriber。本节来分析bigchaindb的pipeline机制。
之前说到,对于命令行命令bigchaindb start
,将会调用commands.bigchaindb.run_start()
。该函数完成了生成公私钥、初始化数据库以及启动所必要的进程。process.start()
将完成进程启动任务。
def start(): events_queue = setup_events_queue() # start the processes logger.info('Starting block') block.start() logger.info('Starting voter') vote.start() logger.info('Starting stale transaction monitor') stale.start() logger.info('Starting election') election.start(events_queue=events_queue) # start the web api app_server = server.create_server(bigchaindb.config['server']) p_webapi = mp.Process(name='webapi', target=app_server.run) p_webapi.start() logger.info('WebSocket server started') p_websocket_server = mp.Process(name='ws', target=websocket_server.start, args=(events_queue,)) p_websocket_server.start() # start message logger.info(BANNER.format(bigchaindb.config['server']['bind']))
其中setup_events_queue
仅仅是返回一个Queue()
实例。而之后的几个start操作(具体来说是block/vote/election)基本大同小异:都是创建一个pipeline、setup并启动该pipeline。
# pipeline/block.pydef start(): pipeline = create_pipeline() pipeline.setup(indata=get_changefeed()) pipeline.start() return pipeline# pipeline/vote.pydef start(): pipeline = create_pipeline() pipeline.setup(indata=get_changefeed()) pipeline.start() return pipeline# pipeline/election.pydef start(events_queue=None): pipeline = create_pipeline(events_queue=events_queue) pipeline.setup(indata=get_changefeed()) pipeline.start() return pipeline
以block.start为例,创建pipeline的函数如下。Pipeline的构造函数中输入的为一个list,其中第一个元素是一个队列,其他每个元素对应到类BlockPipeline的成员函数。暂时按照名称来猜测,其作用应当是按照这个list的顺序来处理输入的数据。
def create_pipeline(): block_pipeline = BlockPipeline() pipeline = Pipeline([ Pipe(maxsize=1000), Node(block_pipeline.filter_tx), Node(block_pipeline.validate_tx, fraction_of_cores=1), Node(block_pipeline.create, timeout=1), Node(block_pipeline.write), Node(block_pipeline.delete_tx), ]) return pipelinedef Pipe(maxsize=0): return queues.Queue(maxsize, ctx=mp.get_context())
Node与Pipeline类均来自于一个额外的包multipipes
,这个包下除了__init__.py
也就一个pipeline.py
。。
Node
我们先来看看Node类。其__init__
函数接收的第一个参数为target,若第一个参数不存在时,target被设置为pass_through
用来将参数进行直接返回。在进行一系列的合法性判定后设置了该Node能够开启的进程数为self.number_of_processes
,其后利用multiprocessing模块来创建这些进程,每个进程运行的目标为self.safe_run_forever
函数。
def pass_through(val): return valclass Node: def __init__(self, target=None, inqueue=None, outqueue=None, name=None, timeout=None, number_of_processes=None, fraction_of_cores=None): self.target = target if target else pass_through ... self.processes = [mp.Process(target=self.safe_run_forever) for i in range(self.number_of_processes)]
self.safe_run_forever
将调用run_forever
,来进入无限循环,并检测按键中断异常。循环中将一直调用run
函数。run
函数去掉一些判定检查后可以简化为如下步骤:
- 如果有输入队列,则从输入队列中获取本Node操作的参数
- 执行target程序
- 如果有输出队列,则将target程序的执行结果放入输出队列中(为list则一个个放入输出队列中)
def run(self): if self.inqueue: args = self.inqueue.get(timeout=self.timeout) result = self.target(*args) if result is not None and self.outqueue: self.outqueue.put(result)
除此之外,还对外提供了start、terminate、is_alive
等函数来启动、终止这些进程或者判断进程是否还存活。另外还提供了poison_pill
函数,往输入队列中加入进程数个POISON_PILL
,而在run时发现输入队列队首为POISON_PILL
时,在执行一次target程序后,将退出Node的无限循环。
pipeline
在知道Node其实就是一个用来执行target程序的多进程程序后,我们来分析pipeline类,pipeline的__init__
函数如下
class Pipeline: def __init__(self, items): self.items = items self.setup() def setup(self, indata=None, outdata=None): items_copy = self.items[:] if indata: items_copy.insert(0, indata) if outdata: items_copy.append(outdata) self.nodes = [item for item in items_copy if isinstance(item, Node)] self.connect(items_copy, False)
__init__
将调用setup函数,将构造函数传入的list中的所有属于Node实例的元素保存到成员属性self.nodes中,之后调用connect函数
def connect(self, rest, pipe=None): if not rest: return pipe head, *tail = rest if isinstance(head, queues.Queue): if pipe: raise ValueError('Cannot have two or more pipes next' ' to each other.') return self.connect(tail, pipe=head) elif isinstance(head, Node): if pipe is None: pipe = Pipe() if pipe is not False: head.inqueue = pipe head.outqueue = self.connect(tail) return head.inqueue
附带说明下*
和**
前缀,这些可以用来获取可变数量的参数。在args变量前有*
前缀 ,所有多余的函数参数都会作为一个元组存储在args中 。如果使用的是**
前缀 ,多余的参数则会被认为是一个字典的健/值对 。
- 对于
def func(*args):
,*args
表示把传进来的位置参数存储在tuple(元组)args里面。例如,调用func(1, 2, 3) ,args就表示(1, 2, 3)这个元组 。 - 对于
def func(**args):
,**
args表示把参数作为字典的健-值对存储在dict(字典)args里面。例如,调用func(a=’I’, b=’am’, c=’wcdj’) ,args就表示{‘a’:’I’, ‘b’:’am’, ‘c’:’wcdj’}这个字典 。
回到__init__
调用setup调用connect时,参数rest是一个list,list的第一个元素是队列,其后的都是Node。head将指向队列,而tail指向所有的Node组成的list。
- 首次进入connect时,由于head是Queue的实例,将进入if分支,从而再次调用connect
- 第二次进入connect时,head将指向第一个Node,pipe则指向队列,从而进入elif分支,并将第一个Node的inqueue设置为队列,进入第三次connect
- 第三次进入connect时,head指向第二个Node,pipe为None,进入elif…
- head为最后一个Node时,tail为空list,connect(tail)将返回pipe,而此时pipe为None,故最后一个Node的inqueue为一个新的Pipe(),而outqueue为None,并返回inqueue来作为上一层的outqueue
因此,__init__()
将构成一个链式结构。队列为第一个Node的inqueue,后面每个Node的inqueue为前一个Node的outqueue,最后一个Node的outqueue为None,从而形成pipeline
start
start的第一条则是创建了一个上面说明的链式结构,对于block.py来说,其pipeline中对应的都是要依次执行BlockPipeline
中的几个函数,而每个Node会从inqueue取参数,执行target程序,将结果放入outqueue,这个outqueue又连上下一个Node的inqueue,如此,pipeline就正式构建成功。
# pipeline/block.pydef start(): pipeline = create_pipeline() pipeline.setup(indata=get_changefeed()) pipeline.start() return pipeline
start在构建完pipeline之后又执行了一次setup,并给出了一个indata。回顾setup代码,当indata不为None时,setup将indata放在pipeline的最前面。相当于indata的outqueue指向队列,而第一个node的inqueue也指向队列。其实也就是将indata给出的数据放入到第一个node的inqueue了。
def setup(self, indata=None, outdata=None): items_copy = self.items[:] if indata: items_copy.insert(0, indata) if outdata: items_copy.append(outdata)
重新将pipeline构建完成后,再调用pipeline.start()
,也就是把所有Node的进程启动起来,每个Node开始从inqueue取数据,从outqueue输出数据给下一个Node。。。
def start(self): for node in self.nodes: node.start()
因此,只要indata还产生数据到队列里,pipeline就会流水线地处理这个数据。而get_changefeed
应当是后端存储变更所产生的反馈了。。等分析后端存储时再来看吧。。
再回过头来看bigchaindb在最开始启动的这些进程:
def start(): events_queue = setup_events_queue() # start the processes logger.info('Starting block') block.start() logger.info('Starting voter') vote.start() logger.info('Starting stale transaction monitor') stale.start() logger.info('Starting election') election.start(events_queue=events_queue) # start the web api app_server = server.create_server(bigchaindb.config['server']) p_webapi = mp.Process(name='webapi', target=app_server.run) p_webapi.start() logger.info('WebSocket server started') p_websocket_server = mp.Process(name='ws', target=websocket_server.start, args=(events_queue,)) p_websocket_server.start() # start message logger.info(BANNER.format(bigchaindb.config['server']['bind']))
block/vote/election的结构基本都是一样的了,等待各自的反馈出现,出现后调用对应类的函数按流水线来进行处理..stale虽然有所不同,但也区别不大了。除此之外也就还启动了一个web api,这个下次再看…
- bigchaindb源码分析(二)——pipeline
- bigchaindb源码分析(三)——后端存储
- bigchaindb源码分析(七)——投票
- bigchaindb源码分析(八)——stale
- bigchaindb源码分析(九)——选举
- bigchaindb源码分析(十)——总结
- bigchaindb源码分析(五)——写事务(上)
- bigchaindb源码分析(六)——写事务(下)
- bigchaindb源码分析(一)——命令行参数与配置文件解析
- bigchaindb源码分析(四)——创建创世区块
- netty4源码分析——pipeline关联
- jedis 源码阅读三——PipeLine
- Scrapy源码分析-Item Pipeline中文文档(四)
- Android Contacts源码分析二——(PeopleActivity)分析
- pipeline之stage(二)
- GCC源码分析(二)——前端
- Android开发——MediaProvider源码分析(二)
- Cordova CLI源码分析(二)——package.json
- ASP.NET MVC与MongoDB的整体搭建
- java.lang.Number源码
- 关于用JavaScript获取session值并阻止页面跳转的问题
- 《Angular2入门系列基础》——angular2组件入门(一)
- NOIP2012同余方程
- bigchaindb源码分析(二)——pipeline
- C++略识之构造函数
- ACTIVEMQ设置Timestamp和jms简介
- 数组进阶对象数组
- NOIP 2004 合并果子 题解 (堆 )
- LeetCode-543. Diameter of Binary Tree (Java)
- 深入解析php中的foreach问题
- mybatis批量操作中<foreach>标签的使用
- 深入理解 Android NDK 编译(二)