Nodejs 线上服务稳定性保障体系

Nodejs 线上服务稳定性保障体系

PS:欲了解更多关于 Nodejs 开发的信息,请关注我的 Live 直播:小芋头君 的 Live -- 全栈工程师养成指南

本文首发于 大搜车前端团队博客小芋头君知乎专栏,转载请注明出处。

本文会有条理的将我们团队在稳定性保障方面做的一些事情与大家分享,文中着重强调“线上”服务的保障,尽量不会涉及开发过程中的话题,改天会就开发过程的质量保障另外介绍。另外,我们在此方面也并非完全成熟,大家可以作为参考,但也许并非最佳实践,本文我会尽量讲我们的解决问题的思路,而不是最终如何执行。

说到线上保障,我觉得主要是以下几个问题需要解决(从前到后):

  1. 开发过程中,如何保障代码质量?
  2. 发布前,如何及早提前发现潜在问题?
  3. 发布过程中,如何保障上线过程?
  4. 发布后,如何快速处理回滚?
  5. 线上故障之后,如何最短时间发现问题?
  6. 发现问题后,如何最快时间内定位问题并修复?

下面就这几个问题分别剖析。

一. 开发过程中,如何保障代码质量?

这一部分的话题,我不展开讨论,每个话题都能讲好几天,关于如何正确的处理错误,上个月@王子亭 在我们公司主办的杭州 NodeParty 上有过一个很详细的话题,原文见:jysperm.me/2016/10/node 。另外两个话题,以后有机会我会展开分享。

二. 发布前,如何及早提前发现潜在问题?

开发流程,对于每个公司来说可能因人而异,看产品性质和团队组成。对于我们公司来说,测试工程师是比较多的,一般质量保证都是测试同学来做保障。在开发过程中需要在fix所有bug之后才能进入下一步状态,通常是“预发布”,预发布的目的是验证一些线上配置和数据的正确性,通常进入预发布已经很少产生bug,通常1天后即进入上线状态。“灰度”是看产品情况,分层次向用户分发新版本。

另外,在我们 Nodejs 团队,每个需求在开发完成之后(合 develop 分支之前)或者 hotfix 合并 master 之前,都需要师兄做代码 Review,代码 Review 不是 Review 代码写的是否规范,因为我们已经有强制的 ESLint 限制,Review 主要是检查技术方案是否有问题,把握代码设计的思路等。这一点现在做的还是比较好的,每个 Review 都会产出很多交流,对于新人的成长也非常有帮助。不过代码 Review 因人而异,如果条件不允许,不可强推,否则反而会影响产品进度或者产生形式感。

三. 发布过程中,如何保障上线过程?

  1. Gitflow 流程,团队博客之前有过介绍:f2e.souche.com/blog/npm文中介绍了我们的自动化 Tag 工具,和定制化的 Gitflow 流程。
  2. 发布前自动化保障,做了两件事情,启动并验证应用无致命错误(crash),执行关键接口自动化功能测试,保障此次发布不会对线上产生致命影响。为什么需要这一步,因为我作为一个发布者,有时候很难把握住开发人员的代码,但是发布动作只有我能做,那我如何做到心里有数?这个事情就非常重要了,这样甚至我可以在不了解任何需求的情况下任意发布,至少,不会引起P0级别的BUG。
  3. 关于我们的发布过程,三个要点:A.分布式多机每台机器开多个 Cluster,重启过程中使用 PM2 的 gracefulReload 来重启,应用代码对此响应,将各种链接释放将 express 的 server 释放后重启,此时才会去重启下一个 Cluster,以此保障线上服务不会停机。B.发布脚本使用 Fabric ,上传用 rsync,安装依赖用内网 SNPM。C.我们的线上应用不依赖机器环境,随便开台机器,发布上去就可以运行,PM2 和 Node 二进制包都是和应用以及 node_modules 打包在一起的,这个对于快速升级集群非常重要!

四. 发布后,如何快速处理回滚?

上线后,万一出现问题,如何快速回滚?去找 Git 记录,然后 Git reset ?恐怕来不及了,所以在每次发布后,需要给 Git 打 Tag,标记此次发布的内容,并且更改 Package.json 中应用的版本号,以及增加 changelog,我们将此过程标准化,直接做成一个命令行工具,这个工具在前面那篇文章中有介绍:f2e.souche.com/blog/npm

回滚的操作很简单,就是回滚到上一个 Tag,将此动作,写成一个脚本,寻找上一个 tag 点的 hash,然后执行 git reset hash 即可回滚到本次发布前的 tag 点。

五. 线上故障之后,如何最短时间发现问题?

这部分内容较多,是我们工作的重点,关于日志记录和报警,日志分级等实现,可以参见团队博客之前的文章:f2e.souche.com/blog/ri-

具体上图里都有表述,通过这一整套体系,我们可以被动的发现问题,并且第一时间得知,而不是通过用户的反馈或者老大的鞭策才发现问题,通常等整个应用崩溃或者卡住的时候,一切都已经晚了,而这套监控系统,可以在问题刚刚有苗头的时候就及早发现。

最近我们在着重做的是 图中的定时任务调度和监控系统,因为之前的体系,对于独立运行的定时任务不太友好,不太好监控,并且稳定保障也做得不够好,另外调度也比较混乱,针对这个问题,正在开发一套调度监控系统,可以做到统一配置调度,然后监控定时任务的报错,以及定时任务是否按照预期上报了健康状态(因为定时任务很容易出现卡住或者超时的状态),如果任务不健康,还要及时自动重启此任务。目前正在 code review 阶段,还没有部署到线上。

六. 发现问题后,如何最快时间内定位问题并修复?

这里就不上思维导图了,基本上就是上图的那一整套方案中的一部分。我这里详细描述下,我们如何通过日志快速定位的。

首先,所有 Error 级别以上的日志都会进 ELK,那这些 Error 都记录了什么类型的错误呢?

  1. 业务代码错误,这是比较特殊的,是开发者自己记录的错误。根据此错误,可以在线上定位具体业务逻辑,一般记录的时候会记录错误栈+错误文件和行数+上下文信息,例如参数之类的数据。
  2. 全局通用错误,例如发起请求的错误,例如 express 的中间件错误,例如数据库错误,缓存错误等,如果出现错误,会统一通过框架记录到日志中。
  3. 最后一道门,uncaughtException 和 unhandledRejection ,用来记录被忽略的可能导致应用 crash 的错误,例如直接 throw 出来的 Error 或者 语法错误等。对于 uncaughtException 我们的处理方式是捕获,然后记录日志,记录完毕,吞掉。晚上对于此种处理方式颇有异议,但是作为一个线上服务,crash 是非常严重的。but 一定要记住,在测试环境千万不要吞掉 uncaughtException,把错误暴露出来,并且直接 crash 掉,这样才能在开发和测试阶段排查出 crash 级别的错误。(此处大多数文章推荐出问题后重启而不是吞掉,我觉得这里值得商榷,不要简单定论,最终,快速修复问题才是王道,而不是做一堆假设)

以上记录的错误日志,在 kibana 中很容易定位到 详细的错误日志,快速分析原因。

如果你觉得还不够,哪还有最后一道武器。利用一种请求跟踪的方案,在进入一个请求之后生成一个 RequestId,讲此请求记录到日志平台,然后在内部业务代码报错之后,能够获取到这个调用链条上最顶层的请求的 RequestId,以此日志,我们可以追踪到导致报错的这个请求的详细信息!

小结

线上服务稳定性保障是一门大学问,上述讲的只是一个基础措施,在服务变得越来越庞大之后,其实整个架构都是在为稳定性健壮性服务,不过此文不想涉及太深,希望大家能够看完此文后有一些感想,在自己公司的服务稳定性上有一些启发,谢谢大家。

PS:我们团队招资深 Nodejs 服务端工程师,阿里P6-P7级别,有意者简历我:sunxinyu@souche.com。直接进入面试通道,我司最近刚拿了蚂蚁金服和神州租车的一亿美金投资,业务发展很快,急需人才加入。

编辑于 2016-11-26 17:21