1

监控什么

今天我们来聊聊如何监控你的应用程序,这里的监控说的不是让我们去监控用户,而是监控应用的健康状态,什么是健康状态呢?对于后端的同学来说,在微服务的架构下,每个子服务是否正常工作、返回的结果是否满足预期,这些就算是健康状态,再举个例子,你的台式机,对于操作系统来说,每个硬件是否能正常的工作、工作的稳定性,这些都是需要关注的健康状态。

既然我们关心健康状态,那么我们该如何衡量一个“设备”的健康状态呢?对于上面的例子,CPU运行的温度、硬盘读取的速度、子服务执行的效率,这些都可以作为健康状态的参考标准。而对于我们前端来说,一个服务的响应速度、某个页面渲染的时间、外接设备是否正常运行、以及正常运行的时间比,这些都可以作为我们衡量一个“设备”是否健康的标准。

上面说的了要监控什么指标,那这些指标具体的实例又是什么呢?由于我主要做react-native应用的开发,我今天就基于react-native来讨论一下这件事,而对于传统的web,相对来说就简单一些了,但具体的思路不会差太多。

在我遇到的实际场景中,我的应用程序常常需要链接多个外接设备,例如:键盘、扫码枪、各种个感应器,所以我需要时刻关注这些设备的健康状态,一旦发现某个设备不能正常工作或者在未来的某个时刻不能正常工作,就需要马上反馈出来,而这只是一部分,这些物理设备有着很明确的“指标”。

另一方面,诸如网络状态、电池电量,这些应用内的“指标”也需要我时刻的关注,什么时候处于弱网环境、什么时候出现低电量等各种各样的异常情况都会让我们的应用程序变得不健康。所以,我们的目标就是围绕着这两块展开监控,那么接下来我 们说说该从什么地方下手。

生命周期

将所有想要监控的服务收集到一起,作为一个总控制,然后在总控中对各个服务器的各个生命周期埋点。

1、主动式:手动的从各个生命周期中hook想要的数据,然后通过计算,收集上报。

2、被动式: 在各个生命周期中埋点,等待某一类事件的触发。

可是这么多设备,如果我们一个个的去监控、去适配,那就和给windows系统的硬件写驱动一样繁杂了,这对于我们前端开发来说工作量实在是太大了,所以为了方便我们进行统一的管理,和复用统一的代码,我们需要一个“模式”去规范和统一我们的设备。

现在我们用一个统一的class去集中监控我们的设备,另外一个问题就是众多的设备,无论是“物理”的设备还是“虚拟”的设备,如果我们专门为每一种去写监控代码,工作量实在是太大了,所以我们可以让这些设备在上层表现的“一致”,为此,我们引入“生命周期”这个概念,在一个设备启动、运行、暂停、卸载的各个阶段,我们都可以进行监控,无论是扫码器,还是网络请求的发起,各种各样的形态都逃不过这个步骤,所以,只要能在这个上面做好文章,那么监控各种数据就易如反掌了。

以下我会从两个方面来介绍一下我总结的“基础”的监控项,个人认为,无论你的项目为了应对什么样的场景,下面的这些例子基本都会是“必备”的选项了,即便你的项目比我的项目更加精简,但是下面介绍的思路也会是不错的参考。

主动式监控

上面说了这么多,那么我们来具体的看看需要监控些什么呢?

在一个项目刚上线的时候,存在着很多隐藏的问题,所以我们需要更多的日志去监控应用程序是否正常的运行,以及是否按照我们自身设定的路线去执行,一旦项目稳定,一些模块或逻辑被证实是没有问题的了,那么相应的我们也要去移除一些埋点日志,另外一方面,在开始上线的时候由于用户量少,一旦出现线上bug,由于缺少案例,导致定位和分析的难度都会增大,所以,这个时候我们就需要主动的埋一些监控点,来应对这种情况的发生,在出现紧急bug的时候我们可以通过日志点去推测和演算用户的行为,以及当时的情况。

但是,说到底这些监控都是临时的,不一个长期的监控,所以我的原则也是尽可能的不去侵入到业务代码中,无论是通过切面编程还是高阶组件封装,都要保证这些监控代码可以通过一个或一组规则快速的关闭统计,解放算力。

因此,凡是主动监控都要具备自动判断和动态策略这两个特点,可以动态的感知到当前的状态,从而做出对主业务影响最小的行为,即便监控再重要,也不能因为监控的行为而影响业务进程的工作,这只能是一个锦上添花行为。

被动式监控

为什么要有被动式的监控呢?首先,有些监控的相对“独立”的,每一次的监控点并不会100%的触发,每次的触发并不存在上下文或者前后的必然联系。也就是说,这些点的触发都是单独存在的,所以我们不需要对其进行诸如计算、格式化等分析操作,也不需要保存它的上下文,比如开始、结束时间。

相对于上面介绍的主动式监控,被动式监控的代码和逻辑都会长期的运行,因为在项目的中后期,在移除了大多数异常、性能监控后,这些仅存的代码就成了我们排查问题的关键所在了。因此,这些监控要保证自身的稳定,以及所积累的信息准确、及时,谁都不希望看到奔溃或者逻辑错误的警报在发生之后很久才上报出来。

那么有哪些需要我们做被动式的监控呢?在我的项目中,一个外接设备是否在线,用户点击键盘上某个按钮等行为就是一个典型的被动式监控,我不知道用户什么时候去点键盘,我也不知道我的外接设备什么时候断开,我只是需要捕获到这个行为的发生就可以了。

而且对于这些监控点,我们实际上是不需要开发自己去写代码的,它应该存在于依赖的包中,这就避免了我多次写重复代码的问题,举个例子,我在A项目中有个对扫码器扫描到内容的监控,而在B、C项目中我仍旧需要这个功能,那么我就可以不再反复的去写这块的日志代码了,因为它应该存在于这个扫码器的包中,我要做的,只是提供一个logger方法而已,返回的格式、内容都不需要我去关心。

而这些通过埋点、用户输入、事件回调等方式收集上来的日志,我们都要如实的上报到远程服务器(这里和主动式监控有所区别,主动监控的内容可以允许我们自己计算、统计),因为这些都是真正的异常,以后会影响我们debug的日志要最大程度的保留现场。

客户端流程图

图片描述

服务端

上面说了那么多,都是基于客户端去做的,现在我们在客户端已经准备好了想要的数据,那么我们该如何去使用他们呢?

对于发送上来的日志,我可以做如下三件事:

  • 监控24小时在线状态
  • 异常指标的快速报警
  • 可视化的展示监控信息

下面我们就围绕这三点来设计我们的服务端系统。

对于第一点,我们可以通过与客户端的心跳包来检查客户端是否存活,因为在业务场景中,我们的应用为react-native的,所以在检测心跳的时候就要区分是native模块还是js的业务模块了,同时很多场景下存在无人使用时js业务不会上传日志以及在移动网络下业务精简日志的情况,因此我们的24小时监控服务就要足够灵活、多变。所以在报警的时候可以通过读取配置的方式在服务器不重启的情况下动态的变换监控规则。

例如什么项目需要监控native存活,什么项目需要监控js环境存活,什么时候将报警通知给何人,并且可以通知到是什么地方的什么设备出了什么故障,这些都是基础功能。

而第二点,需要我们做的除了包含第一点之外的功能外,还有一个异常记录的功能,因为第二点的异常具有偶发性,并且相对于心跳包来说,日志内容更加丰富,因此我们可以针对这些日志做更多的事情,但首先就是将这些日志分类的保存起来。同时,在第二点中存在不同的应用对不同的异常有不同的定义的情况,比如说A应用认为数据初始化的接口超时就属于异常,而B应用则认为即便超时也不影响,那么就需要为每个应用单独配置异常指标,从而做到分项目、分阈值的功能。

还有一些额外的拓展,由于上面做的分项目、分阈值处理,就势必存在一些通用异常,那么每个项目就应该具有继承公共异常的功能,以及一些模板异常的设定。这些都是这个服务所需要的功能。

最后一点,在搜集到异常以及通知到相关负责人之后,这次异常报警就算结束了嘛?当然没有这么简单了,我们可以基于web服务来查看一些日志的基本情况,例如什么项目报警最多,什么地方报警最多,什么时间报警最多等等图表提供我们查看和分析。更有甚者我们可以根据不同的报警级别给不同的人发送不同的报警内容。

最后是这个服务端自身的健壮性,由于我们的服务是基于nodejs来做的,因此通过pm2,我可以很方便的在进程挂掉之后马上恢复服务,同时为了服务相互解耦和最大化cpu效率,通过pm2启动脚本来将24小时离线服务、异常指标报警、日志分析展示服务相互独立开,并为可以启动多线程的服务提供cluster模式的支持。并且将共享的数据通过redis缓存,配置通过数据库持久化等方式来备份和保证服务的健壮与高效。

服务端流程图

图片描述

总结

至此,一个由客户端+服务端的健康监控系统初步完成。它们这些服务看似相互依赖,但又相互解耦,不会造成一个环节失效导致整个系统奔溃,同时又可以做到对正常业务最小化的侵入。

当然,这些都只是一个雏形,一个全部由js去完成的项目,相对于大公司那些完整而又系统的监控来说,仅仅只能作为开发业务的我们自查的一个工具。虽然有这些系统来保证我们的项目正常、健康的运行,但是更重要的是我们开发者自己代码的健壮和稳定。工具做的再好,也不能替代我们自己写的优异的代码。


jonnyF
157 声望6 粉丝