stackstorm 28. 源码分析之----stackstorm的resultstracker服务分析

本文详细解析了StackStorm中的ResultTracker服务,从主入口点开始,逐步剖析`st2resultstracker`如何启动、工作原理,特别是`_bootstrap()`方法中的查询状态处理和`_fire_queries()`方法中的查询执行流程。ResultTracker主要用于监测长时间运行的工作流任务,通过查询模块(如mistral_v2)获取执行状态,更新到St2的liveaction和actionexecution记录中,确保状态同步。
摘要由CSDN通过智能技术生成

目标:
弄清楚st2resultstracker服务原理

调试命令:
sudo /opt/stackstorm/st2/bin/st2resultstracker --config-file /etc/st2/st2.conf

1 主入口
/opt/stackstorm/st2/lib/python2.7/site-packages/st2actions/cmd/st2resultstracker.py

def _run_worker():
    LOG.info('(PID=%s) Results tracker started.', os.getpid())
    tracker = resultstracker.get_tracker()
    try:
        tracker.start(wait=True)
    except (KeyboardInterrupt, SystemExit):
        LOG.info('(PID=%s) Results tracker stopped.', os.getpid())
        tracker.shutdown()
    except:
        return 1
    return 0

def main():
    try:
        _setup()
        return _run_worker()
    except SystemExit as exit_code:
        sys.exit(exit_code)
    except:
        LOG.exception('(PID=%s) Results tracker quit due to exception.', os.getpid())
        return 1
    finally:
        _teardown()


分析:
1.1 重点是
tracker = resultstracker.get_tracker()
tracker.start(wait=True)
先分析  tracker = resultstracker.get_tracker()
进入:
st2/st2actions/st2actions/resultstracker/resultstracker.py

def get_tracker():
    with Connection(transport_utils.get_messaging_urls()) as conn:
        return ResultsTracker(conn, [RESULTSTRACKER_ACTIONSTATE_WORK_QUEUE])

分析:
1.1.1) 对于RESULTSTRACKER_ACTIONSTATE_WORK_QUEUE
(Pdb) p RESULTSTRACKER_ACTIONSTATE_WORK_QUEUE
<unbound Queue st2.resultstracker.work -> <unbound Exchange st2.actionexecutionstate(topic)> -> create>
那实际从exchange经过routing_key绑定到队列的关系如下:
st2.actionexecutionstate--->create--->st2.resultstracker.work

1.1.2) 分析ResultsTracker
class ResultsTracker(consumers.MessageHandler):
    message_type = ActionExecutionStateDB

    def __init__(self, connection, queues):
        super(ResultsTracker, self).__init__(connection, queues)
        self._queriers = {}
        self._query_threads = []
        self._failed_imports = set()

分析:
ResultsTracker本质是一个消息处理器,实现了自己的process方法进行消息的处理。

1.2 分析tracker.start(wait=True)
class ResultsTracker(consumers.MessageHandler):

   def start(self, wait=False):
        self._bootstrap()
        super(ResultsTracker, self).start(wait=wait)

进入_bootstrap(),代码如下:
    def _bootstrap(self):
        all_states = ActionExecutionState.get_all()
        LOG.info('Found %d pending states in db.' % len(all_states))

        query_contexts_dict = defaultdict(list)
        for state_db in all_states:
            try:
                context = QueryContext.from_model(state_db)
            except:
                LOG.exception('Invalid state object: %s', state_db)
                continue
            query_module_name = state_db.query_module
            querier = self.get_querier(query_module_name)

            if querier is not None:
                query_contexts_dict[querier].append(context)

分析:
1.2.1)
    _bootstrap(self):
    1 调用ActionExecutionState.get_all()向action_execution_state_d_b查询得到动作执行状态列表,返回样例如下:
      [<ActionExecutionStateDB: ActionExecutionStateDB(execution_id=5e952199d53a7e0001e62a44, id=5e952199bf92d90001c49cc2, query_context={u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}, query_module="mistral_v2")>]
      对动作执行状态列表中每个动作执行状态对象,生成对应的查询上下文对象,样例如下:
      <QueryContext id=5e952199bf92d90001c49cc2,execution_id=5e952199d53a7e0001e62a44,query_context={u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}>
      获取查询上下文对象的查询模块(例如:u'mistral_v2')
    2 调用get_querier(self, query_module_name):
            总结: 根据query_module名称(例如u'mistral_v2'),在<查询模块名,查询模块对象>字典中查找有无该查找对象。
                  若有,则直接返回;
                  否则实例化一个mistral_v2.MistralResultsQuerier对象,开启死循环,每隔5秒,
                    从队列中获取待查询对象,根据st2的execution_id查询到liveaction,根据mistral的execution_id查询到workflow状态以及该工作流的task列表,
                    对每个task根据task id查询得到task具有信息,返回到最终的结果列表中。
                    根据mistral工作流状态,以及st2中execution.child中每个child execution状态来确定execution的状态,返回该状态以及结果(字典)
                    根据execution_id查询到st2中liveaction,设置liveaction的status为输入参数指定的status;设置liveaction的results为输入参数指定的results;更新该liveaction。
                    根据liveaction_db的id,向action_execution_d_b中查询获取execution记录,并利用liveaction_db的信息更新execution记录。
                    根据exchange: st2.liveaction和routing_key: 'update',发送liveaction更新消息。
                    根据query_context.id查询到ActionExecutionStateDB对象并从数据库中删除该对象
            返回:querier对象,例如:
                <mistral_v2.MistralResultsQuerier object at 0x485f790>
    3 遍历待查询对象列表,将每个待查询对象和当前时间的二元组放入查询对象队列中

1.2.2)分析
querier = self.get_querier(query_module_name)
代码如下:
    def get_querier(self, query_module_name):
        if (query_module_name not in self._queriers and
                query_module_name not in self._failed_imports):
            try:
                query_module = register_query_module(query_module_name)
            except:
                LOG.exception('Failed importing query module: %s', query_module_name)
                self._failed_imports.add(query_module_name)
                self._queriers[query_module_name] = None
            else:
                querier = query_module.get_instance()
                self._queriers[query_module_name] = querier
                self._query_threads.append(eventlet.spawn(querier.start))

        return self._queriers[query_module_name]

分析:
1.2.2.1) 查看 query_module = register_query_module(query_module_name)代码如下
def register_query_module(module_name):
    base_path = cfg.CONF.system.base_path
    module_path = os.path.join(base_path, 'runners', module_name, 'query', module_name + '.py')

    if module_name not in QUERIER_MODULES_CACHE:
        LOG.info('Loading query module from "%s".', module_path)
        QUERIER_MODULES_CACHE[module_name] = imp.load_source(module_name, module_path)
    else:
        LOG.info('Reusing query module "%s" from cache.', module_path)

    return QUERIER_MODULES_CACHE[module_name]

分析:
register_query_module(module_name):
1 根据输入参数,形如:
    (Pdb) p module_name
    u'mistral_v2'
2 获取module路径(例如:'/opt/stackstorm/runners/mistral_v2/query/mistral_v2.py')
3 调用imp.load_source(name,pathname[,file])的作用把源文件pathname导入到name模块中,
name可以是自定义的名字或者内置的模块名称。
4 返回该导入的模块,形如:
<module 'mistral_v2' from '/opt/stackstorm/runners/mistral_v2/query/mistral_v2.pyc'>


1.2.2.2)分析self._query_threads.append(eventlet.spawn(querier.start))
主要就是querier.start
进入:
st2/st2common/st2common/query/base.py代码如下
    def start(self):
        self._started = True
        while True:
            while self._query_contexts.empty():
                eventlet.greenthread.sleep(self._empty_q_sleep_time)
            while self._thread_pool.free() <= 0:
                eventlet.greenthread.sleep(self._no_workers_sleep_time)
            self._fire_queries()
            eventlet.sleep(self._query_interval)

分析:
Querier.start(self):
如果查询队列是不为空,且协程池中空闲worker个数大于0,则调用_fire_queries方法,然后sleep 5秒,继续循环执行。 
对于真正进行查询和处理的方法_fire_queries的分析参见2

2 对_fire_queries分析
@six.add_metaclass(abc.ABCMeta)
class Querier(object):

    def _fire_queries(self):
        if self._thread_pool.free() <= 0:
            return

        now = time.time()
        reschedule_queries = []

        while not self._query_contexts.empty() and self._thread_pool.free() > 0:
            (last_query_time, query_context) = self._query_contexts.get_nowait()
            if now - last_query_time < self._query_interval:
                reschedule_queries.append((last_query_time, query_context))
                continue
            else:
                self._thread_pool.spawn(
                    self._query_and_save_results,
                    query_context,
                    last_query_time
                )

        for query in reschedule_queries:
            self._query_contexts.put((query[0], query[1]))

分析:
2.1)
Querier._fire_queries(self):
1 从查询队列中获取一个查询对象,如果当前时间减去上次查询时间还没有超过查询时间间隔,则放入
    reschedule_queries列表中后面会再次存放到查询队列中;否则执行2
2 使用协程池执行_query_and_save_results方法,具体如下:
    _query_and_save_results(self, query_context, last_query_time=None):
    1 根据输入参数,形如:
        (Pdb) p query_context
        <QueryContext id=5e952199bf92d90001c49cc2,execution_id=5e952199d53a7e0001e62a44,query_context={u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}>
        (Pdb) p last_query_time
        1586837111.809033
    2 获取查询的execution_id和查询上下文,形如:
        (Pdb) p query_context.execution_id
        '5e952199d53a7e0001e62a44'
        (Pdb) p query_context.query_context
        {u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}
    3 调用查询器的query方法,具体如下:
    3 遍历需要再次调度的reschedule_queries,将数据再次入队

2.2 分析_query_and_save_results方法
代码如下:
    def _query_and_save_results(self, query_context, last_query_time=None):
        this_query_time = time.time()
        execution_id = query_context.execution_id
        actual_query_context = query_context.query_context

        LOG.debug('Querying external service for results. Context: %s' % actual_query_context)
        try:
            (status, results) = self.query(
                execution_id,
                actual_query_context,
                last_query_time=last_query_time
            )
        except:
            LOG.exception('Failed querying results for liveaction_id %s.', execution_id)
            if self.delete_state_object_on_error:
                self._delete_state_object(query_context)
                LOG.debug('Removed state object %s.', query_context)
         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值