bigchaindb源码分析(一)——命令行参数与配置文件解析
来源:互联网 发布:禁止安装软件的软件 编辑:程序博客网 时间:2024/06/07 02:42
bigchaindb版本:
BigchainDB (1.0.0rc1)bigchaindb-driver (0.3.1)
命令行参数解析
使用whereis定位bigchaindb可执行文件为/usr/local/bin/bigchaindb
,该文件调用了bigchaindb.commands.bigchaindb.main()
函数。re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
相当于字符创的替换,将sys.argv[0]
的结尾字符-script.pyw
与.exe
替换成空。
root@bigchain:~# whereis bigchaindbbigchaindb: /usr/local/bin/bigchaindbroot@bigchain:~# cat /usr/local/bin/bigchaindb #!/usr/bin/python3# -*- coding: utf-8 -*-import reimport sysfrom bigchaindb.commands.bigchaindb import mainif __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit(main())root@bigchain:~#
main函数简单地执行了utils.start。其第一个参数为create_parser
函数,该函数利用argparse
模块定义了脚本能够解析的命令行参数,如configure\backend等等(称之为子命令),并且将解析到的子命令复制给command变量。
def main(): utils.start(create_parser(), sys.argv[1:], globals())def create_parser(): parser = argparse.ArgumentParser( description='Control your BigchainDB node.', parents=[utils.base_parser]) # all the commands are contained in the subparsers object, # the command selected by the user will be stored in `args.command` # that is used by the `main` function to select which other # function to call. subparsers = parser.add_subparsers(title='Commands', dest='command') # parser for writing a config file config_parser = subparsers.add_parser('configure', help='Prepare the config file ' 'and create the node keypair') ...
此外,解析的参数除了create_parser
函数之外,commands/utils/py
同样给出了一些可以解析的命令行参数,包括用来读取配置文件的-c
、日志输出级别的-l
、对于提示默认设置为yes的-y
以及用来查看版本信息的-v
。
base_parser = argparse.ArgumentParser(add_help=False, prog='bigchaindb')base_parser.add_argument('-c', '--config', help='Specify the location of the configuration file ' '(use "-" for stdout)')...
在定义了命令行解析器后,utils.start函数的第一步在于parse_args
来进行解析,并确保用来表征configure\backend\start等的command变量存在,若command变量不存在,则命令行输入未带有子命令,从而弹出help。
def start(parser, argv, scope): args = parser.parse_args(argv) if not args.command: parser.print_help() raise SystemExit()
start函数的第三个参数为scope,调用时的形参为globals()。globals是一个python的内置函数,用来获取该模块的名字空间,包括函数、类、其它导入的模块、模块级的变量和常量,并以字典形式返回。start函数在解析完命令行参数后,接下来的是根据子命令找到对应的要调用的函数。其中函数的名字为子命令字符串中将’-‘替换为’_’,并在字符创前面加入run_
。因此,当执行bigchaindb start
时,args.command
为start
,而func则为run_start
。若该模块中找不到func函数,则抛出NotImplementedError
异常。
此后还根据命令行参数来设置multiprocess
的值。之后调用func函数。
func = scope.get('run_' + args.command.replace('-', '_'))if not func: raise NotImplementedError('Command `{}` not yet implemented'. format(args.command))...return func(args)
执行子命令
以命令行bigchaindb start
为例,utils.start函数将调用run_start()
函数,该函数位于commands.bigchaindb
中。该函数拥有两个装饰器(decorator)。这意味着在调用run_start(args)
时,将会执行run_start=start_logging_process(configure_bigchaindb(run_start))
,之后才调用真正的run_start(args)
。装饰器的例子可以阅读博客(http://www.cnblogs.com/SeasonLee/articles/1719444.html),不过注意是先调用的configure_bigchaindb
。
@configure_bigchaindb@start_logging_processdef run_start(args):
我们先来阅读两个装饰器的代码,再来看run_start
函数
配置bigchaindb
configure_bigchaindb
位于commands.utils
中。
def configure_bigchaindb(command): @functools.wraps(command) def configure(args): try: print(">>> enter configure") config_from_cmdline = { 'log': { 'level_console': args.log_level, 'level_logfile': args.log_level, }, 'server': {'loglevel': args.log_level}, } except AttributeError: config_from_cmdline = None bigchaindb.config_utils.autoconfigure( filename=args.config, config=config_from_cmdline, force=True) command(args) return configure
此时传入的command可以看成是带有装饰器start_logging_process
的run_start
函数,因此,configure函数的最后一句command(args)
相当于执行了
@start_logging_processdef run_start(args): ...run_start(args)
也就是说,会先执行start_logging_process
,再执行真正的run_start
。至于configure函数上的装饰器@functools.wraps(command)
的目的在于确保原函数的一些属性不被装饰器函数所覆盖。如下面的例子,add
函数的__name__
已经被赋值为run
。而使用functools.wraps
能够确保原函数的属性不变。
>>> def test(func):... def run(x1, x2):... print("run>>")... return func(x1,x2)... return run>>> @test... def add(x1, x2):... print("x1+x2=%d" % (x1+x2))... add(1,2)run>>x1+x2=3>>> print(add.__name__)run
再来看configure
函数的具体内容,该函数调用了config_utils.autoconfigure
,第一个参数为命令行中输入的配置文件的路径,第二个参数为一个说明日志输出级别的字典。
def autoconfigure(filename=None, config=None, force=False): # start with the current configuration newconfig = bigchaindb.config # update configuration from file try: newconfig = update(newconfig, file_config(filename=filename)) except FileNotFoundError as e: if filename: raise else: logger.info('Cannot find config file `%s`.' % e.filename) # override configuration with env variables newconfig = env_config(newconfig) if config: newconfig = update(newconfig, config) set_config(newconfig) # sets bigchaindb.config
该函数首先将newconfig
设置为默认的配置(位于bigchaindb/__init.py
中),然后调用update来将配置文件中的json更新到newconfig
中。file_config
的作用在于使用json.load将配置文件中的json加载进来。update
函数如下。作用在于递归地遍历配置文件的json,将key value同步到newconfig
。
def update(d, u): for k, v in u.items(): if isinstance(v, collections.Mapping): r = update(d.get(k, {}), v) d[k] = r else: d[k] = u[k] return d
autoconfigure
之后再依次利用现有的环境变量、利用形参传入的说明日志级别字典来更新newconfig,最后将newconfig设置为当前bigchaindb实例所使用的配置。
我们先来看env_config
函数,该函数将一直调用到env_config->map_leafs->_inner
,_inner
拥有的两个变量分别为func指向函数load_from_env
、mapping指向newconfig
。_inner
的作用方式如上面的update
一样,递归遍历newconfig的值,对每个key调用load_from_env
来进行重新赋值,调用时第一个参数为newconfig中某个key的value,第二个参数为一个表示路径的path。
若newconfig中有一项{'database': {'host': 'localhost'}}
,那么load_from_env
的形参为localhost, ['database', 'host']
。而该函数的函数体则是根据path拼凑出环境变量的名字,再调用os.environ.get
来取该环境变量来更新newconfig
,若环境变量不存在,则依旧使用原来的value。
CONFIG_PREFIX = 'BIGCHAINDB'CONFIG_SEP = '_'def env_config(config): def load_from_env(value, path): var_name = CONFIG_SEP.join([CONFIG_PREFIX] + list(map(lambda s: s.upper(), path))) return os.environ.get(var_name, value) return map_leafs(load_from_env, config)def map_leafs(func, mapping): def _inner(mapping, path=None): if path is None: path = [] for key, val in mapping.items(): if isinstance(val, collections.Mapping): _inner(val, path + [key]) else: mapping[key] = func(val, path=path+[key]) return mapping return _inner(copy.deepcopy(mapping))
具体来看如何根据path来获取环境变量,即语句
var_name = CONFIG_SEP.join([CONFIG_PREFIX] + list(map(lambda s: s.upper(), path)))
lambda相当于是一个简单地函数,lambda s: s.upper()
的含义为对于输入的字符串s,返回s大写之后的字符串。而map(func, seq)
则是对序列seq的每一项用func进行计算,故[CONFIG_PREFIX] + list(map(lambda s: s.upper(), path))
返回将path中每个元素变为大写后的序列,并在该序列最前面插入一个元素CONFIG_PREFIX
。join函数则将序列转化为字符串,并且两个相邻元素之间用CONFIG_SEP
相连。因此,当load_from_env
的形参为localhost, ['database', 'host']
时,对应的环境变量为BIGCHAINDB_DATABASE_HOST
。
至此,autoconfigure
已经获取到了更新之后的newconfig
,最后一句set_config(newconfig)
将newconfig设置为目前的配置。其中利用到了函数update_types
,来利用map_leafs
来遍历newconfig,从而根据bigchaindb.__init__.py
中定义的config来更新newconfig的类型。配置完成!最终的配置存储在变量bigchaindb.config
中。
def set_config(config): # Deep copy the default config into bigchaindb.config bigchaindb.config = copy.deepcopy(bigchaindb._config) # Update the default config with whatever is in the passed config update(bigchaindb.config, update_types(config, bigchaindb.config)) bigchaindb.config['CONFIGURED'] = True
启动日志
在配置完成后将调用start_logging_process
。该函数在调用setup_logging
后将调用真正的run_start
。在启动日志时,bigchaindb使用publisher\subscriber的结构。
def start_logging_process(command): @functools.wraps(command) def start_logging(args): from bigchaindb import config setup_logging(user_log_config=config.get('log')) command(args) return start_loggingdef setup_logging(*, user_log_config=None): setup_pub_logger() setup_sub_logger(user_log_config=user_log_config)
setup_pub_logger
启动publisher,并打开DEFAULT_SOCKET_LOGGING_PORT
端口来创建一个socket handler。
def setup_pub_logger(): dictConfig(PUBLISHER_LOGGING_CONFIG) socket_handler = logging.handlers.SocketHandler( DEFAULT_SOCKET_LOGGING_HOST, DEFAULT_SOCKET_LOGGING_PORT) socket_handler.setLevel(logging.DEBUG) logger = logging.getLogger() logger.addHandler(socket_handler)
setup_sub_logger
使用配置文件中key为log
下的配置参数接收端口DEFAULT_TCP_LOGGING_PORT
的信息。这也意味着如果在同一节点上要启动两个bigchaindb实例将会打开两次端口DEFAULT_TCP_LOGGING_PORT
,会出现地址已经在使用的错。
def setup_sub_logger(*, user_log_config=None): server = LogRecordSocketServer() with server: server_proc = Process( target=server.serve_forever, kwargs={'log_config': user_log_config}, ) server_proc.start()class LogRecordSocketServer(ThreadingTCPServer): allow_reuse_address = True def __init__(self, host='localhost', port=logging.handlers.DEFAULT_TCP_LOGGING_PORT, handler=LogRecordStreamHandler): super().__init__((host, port), handler) def serve_forever(self, *, poll_interval=0.5, log_config=None): sub_logging_config = create_subscriber_logging_config( user_log_config=log_config) dictConfig(sub_logging_config) try: super().serve_forever(poll_interval=poll_interval) except KeyboardInterrupt: pass
run_start
终于到了真正的run_start
。忽略掉生成密钥等操作,该函数其实只调用了_run_init()
与process.start()
。前者会对后端存储的数据库进行一些初始化操作,包括创建数据库、创建表,以及创建创世区块。
@configure_bigchaindb@start_logging_processdef run_start(args): ... try: _run_init() except DatabaseAlreadyExists: pass except KeypairNotFoundException: sys.exit(CANNOT_START_KEYPAIR_NOT_FOUND) ... processes.start()def _run_init(): # Try to access the keypair, throws an exception if it does not exist b = bigchaindb.Bigchain() schema.init_database(connection=b.connection) b.create_genesis_block() logger.info('Genesis block created.')
process.start
则依次启动block、vote、stale、election等进程。
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']))
关于数据库以及这些进程的逻辑,下一篇再进行源码跟踪。。
- bigchaindb源码分析(一)——命令行参数与配置文件解析
- bigchaindb源码分析(二)——pipeline
- bigchaindb源码分析(三)——后端存储
- bigchaindb源码分析(七)——投票
- bigchaindb源码分析(八)——stale
- bigchaindb源码分析(九)——选举
- bigchaindb源码分析(十)——总结
- bigchaindb源码分析(五)——写事务(上)
- bigchaindb源码分析(六)——写事务(下)
- bigchaindb源码分析(四)——创建创世区块
- ibatis源码分析—配置文件解析(1)
- ibatis源码分析—配置文件解析(2)
- springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)
- linux 命令行参数解析源码
- Spring源码分析2 — XML配置文件的解析流程
- (一)MyBatis源码解析之配置文件
- 如何解析命令行参数——getopt与getopt_long
- ibatis源码分析—运行流程解析(一)
- C语言学习笔记——指针的作用
- springboot项目搭建(一)
- JS对象
- ubuntu16.04 安装 lib32readline-gplv2-dev 出错,解决办法
- 51nod1080 两个数的平方和
- bigchaindb源码分析(一)——命令行参数与配置文件解析
- C#从list里查找某个链表里的子项
- connect方法会阻塞,请问有什么方法可以避免其长时间阻塞?
- 基础知识
- hexo提交搜索引擎(百度+谷歌)
- 【MyBatis学习14】MyBatis和Spring整合
- goroutine背后的系统知识
- 225.Implement Stack using Queues(C语言版本)
- web.xml中dispatcher标签详解