货拉拉移动端智能排障体系建设

1,965 阅读15分钟

一、背景

在移动互联网时代,用户体验是应用成功的关键因素之一。然而,即使是最精心设计的应用也难免会遇到各种问题和bug。这些问题不仅会影响用户的使用体验,还可能导致用户流失。因此,及时、高效地处理线上用户反馈显得尤为重要。

货拉拉移动端目前拥有一个完善的用户反馈处理系统,能够及时收到用户反馈,并分发给相应的值班产品经理、测试人员和开发人员进行处理。然而,该系统每天单端app收到约800条反馈,除了软件体验问题,还有大量各个模块的业务问题。目前,这些反馈依赖人工值班分发处理,反馈的处理时效大约在T+1到T+3天之间,这其中存在很大的提效空间。

研发测试同学每天焦头烂额忙着交付需求的同时,还要抽出大量时间排查用户反馈(大部分反馈并不是问题,但是必须排查确认,避免造成线上事故)。因此,如何快速地识别出其中的疑似bug,并且定位解决,就是我们希望通过打造智能排障体系来完成的课题。

1.png

二、产品方案

1. 提效思路

在设计这个系统之前,我们先回溯了所有的用户反馈,发现用户每天反馈的问题都高度集中在部分模块:例如下不了单、对支付有疑问、订单状态不对等,而研发在排查这些问题的时候,每一类问题的排查流程和思路基本一致,以下不了单为例,研发基本都是按以下步骤来分析日志:

2.png

当我们收到一条用户反馈,说“我这个号码怎么下不了单?怎么下不了单?”,研发一般会按以下步骤去分析排查:

  1. 分析app的离线日志,找到用户反馈时间前的下单请求,查看下单请求是否有报错,有报错就转后端跟进
  2. 下单接口没有报错,再去找计价接口的日志(下单前必须计价成功),查看计价请求是否有报错,有报错就转后端跟进
  3. 如果两个接口都没有报错记录,或者压根没有请求记录,就看用户是不是遇到闪退卡死问题了
  4. 如果也没有闪退卡死,那么就逐条分析用户行为日志、页面日志、网络日志,来模拟用户行为,尝试复现

所有下不了单的用户反馈,基本都是这样的排查步骤。而这些相似的排查步骤,我们是可以抽象、提取成为一个个的排障脚本,让脚本来自动分析日志,提取关键信息,从而释放研发人力、提高排障效率。

因此,我们对于「智能排障体系」的整体产品思路是:系统识别疑似bug ,自动进行排障流程,并给出排障结论。 这个思路提取出来两个关键词就是:智能化、自动化

2. 产品设计

整个排障体系将会基于我们现有的 反馈系统、日志系统进行新功能开发。

先给大家看下最终我们的交付成果👇🏻

3. 排障示例

  • 反馈系统:查看反馈详情时,页面上已经有了排障结论,无需研发测试再去排查

  • 日志系统:管理排障脚本,支持手动使用排障脚本解析日志,一键排障

产品方案其实比较清晰,但如何实现智能化和自动化,让产品真正有用好用,技术实现上我们还是遇到了比较多决策点。

三、技术方案

我们想做的不仅仅是对bug的分析,我们还有更高的追求。具体我们的技术方案分为两步走。

1. AI模型识别疑似bug

 1. 识别疑似bug

4.png

这部分主要有两个动作:

  1. 由业务方添加关键词词库,通常是业务核心服务,且用户反馈经常提到的一些词汇,比如冷藏车、拼车等等

  2. 训练疑似bug识别AI模型:

    1. 历史QAMP(内部bug管理系统)打标的疑似bug反馈数据作为模型的训练集
    2. 对识别数据进行正向反馈校准(人工对AI识别的疑似bug二次确认)自动加入训练集,模型在线自动训练迭代更新

2. 通过疑似bug发现新版本引入的新问题

能从用户反馈中识别出疑似bug还只是第一步,移动端的新版本覆盖有个缓慢的过程(用户很少主动更新app版本),假如新版本有线上问题,一般不会像服务端一样大面积爆发。

我们想要在版本覆盖量扩大之前,提前通过用户反馈识别到新版本新问题,因此迭代了AI模型2.0,在识别疑似bug的基础上,进行问题分类,高频新问题会及时告警。

在实现这个需求的过程中,我们尝试过使用GPT通用大模型进行语义提取,得到的结果是不稳定,有的太啰嗦并且不能准确按照要求给出语义,如下图示例:

其中的定位问题,不同的反馈描述,通用大模型会提取成定位问题、位置问题或者导航问题、甚至司机收藏问题,不好进行趋势聚合。

后来,我们又训练了微调私有垂直领域小参数大模型(0.4b),并且按以下流程进行大模型的自动迭代:

  1. 对发现的疑似bug数据进行语义提取分类

  2. 从语义提取分类中识别新问题和历史问题,分别预警

  3. 迭代大模型:

    1. 对识别不准确的数据进行在线标注校准。
    2. 迭代微调更新模型。

5.png

最后的聚合效果如下,比通用大模型的分类效果有很大提升。

(如果大家对这部分的技术细节有兴趣,可以在评论区留言,后面让相关同学再做个专题分享。)

2. 选择脚本语言

在做技术方案评审的时候,大家有一个争议点,就是脚本语言选哪个,用Python还是JavaScript。我们先来看下二者各方面的对比

其实从上手门槛和开发成本来看,Python和JavaScript之间差别不大,但考虑到我们项目的使用场景,有几个重要的诉求和特点:

  • 脚本需要支持服务器端执行,同时也需要支持在浏览器环境中执行

  • 多个脚本需要能够自动、并行执行

  • 解析的数据主要为文本和JSON,提取关键信息并输出,不涉及复杂计算

  • 解析的日志量用户端一般在一千条左右,司机端在一万条左右

对于一万条数据的文本和JSON,如果脚本逻辑相对简单,并且没有大量计算密集型操作,那么脚本的执行时间通常在几秒钟到几分钟之间,用Python和JavaScript差别不大。

综上对比和业务场景需求,虽然Python在复杂数据处理方面可能更为适合,但考虑到浏览器环境执行的要求,我们最终选择了使用JavaScript的方案

3. 选择脚本执行环境

这里其实选择很少了,我们的产品方案要求既要支持服务端解析,也要支持浏览器解析,并且我们的脚本语言是 JavaScript,那么脚本执行执行环境最佳选择就是 Node.js。

关键的技术选型问题解决后,接下来就给大家介绍下我们整体的技术方案。

4. 系统交互流程

6.png

整个排障体系涉及到两个系统和四个服务的交互:用户反馈系统、APP日志系统;以及feedback-server、octopus- server、node-server、log-server。

feedback 服务负责校验、存储反馈数据,关联离线日志任务;octopus服务消费反馈数据,触发node服务解析脚本;log服务组装日志包和脚本数据,从Kafka消费解析结果,更新任务状态。

node服务主要负责使用排障脚本解析离线日志,基于轻量级koa2框架实现,是整个体系中完全从0到1搭建的,也是我们开发工作量最重,调试过程中遇到问题最多的一部分,接下来会花比较多篇幅来介绍下。

Node服务的技术方案主要有以下几个部分:

  • 使用Kafka来消费用户反馈的日志队列;

  • 使用Node的进程池管理,来实现高并发处理大数据量的日志解析任务;

  • 使用Axios网络插件,来实现数据请求/解析,支持解析脚本接收业务订单参数;

  • 封装独立的日志解析模块负责脚本解析;

5. 技术挑战

在落地浏览器解析和Node服务端智能分析这两个能力的过程中,我们也遇到了许多技术挑战,但最终都找到了较为合适的解决方案。接下来,我们将分别从Web浏览器端和Node服务器端两个方面来说明我们所面临的技术难点。

1. Web浏览器日志分析

我们知道,浏览器Javascript是单线程执行,当JS执行高负载运算时,UI渲染就会阻塞,页面就会出现卡顿,用户体验较差(即使如setTimeout和setInterval也只是异步终究还是单线程,不能从根本解决问题),这里我们采用HTML5标准的Web Worker,它能支持一段JS程序运行在主线程之外的线程,与主线程互不干扰,当子线程代码执行完成后,将结果回调给主线程处理和渲染展示,就不会造成主线程阻塞以及页面卡顿。

在采用Web Worker后,我们可以观察到即使有大量的JavaScript解析任务在运行,浏览器页面仍然保持流畅,不再出现卡顿的情况。下面是我们的解析流程图:

image.png

2. Node服务端智能解析

公司有不同的业务线应用程序,比如用户端和司机端等。每个业务线每天都会收到来自用户的不同反馈。当我们接收到用户提交的问题反馈后,这条反馈会被发送到我们的Feedback系统中。

通过AI智能打标,一旦被标记为疑似bug,该反馈信息将被提交到Feedback系统中的Kafka消息队列中,等待Node服务器端的Kafka进行消费。当Node端完成分析后,会将分析结果提交到Feedback系统中。当相关人员打开该反馈时,问题的分析结果会自动显示在反馈问题页面中,从而大大提高了bug的分类和排查效率。

9.png

我们在Node端采用node-rdkafka消费Feedback系统的Kafka消息队列,但在消费消息队列的过程中,会遇到以下问题:

  1. 多条消息被同时消费

      消息的处理顺序无法按照指定顺序进行,例如在分析某个反馈过程中(根据日志量的大小,分析所需的时间长短不一),可能会同时收到多条待处理的分析任务,导致混乱。

      为解决这个问题,我们引入了心跳机制。在开始监听Kafka消息队列时,我们启动一个定时器,并增加一个是否正在解析的标记。如果正在处理任务,则停止Kafka消费。一旦当前分析任务完成,我们将该任务标记设置为false,就会继续消费新的消息任务。这样,通过心跳机制,我们能够更好地控制分析任务的顺序和并发处理。

      接收Kafka新消息,将isWorking标记为true:

    // 开始监听
    consumer.on("data", (message) => {
        isWorking = true
        const msg = message.value.toString();
        try {
            console.info("[kafka] consumer receive msg. ", msg);
            const {event} = JSON.parse(msg)
            if (event !== 'wait_analysis') {
                consumer.commit(message);
                isWorking = false
                return
            }
            processKafkaMessage(message)
        } catch (err) {
            consumer.commit(message);
            isWorking = false
        }
    });
    

      增加心跳机制,当isWorking为true时,停止消费:

    consumer.on("ready", () => {
        consumer.subscribe([KafkaConfig.OFFLINE_LOG_ANALYSIS.topic]);
        setInterval(() => {
            if (!isWorking) {
                if (AppConfig.app.isDEV()) {
                    console.log('持续监听中...')
                }
                consumer.consume(1);
            }
        }, 1000);
        console.info("Consumer ready, start receive message.");
    });
    

      解析完毕后,将isWorking标记为false,继续开始监听新的解析任务:

    const processKafkaMessage = (message) => {
        try {
            handleKafkaMessage(message, async () => {
                console.info("######" + '该消息处理完成')
                consumer.commit(message);
                setTimeout(() => {
                    isWorking = false
                }, 1000);
            });
        }catch(err) {
            consumer.commit(message);
        }
    };
    
  2. Node进行处理大量日志的解析任务时,卡死无法继续监听Kafka

     我们了解到Node是单线程的,在处理异步IO方面非常高效。然而,如果我们将密集型分析任务直接在Node的主线程中处理,可能会导致主线程阻塞。

      为了解决这个问题,我们采用了子进程来创建独立的子进程,并将接收到的解析任务分派给子进程来处理。这样,主线程可以专注于监听Kafka消息,类似于前端浏览器的方式。通过这种方式,我们成功解决了主线程阻塞的问题。

6. 排障脚本编写

基础能力都打通后,最后一步就是让各个app的研发根据各自业务的高频反馈编写排障脚本。为了尽可能降低大家写脚本的门槛,我们提供了脚手架、常用代码片段、预置全局参数代码,大家只需把排障逻辑写成js代码。

1. 本地编写调试脚本

  • 配置本地环境

前端项目依赖的命令工具yarn,可先熟悉和安装yarn的安装和使用

  • 安装项目

先将前端项目拉到本地,然后切换到脚本开发分支

// 拉取项目到本地
$ git clone [git url]
// 切换到脚本开发分支
$ git checkout -b feature/write_local_script origin/feature/write_local_script
  • 启动项目

先安装项目的所有依赖

$ yarn install

然后启动项目即可

$ yarn start

  • 编写脚本

    •   

    •   /**
         * 本地编写脚本的方法
         * @param singleLogData 当前日志对象
         * @param callback 打印在页面的回调函数
         * @param isLastLog 是否为最后一条消息
         * @param isFirstLog 是否为第一条消息
         * @param extraParams 用户自定义参数
         **/ 
        function devExcuteCustomJSScript(singleLogData = {}, callback, isLastLog = false, isFirstLog = false, extraParams = '') {
      
            // 单条log日志信息
            var logInfo = singleLogData 
            // 脚本需要输出的日志信息,此回调数据格式为JSON对象
            var logCallBack = callback  
            // 是否为第一条消息
            var isFirst = isFirstLog
            // 是否为最后一条消息
            var isLast = isLastLog
            // 用户自定义参数(字符串,根据输入方式进行解析)
            var userParams = extraParams
            // 请在此处编写脚本逻辑
      
            // ............................................................................................................
            // 
            //                                                此处编写脚本代码
            // 
            // ............................................................................................................
      
        }
      

2. 常用代码片段

我们提供了一些常用的脚本代码片段,研发同学可以直接复制使用。

3. 线上验证脚本

我们在日志系统的配置区提供了编写脚本的前端编辑器,大家本地调试好的脚本也可以用线上真实的日志数据进行再次验证,是否符合预期。

四、收益

  • 处理效率

产品上线后,各业务线第一时间根据各自用户反馈的高频问题,编写了大概将近百个排障脚本,22w条日志解析时长在2分钟多,万条以内的日志基本10s内可以解析完成。

其中一个最优秀的排障脚本,帮Android司机端分析了 46条语音播报相关的反馈,并定位到8种原因,如果是研发一条条排查分析的话,至少需要1天时间,是智能排障体系非常优秀的提效案例。

  • 处理数量

随着各端开发的排障脚本越来越多,脚本覆盖的疑似bug数也同步提升,目前排障脚本能解决的疑似bug覆盖率已达95%以上

这里的产品提单数,代表的就是人工判断是疑似bug,并提bug单给研发跟进的反馈。这个数据代表的是人工处理的反馈数,可以看到:

  1. 人工处理的反馈数占比极小,大部分用户反馈可能因为时间和人力原因,没有被关注到;

  2. 随着脚本覆盖的疑似bug数越来越多,人工处理的反馈数有在下降,因为值班产品在打开反馈详情的时候,页面上大部分已经有了排障结论,不需要再提单处理了

五、后续规划

产品上线后,在推广过程中,我们也收到了很多来自使用方的建议,包括AI模型对疑似bug识别的准确率、长尾问题的排障脚本覆盖、业务快速迭代脚本逻辑也要同步迭代带来的维护成本等等,这些也都是我们后续规划要持续打磨优化的方向。

六、总结

本篇文章重点介绍了打造智能排障体系的产品思路和技术选型,详细阐述了Node服务的设计和落地过程中遇到的问题。但其他部分没有过多涉及技术细节。如果大家对其他技术细节有兴趣或有疑问,可以在评论区留言,我们非常乐意与大家探讨和交流。