有关线上监控

Posted by 王青 on May 31, 2017

线上监控目的

  • 尽早的发现线上问题
    • 当前 的异常情况。
    • 一段时间内 的异常情况。

各种各样的监控

机器、服务情况、业务等等

不同种类的监控一般实施人员会有区别

  • 运维、云平台服务提供商
  • 开发
  • 测试

运维一般做业务无关的监控,主要是 机器、容器 相关,同时也可能包括一些服务(进程)的 死活 监控等等。

一个活着的服务是不是提供了 可用的服务 ,则一般由开发来做。(不绝对)

开发和测试在这方面 并没有明确的界限 ,一般会有一个分工,以免产生重复劳动。

具体需要做哪些监控,取决于 实际的需求

  • 机器
    • 存活、CPU、内存、硬盘、带宽等等
  • 服务
    • 进程存活、数量正确,等等
    • 是否正常响应
  • HTTP 接口(通过实时请求检查返回的 header 、 body)
    • 是否 200 或符合预期的 301 / 302
    • errno 是否未 0 或者符合预期的其他错误码
    • header 中关键字段是否正确
      • Location
      • Set-cookie
      • Content-Type
    • body 中字段是否正确
      • 根据需要对比数据库或其他存储介质
    • 响应时间
  • 页面UI(一般通过 模拟浏览器 行为来校验 dom或截图
    • header 部分同 HTTP 接口
    • 页面元素是否存在或者样式是否正确
  • 日志(从各机器采集日志来进行统计分析)
    • 4xx/5xx 一段时间内数量
    • 请求量( 激增暴跌
    • 平均响应时间
    • 业务日志报错情况( 提前日志输出
  • 业务
    • 日志和数据库(或其他存储介质)是否一致
    • 各个存储介质之间数据一致性
      • 如订单与商品
    • 某些业务量的 激增暴跌
  • 竞品 / 第三方
    • 新上架的商品
    • 价格变化
    • 排行榜

主要的流程

  • 采集
    • 接口请求、日志拉取等
  • 校验 / 统计
  • 检查是否需要报警
    • 单词/连续失败
    • 一段时间内失败次数
    • 数量激增或暴跌
    • 数据不一致
  • 报警
    • 短信、邮件、微信等
  • 存储
    • 作为长期分析使用

目前的工作

接口监控、日志监控、页面元素监控(待完成)

使用 nodejs 进行编写

目录结构(参看代码)

  • ylwlib // 存放自写的基础库和配置
    • config.js // 配置文件,包括邮件、部分阈值等等
    • req.js // HTTP 请求封装
    • mail.js // 发邮件
    • sms.js // 发短信
    • util.js // 一些公共方法,校验之类的
  • apimon // 接口监控
    • apimon.js // 单个 case 文件执行用
    • runApiMon.js // 所有 case 执行的管理入口
    • cases // 目录,存放各种 case 文件
    • var // 运行中需要的数据文件存放
  • logmon // 日志监控
    • logmon.js // 日志监控执行文件
    • logmonCases.js // 日志监控各阈值配置文件
  • uimon // UI 监控(待完成)

有关执行

  • 目前在测试机上执行, 由 crontab 维护
  • 日志监控每小时执行一次
  • 接口监控每分钟执行一次
    • 目前是串行,后续可以根据情况改成并行
  • 日志监控每次执行的时候,会进行 git pull , 意味着日常只要提交 git ,就能保证下次监控使用最新的 case 列表。不需要再去调整已经部署完毕的监控框架。

  • 一些小逻辑
    • 考虑到执行时间暂时不可控(比如超过1分钟),都有 lock 文件。
    • 同时为了避免某些异常导致 lock 文件不被清除,都有 过期失效 逻辑。
    • 有关接口监控的报警阈值,目前是两个控制逻辑
      • 连续报警(目前是连续2次)
      • 最近N次成功失败记录
        • 当次失败(未达到连续报警阈值)
        • 最近 N 次记录中,失败率达到 M% 的,认为不稳定,也会报警。

有关接口监控报警文案

  • 邮件,会另附上 URL 地址
  • 短信不含 URL 地址
    • IP详情页(奶爸宣言) get_Chrome_producer: network FAILED
    • IP详情页(奶爸宣言) get_Chrome_producer: objId校验 FAILED
    • URL描述 + get/post + UA + user[nologin/nopower/author/producer] + 检查项描述 + FAILED
    • network 表示网络问题,比如自身网络失败,或者域名不可解析
    • 其他项基本都是 case 中配置的
  • 所以写 case 的时候,需要用简单文案表达清楚 监控的事项

日志监控配置简单说明

直接参考文件 logmonCases.js

接口监控 Case 说明

部署路径

apimon/cases

目前是按照 yunlaiwu.com 的前缀 m/user/api/www 来区分的目录,实际不影响任何监控功能。 目录可以按照需要来部署。

文件名举例

sns_user_info.js

最好按照 url 的路径来取名,方面查阅。 一定要 .js 结尾,同时 符合 js 语法。

case 编写举例

'use strict';

/**
 * <必填> list cases 本身是个 list, 每个 item 根据 不同 情况, 如不同 请求参数区分。
 */
var cases = [
    {
        // <必填> string url:  url 地址, 可以是接口或者页面。
        url: 'https://api.yunlaiwu.com/sns/user/info?uid=57b4213a165abd0065c7ad54',

        // <选填> object qs:  querystring, 可以直接放 url 里
        // qs: {uid: '57b4213a165abd0065c7ad54'}, 

        // <选填> object form: 给 post 用的提交参数。
        // form: 

        // <选填> list<string> ua: 元素为 ylwlib/config.js 里的 userAgentList 的 key 。不填时,默认执行 ['Chrome']
        ua:  ['Chrome', 'iOS'],
        
        // <选填> list<string> method: 元素为  get 或 post ,  不填时,默认执行 ['get']
        method: ['get'],

        // <选填> list<string> method: 元素为  ylwlib/userlist.online 里 的 key, 未登录情况填 'nologin'。不填时,默认执行 ['nologin']
        user: ['nologin', 'producer'], // 元素为 

        // <必填> string desc: 描述,报警时描述使用。
        desc: '用户信息接口', 

        // <选填> bool followRedirect: 遇到 302 等跳转是否跟随跳转,默认是 true。 为 false 时, 302 将不会继续请求 location。
        // followRedirect: true,

        // <选填> object check: 需要检查的项。 不填时,默认校验 statusCode 是否为 200。
        check: {
            // <选填> string statusCode: 返回码, 一般是 200, 有些情况下可能是 302, 或其他。
            statusCode: 200,
            // <选填> string errnoKey: 接口里用来标示 errno 的 key,一般是 errno,某些时候可能 code,不填则不校验。(页面没必要填)
            errnoKey: 'errno',

            // <选填> list<object> funcs: 自定义的校验方法列表。
            funcs: [
                {
                    // <必填> string desc: 描述,报警时描述使用。
                    desc: 'objId校验',

                    // <必填> function func: 自定义的校验方法。 入参固定为 ({headers, body}) 用法简单如下,headers 里的参数可以自行打印查看。
                    // 请务必在成功时返回 true, 失败是返回 false, 否则可能影响监控。 
                    func: function ({headers, body}) {
                        try {
                            // 转成 对象
                            var body = body;

                            if (typeof body != 'object') {
                                body = JSON.parse(body);
                            }

                            if (body.data.objectId == '57b4213a165abd0065c7ad54') {
                                return true;
                            }
                        } catch (e) {
                            return false;
                        }
                        return false;
                    }
                },
            ]
        }
    },
];

// <必填> 通用写法,勿改动,请照抄
module.exports = {
    cases: cases,
}

日常增加新的 case 之后, 可以本地调试。

有关一些本地调试相关的事情在 README 上有说明 https://github.com/yunlaiwu/ylwautotest

后续工作

  • UI监控的加入
    • 使用 phantomjs 作为组件
    • 目前主逻辑能跑通,具体的监控逻辑尚未完成
  • 相关同学也可以尝试对新接口、页面进行自己的补充
    • 如有必要( 请确保代码无误 ),也可以对框架本身代码进行维护
  • 谢谢 ^_^