服务端业务异常日志监控

背景

我们是一家 H5 的游戏公司
多数项目的服务端使用 SKYNET+LUA 的形式开发
协议层走的的 websocket

问题

1
2
3
4
5
6
7
8
9
10
一般情况下我们都是走这样的流程

1. 业务更新版本
2. 等待用户反馈问题
3. 判断客户端还是服务端
4. 根据用户信息查看日志
5. 修复BUG更新版本

解决问题被动而且效率低下.
大量体验不好的用户直接流失.

方案

我们采用目前主流的 ELK 方案来解决这个问题

使用 elasticsearch 在日志存储
使用 logstash 做日志标准化
使用 kibana 做统计展示

另外
使用 filebeat 来做日志收集
使用使用 Crontab, Python 在实现日志监控
使用 DingTalk 接受告警信息

使用 ELK 是因为主流, 坑少, 资料多(不喜欢 java, 太笨重)
使用 filebeat 是因为原配, 小三的不要(漂亮的还是可以考虑的)
使用 Crontab, Python 是因为 es 的 watch 不能满足我们(需求第一)
使用 DingTalk 就要问老板了(老板第一)

没有使用 Kafka 是因为没有数据级和可靠性的需求
即使后面有了需求, 加入 Kafka 也不是多大的事

看图…

5013d8139a7ef721ed4e2cc6bb7b0020.png

实施

filebeat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. 使用Saltstack部署filebeat并且以后主机上线后会自动部署
新上线的主机在镜像中已经安装了salt-minion
启动后会自动连上salt-master然后获取自己应该执行的任务


2. 使用include_lines选项来包括需要监控的关键字
我们的日志量很多, 又想节约成本, 所以只传输我们关心的日志.

3. 使用multiline.pattern来合并多行日志
老生常谈的问题, 主要解决日志多行的问题.

4. 使用output.logstash传输日志到logstash
# 随机获取不同的主机来传输日志, 既可以实现负载又可以实现高可用
# 真是完美啊, 胖客户端才是王道!!!
hosts: ["192.168.1.101:9100", "192.168.1.102:9100", "192.168.1.103:9100"]
loadbalance: true

logstash

先来看看配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 多少个干活的
# 进程相当于产品经理, worker相当于程序员的数量
pipeline.workers: 8

# 一次处理一批, 效率高啊
# 就像每次吃瓜子都是抓一把
pipeline.batch.size: 10000

# 收到的数据先写入磁盘
# 不然进程崩了, 系统挂了, 数据就丢失了
queue.type: persisted
path.queue: /usr/share/logstash/queue

# 以文件的形式存储, 一个文件的大小
queue.page_capacity: 256mb

# 不限制最大事件数
queue.max_events: 0

# 限制最大使用80G硬盘
# 别问为什么, 问就是不知道.
# 猜测可能是为了避免出了问题没人发现吧.
queue.max_bytes: 81960mb

然后是 logstash 的 pipeline
这里举一小段例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 匹配服务端超时的日志
if "Cost Time Too Long" in [message] {
grok {
match => {
"message" => [
"^.*\[%{TIMESTAMP_ISO8601:timestamp}\].+Cost Time Too Long (?<message_name>\w+) (?<elapsed_time>[0-9.]+) (?<online_time>[0-9]*).*",
"^.*\[%{TIMESTAMP_ISO8601:timestamp}\].+Cost Time Too Long (?<message_name>\w+) (?<elapsed_time>[0-9.]+).*"
]
}
add_tag => ["TIMEOUT"]
}

# 匹配服务端用户登录的日志
} else if "Player:Login" in [message] {
grok {
match => {
"message" => [
"^.*\[%{TIMESTAMP_ISO8601:timestamp}\].+Player:Login \w+ \w+ (?<user_id>[^ ]+).*"
]
}
add_tag => ["LOGIN"]
}

# 匹配服务端所有错误的日志
} else {
grok {
match => {
"message" => [
"^.*\[%{TIMESTAMP_ISO8601:timestamp}\].*?stack traceback:.*?/(?<fileline>[^/]*.lua:[0-9]+):.*$"
]
}
add_tag => ["ERROR"]
}
}

因为我们的日志格式都是非标准的, 日志等级也是很随便的
这就需要我们针对每个项目的格式进行转换和处理
以保证最终的格式都是一致的(制定了标准也很难推进)

elasticsearch

配置就不说了, 都是标配
主要注意的是分片和 index 名字的管理

  1. 主分片为节点数(最高写入)

  2. 副本数为 1(数据不丢)

  3. 同一个主分片和副本不能在一台机器上, 不然机器挂了就完了

  4. 单个分片的大小在内存大小左右就行了, 毕竟是要把数据装入内存

  5. index 的名字要么使用日期的形式命名要么使用 ilm 来自动管理
    使用日期可以灵活的对某一个日期的 index 做处理
    但是需要手动删除过期的数据

    使用 ilm 管理就只能通过 RESTAPI 来进行数据处理
    好处是不用自己删除过期数据

kibana

主要使用的是 TSVB 组件和 TABLE 组件
这个 TSVB 组件还不错, 就是有些需求实现不了
可能是我学艺不精, 毕竟才用了一个月

table 组件也是奇葩的很, 创建的时候就指定了 index name.
每次都只能修改 kibana 的 obejct 也是心累啊

监控检测

前面也提到为什么我们没有使用 es 的 watch 功能
主要是希望能提高告警的准确性反应出问题
我们策略是这样的, 取一小时内的数据

1
2
3
4
5
6
7
当前异常比 = 当前异常数 / 当前用户数
昨日异常比 = 昨日异常数 / 昨日用户数

相差异常比 = 当前异常比 / 昨日异常比

if 昨日相差异常比 > 2 and 前时相差异常比
报警

这里还可以优化一下
即使当前的相差比昨天的大, 也不能隔一小时报警一次.
违背了我们的初心, 所以升级一下策略

1
2
3
4
5
昨日相差异常比 = 当前异常比 / 昨日异常比
前时相差异常比 = 当前异常比 / 前时异常比

if 昨日相差异常比 > 2 and 前时相差异常比 > 2
报警

为什么是 > 2, 这个也是拍脑袋想的
目前暂时没有想到更好的方法来动态调整.
有一个思路是根据异常数的差算出临界值, 只是一种思路.

报警通知

我们在 DingTalk 上封装了一层接口

  1. 集成各种报警第三方接口
  2. 实现告警的重复收敛

目前我们 Crontab 的时间间隔是一小时.
这个时间间隔目前来看可能没有太大的问题.

随着项目的发展, 可能会出现报警不及时的问题.
所以需要配合重复收敛功能来实现及时告警, 同时又不会重复的告警
完美…

效果

先看效果, 再谈疗程(屏蔽业务数据)

统计效果

fd033fc85e475f9df95302130b8688a2.png

监控效果

31312d42e26df1a9d7faf29343949208.png

这里说说为啥会有个用户数的指标.
我们是这么考虑的

单纯的看日志的异常数是不准确的.
因为日志的异常数是随着玩家的增加而增加的.
很容易出现, 同样一个问题, 因为玩家增加而多次报警.
形成”狼来了”的感觉
所以这里使用用户数来做参考.

这里的用户数是登录数.
其实使用在线用户数效果会更好.
只是实现的成本较高, 以后再进行迭代.

总结

首先这个系统做下来来, 用过的研发都说好.
毕竟研发也没有时间天天去看日志.
每次等用户发现 BUG 反馈过来, 太过于被动了.

其次, 通过聚合统计能够发现影响重大的在哪, 集中发力

当然也有一些问题存在

  1. 目前无法快速的接入项目
    运维标准化和日志标准化的问题
    每个项目可能会有不同的监控需求

  2. 只监控了错误日志, 用户请求的超时并没有监控.
    研发已经没有精力做体验上的优化了, 卡个几秒大家觉得可以接受了.

  3. 异常信息挖掘
    集合其他主机监控, 服务指标, 业务指标来从多维度来快速定位问题.

  4. 只能发现问题, 没法解决问题
    如果产品初期管理不善, 那积累起来的历史问题会完全无从下手

以上