一次线上游戏卡死的解决历程

GitChat 做者:杨彪
原文:一次线上游戏卡死的解决历程
关注微信公众号:GitChat 技术杂谈 ,一本正经的讲技术html

【不要错过文末活动】java

事故的发生详细过程

故事是发生在几个月前的线上真实案例,我将在本文中以故事形式为你们还原此次解决游戏卡死的经历过程,其中有不少线上实战经验和技巧都值得分享借鉴的,也有做者自创的处理线上问题“四部曲”--望问闻切,还有最经典的“甩锅”秘诀。无论白猫黑猫,能立马解决线上问题的就是好猫,线上问题实战经验最重要。下来就让我先来回顾下此次事故发生的背景吧。git

公司的游戏得到了Google Play的最佳新游推荐位展现,这表明着公司游戏能够在Google Play首页持续一周的全球推荐。若是对Google Play还不了解的小伙伴们能够看看下图,展现了Google Play推荐位的效果:算法

Google Play 推荐位

就是在这样一个重大利好消息推进下,项目研发组紧急加班加点地赶制进度和进行游戏压力测试(之后有机会详细写篇游戏压力测试机器人实现方案),最后内网测试环境(Testing Environment)和预生产环境(Staging Environment)一切都测试正常,随时等待更新线上正式坏境了。数据库

像以往同样,游戏发布了停服更新公告(因为新增长了联盟战和几个大的活动,担忧不停服更新有问题),执行完停服、备份、更新等一系列自动化流程后,服务器状态变为“更新完毕,白名单可进入”状态,而后通知QA进行上线前的最后一次生产环境(Production Environment)测试。整个项目的同窗和QA同窗以白名单身份在线上生产环境测试了近半个小时,没有任何bug和异常,咱们就信心满满的准备开服了。编程

游戏对外开放后,咱们像往常同样边观察边继续作着新的工做,忽然公司运营同窗过来讲游戏怎么感受好卡,咱们的第一反应是你网卡了吧(由于游戏服务器在国外,中间有一道不可逾越的qiang),我也没太在乎仍是继续作着别的事情,后来QA同窗也说游戏好卡啊,我本身也登录游戏试了下,确实挺卡,每一次操做都要等待很久,不过到如今我仍是没有意识到服务器卡了,只是让运维同窗查看游戏服的log有没有报错,而日志显示彷佛一切都正常(后面会解释为何日志还正常地输出)。缓存

慢慢地游戏内的聊天中开始有玩家反馈此次更新后游戏太卡,并且反馈的用户愈来愈多,我这才意识到问题的严重性了,游戏服确定哪里操做太慢反应迟钝了(以前可能由于游戏公测到如今大半年还没出现过事故,因此有点掉以轻心了)。安全

公司BOSS也过来问怎么回事,正值Google Play推荐导量的时期,公司上下很是重视。固然我知道越是面对大问题,有经验的人就越要冷静,我直以为给BOSS说:“服务器有点卡了,小问题,立刻就能弄好的,别着急”。其实当时我内心也没底,不知道问题在哪,不过根据本身以往经验和实践操做,只要按照“四步曲”流程化的执行一遍,确定能找到点线索和眉目的。服务器

更多的线上应急和技术攻关能够参照我和朋友合著的《分布式服务架构:原理、设计与实战》一书中的第六章“Java服务的线上应急和技术攻关”,该章中介绍了海恩法则和墨菲定律,以及线上应急目标、原则和方法,同时提供了大量的Linux和JVM命令,也有不少平时工做中经常使用的自定义脚步文件和实际案例。微信

接下来咱们一块儿,一步步地解决游戏卡死的问题,文章中不少截图只是本次文章演示说明,不是当时的现场截图,不过我会说明清楚尽可能还原线上真实过程。

事故的处理过程还原

解决线上问题的“四部曲”

  • :就是观察的意思,出了问题最重要一点就是观察线上问题发生的规律,切忌有病乱投医,一上来就先各类偿试各类改的。除了观察现象外,咱们还要观察各类日志、监控和报警系统,具体如何搭建“大数据日志监控系统”请参照做者书的第四章。
  • :就是问清楚如今问题发生的状况,这个很重要,后面会重点介绍具体须要问清楚的哪些问题。
  • :就是认真听取别人的意见,有时线上出了问题,咱们大多数内心仍是比较抵触别人说这说那的,不过在这种状况下,咱们更应该多听,找到可能引发问题的状况或有关的事情,同时也为后面的“甩锅”技巧打开思路。
  • :就是动手实践验证了,经过前面的观察、询问问题,咱们心中应该能有些假设和猜想的线索了,这时候就须要动手在测试环境或预生产环境上进一步验证咱们的假设是否成立了。

下面这张图归纳的介绍了“望问闻切”各阶段须要关心和注重的事情:

四部曲

前面经过对“四部曲”的介绍,你们可能会以为很抽象,不过它是咱们解决线上问题的指导方针、核心思想,那咱们在实际项目中又是如何“望问闻切”的呢?

首先是如何发现问题

发现问题一般经过自动化的监控和报警系统来实现,线上游戏服搭建了一个完善、有效的日志中心、监控和报警系统,一般咱们会对系统层面、应用层面和数据库层面进行监控。

对系统层面的监控包括对系统的CPU利用率、系统负载、内存使用状况、网络I/O负载、磁盘负载、I/O 等待、交换区的使用、线程数及打开的文件句柄数等进行监控,一旦超出阈值, 就须要报警。对应用层面的监控包括对服务接口的响应时间、吞吐量、调用频次、接口成功率及接口的波动率等进行监控。

对资源层的监控包括对数据库、缓存和消息队列的监控。咱们一般会对数据库的负载、慢 SQL、链接数等进行监控;对缓存的链接数、占用内存、吞吐量、响应时间等进行监控;以及对消息队列的响应时间、吞吐量、负载、积压状况等进行监控。

其次是如何定位问题

定位问题,首先要根据经验来分析,若是应急团队中有人对相应的问题有经验,并肯定可以经过某种手段进行恢复,则应该第一时间恢复,同时保留现场,而后定位问题。

在应急人员定位过程当中须要与业务负责人、技术负责人、核心技术开发人员、技术专家、
架构师、运营和运维人员一块儿,对产生问题的缘由进行快速分析。在分析过程当中要先考虑系统最近发生的变化,须要考虑以下问题。

  • 问题系统最近是否进行了上线?
  • 依赖的基础平台和资源是否进行了上线或者升级?
  • 依赖的系统最近是否进行了上线?
  • 运营是否在系统里面作过运营变动?
  • 网络是否有波动?
  • 最近的业务是否上量?
  • 服务的使用方是否有促销活动?

而后解决问题

解决问题的阶段有时在应急处理中,有时在应急处理后。在理想状况下,每一个系统会对各类严重状况设计止损和降级开关,所以,在发生严重问题时先使用止损策略,在恢复问题后再定位和解决问题。解决问题要以定位问题为基础,必须清晰地定位问题产生的根本缘由,再提出解决问题的有效方案,切记在没有明确缘由以前,不要使用各类可能的方法来尝试修复问题,这样可能尚未解决这个问题又引出另外一个问题。

最后消除形成的影响

在解决问题时,某个问题可能还没被解决就已恢复,不管在哪一种状况下都须要消除问题产生的影响。

  • 技术人员在应急过程当中对系统作的临时性改变,后证实是无效的,则要尝试恢复到原来的状态。
  • 技术人员在应急过程当中对系统进行的降级开关的操做,在过后须要恢复。
  • 运营人员在应急过程当中对系统作的特殊设置如某些流量路由的开关,须要恢复。
  • 对使用方或者用户形成的问题,尽可能采起补偿的策略进行修复,在极端状况下须要一一核实。
  • 对外由专门的客服团队整理话术统一对外宣布发生故障的缘由并安抚用户,话术尽可能贴近客观事实,并从用户的角度出发。

当咱们详细地了解了如何发现问题、定位问题、解决问题和消除形成的影响后,接下来让咱们看下本次解决线上游戏卡死过程当中是如何具体的应用的。

排查游戏卡死的过程

第一步,找运维看日志

若是日志监控系统中有报错,谢天谢地,很好定位问题,咱们只须要根据日志报错的堆栈信息来解决。若是日志监控系统中没有任何异常信息,那么接下来就得开始最重要的保存现场了。

第二步,保存现场并恢复服务

日志系统中找不到任何线索的状况下,咱们须要赶忙保存现场快照,并尽快恢复游戏服务,以达到最大程度止损的目的。

一般JVM中保存现场快照分为两种:

  • 保存当前运行线程快照。
  • 保存JVM内存堆栈快照。其方法以下:
  1. 保存当前运行线程快照,可使用jstack [pid]命令实现,一般状况下须要保存三份不一样时刻的线程快照,时间间隔在1-2分钟。
  2. 保存JVM内存堆栈快照,可使用jmap –heap jmap –histojmap -dump:format=b,file=xxx.hprof等命令实现。

快速恢复服务的经常使用方法:

  1. 隔离出现问题的服务,使其退出线上服务,便于后续的分析处理。
  2. 偿试快速重启服务,第一时间恢复系统,而不是完全解决问题。
  3. 对服务降级处理,只使用少许的请求来重现问题,以便咱们能够全程跟踪观察,由于以前可能没太注意这个问题是如何发生的。

经过上面一系列的操做后,保存好现场环境、快照和日志后,咱们就须要经过接下来的具体分析来定位问题了。

第三步,分析日志定位问题

这一步是最关键的,也是须要有不少实战经验的,接下来我将一步步还原当时解决问题的具体操做步聚。

诊断服务问题,就像比医生给病人看病同样,须要先查看一下病人的脸色如何、摸一摸有没有发烧、或再听听心脏的跳动状况等等。一样的道理,咱们须要先查看服务器的“当前症状”,才能进一步对症下药。

  • 首先使用top命令查看服务器负载情况

top命令

load average一共有三个平均值:1分钟系统负荷、5分钟系统负荷,15分钟系统负荷。哪咱们应该参考哪一个值?

若是只有1分钟的系统负荷大于1.0,其余两个时间段都小于1.0,这代表只是暂时现象,问题不大。

若是15分钟内,平均系统负荷大于1.0,代表问题持续存在,不是暂时现象。因此,你应该主要观察"15分钟系统负荷",将它做为服务器正常运行的指标。

说明:咱们当时服务器负载显示并不高,因此当时第一反应就排除了承载压力的问题。

  • 接下来再使用top命令+1查看CPU的使用状况

top命令

咱们主要关注红框中指标,它表示当前cpu空闲状况,而其它各指标具体含义以下:

0.7%us:用户态进程占用CPU时间百分比,不包含renice值为负的任务占用的CPU的时间。

0.0%sy:内核占用CPU时间百分比。

0.0%ni:改变过优先级的进程占用CPU的百分比。

99.3%id:空闲CPU时间百分比。

0.0%wa:等待I/O的CPU时间百分比。

0.0%hi:CPU硬中断时间百分比。

0.0%si:CPU软中断时间百分比。

说明:咱们线上服务器为8核16G的配置,当时只有一个cpu显示繁忙,id(空闲时间百分比)为50%左右,其他显示90%多。从这里看彷佛没有什么太大的问题。

既然cpu负载和使用都没太大问题,那是什么卡住了服务呢?直觉告诉我,多是线程死锁或等待了什么耗时的操做,咱们接下来就来查看线程的使用状况。不过在查看线程使用状况以前,咱们首先看看JVM有没有出现内存泄漏(即OOM问题,个人书中有介绍一个实际OOM的案例),由于若是JVM大量的出现FGC也会形成用户线程卡住服务变慢的状况。

  • 使用jstat –gcutil pid查看堆中各个内存区域的变化以及GC的工做状态

jstat命令

S0:幸存1区当前使用比例

S1:幸存2区当前使用比例

E:伊甸园区使用比例

O:老年代使用比例

M:元数据区使用比例

CCS:压缩使用比例

YGC:年轻代垃圾回收次数

FGC:老年代垃圾回收次数

FGCT:老年代垃圾回收消耗时间

GCT:垃圾回收消耗总时间

说明:当时服务也没有出现大量的FGC状况,因此排除了有OOM致使的用户线程卡死。

  • 接下来使用top命令+H查看线程的使用状况

top命令

PID:进程的ID

USER:进程全部者

PR:进程的优先级别,越小越优先被执行

NInice:值

VIRT:进程占用的虚拟内存

RES:进程占用的物理内存

SHR:进程使用的共享内存

S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数

%CPU:进程占用CPU的使用率

%MEM:进程使用的物理内存和总内存的百分比

TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。

COMMAND:进程启动命令名称

说明:经过查看线程%CPU指标,明显能看到某个java线程执行很是占用CPU,所以判定该线程当时出现了问题。那么咱们接下来如何找到这个线程当时在干吗呢?请看如下三步聚。(图片只是示意图,不是当时线上截图)

  • 使用jstack pid打印进程中线程堆栈信息,咱们可使用以下三步找出最繁忙的线程信息。

查看进程中各线程占用cpu状态, 选出最繁忙的线程id,使用命令top -Hp pid

jstack命令

把线程id转成16进制,使用命令printf “%x\n”{线程id}

jstack命令

打印当前线程运行的堆栈信息,查找线程id为0x766B的线程堆栈信息

jstack命令

说明:线上经过打印繁忙线程,查看线程的执行堆栈,并无找到被卡住的业务代码,每次都是执行成功的。当时就很是纳闷,为何一直只是这一个线程在不停地消耗着CPU,忽然一个编程的小技巧帮我找到了问题的罪魁祸首——线程中任务分配不均致使的服务响应变慢。

小技巧:为不一样的业务线程自定义名称,好比打印日志的线程为log_xxx,接收消息请求的线程为msg_xxx,游戏业务线程为game_xxx等。java中具体如何为线程命令以下图所示:

threadname

  • 罪魁祸首——分布式惟一ID生成器

wenti

经过上面红框中的代码咱们能够看到,线程任务的分配规则是经过用户的uuid模上线程池的长度,这样实现的目的是想让同一个用户的全部请求操做都分配到同一个线程中去完成(线程亲和性),这样的实现是为了从用户角度保证线程的安全性,不会出现多线程下数据的不一致性。

而问题就出如今这个uuid取模上了,咱们使用的是Twitter的分布式自增ID算法snowflake,而它生成的全部id恰好与我设置的线程池大小64取模后为0(具体缘由不明),致使全部用户的全部请求所有分配到了一个线程中排队执行了。这也是为何在查看线程堆栈信息时感受都在正常执行,而打印的全部线程中只看到编号为0的线程在执行,其它都空闲等待。

说明:此功能实现是在上线前两天,运营同窗告诉说,有玩家反馈前一刻领取到的钻石在下一刻莫名消失了,个人第一反应确定是多线程形成的,因此就临时采起了这种线程亲和方式统一解决了线程安全的问题。如今找到了问题产生的缘由,接下来看是如何解决的。

  • 使用MurmurHash散列下解决ID生成不均匀的问题

jiejue

第四步,Hotfix后继续观察状况

在测试环境或预生产环境修改测试后,若是问题不能再复现了,能够根据公司的Hotfix流程进行线上bug更新,并继续观察。若是一切都正常后,须要消除以前可能形成的影响。

一键查看最繁忙线程堆栈脚本

此命令经过结合Linux操做系统的ps命令和JVM自带的jstack命令,来查找Java进程内CPU利用率最高的线程,通常适用于服务器负载较高的场景,并须要快速定位负载高的成因。

此脚本最初来自于互联网,后来为了让其在不一样的类UNIX环境下运行,因此我作了一些修改,该命令在我每一次定位负载问题时都起到了重要做用。

命令格式:

./show-busiest-java-threads -p 进程号 -c 显示条数
./show-busiest-java-threads -h

使用示例:

./show-busiest-java-threads -p 30054 -c 3

示例输出:

findtop

脚本源码见《分布式服务架构:原理、设计与实战》书中241页,更多服务化治理脚本请参照书中的第六章“Java服务的线上应急和技术攻关”。

对本次线上事故的总结

在技术方面,线上问题大体分为如下三类。

CPU繁忙型

  • 线程中出现死循环、线程阻塞,JVM中频繁的垃圾回收,或者线程上下文切换致使。
  • 经常使用命令topjstack [pid]Btrace等工具排查解决。

内存溢出型

  • 堆外内存: JNI的调用或NIO中的DirectByteBuffer等使用不当形成的。
  • 堆内内存:程序中建立的大对象、全局集合、缓存、 ClassLoader加载的类或大量的线程消耗等容易引发。
  • 经常使用的命令有jmap –heapjmap –histojmap -dump:format=b,file=xxx.hprof等查看JVM内存状况的。

IO读写型

  • 文件IO:可使用命令vmstatlsof –c -p pid等。
  • 网络IO: 可使用命令netstat –anptcpdump -i eth0 ‘dst host 239.33.24.212’ -w raw.pcap和wireshark工具等。

以上只是简单的介绍了下相关问题分类和经常使用命令工具,因为篇幅有限更多内容请参照《分布式服务架构:原理、设计与实战》书中“线上应急和技术攻关”一章,详细介绍了各类状况下技术命令的使用。

在制度方面的应急处理和响应保障

制定事故的种类和级别

  • S级事故,核心业务重要功能不可用且大面积影响用户,响应时间:当即。
  • A级事故,核心业务重要功能不可用,但影响用户有限;周边业务功能不可用且大面积影响用户体验,响应时间:小于15分钟。
  • B级事故,周边业务功能不可用,轻微影响用户体验,响应时间:小于4小时。

说明:每一个公司定义的事故种类和级别都不同,具体状况具体分析,只要公司有了统一化的标准,当咱们遇到线上问题时才不会显的杂乱无章,知道事情的轻重缓急,以及如何处理和何时处理。

对待事故的态度

  • 保存现场并减小损失,第一时间恢复服务,减小线上损失,保存好如今全部信息用于问题分析定位。
  • 积极主动的解决问题,线上问题第一时间解决,也是展示我的能力的最佳时机。
  • 主动承担部分责任,承担本身能承担的责任,毕竟事故涉及KPI考核等问题,有时也须要混淆问题缘由,拒绝老实人背锅。
  • 不要轻信经验,线上无小事,而大多引发线上事故的问题通常都是小问题,因此必定不要轻信经验,每一项改动都必须通过测试。

说明:当线上出现问题后,大多数人第一反应多是“这不关我事,我写的东西没问题”,面对线上问题不要怕承担责任,反而正是咱们表现我的能力的最好时机。平时你们可能作了很是多的工做,勤勤恳恳的努力奉献着,最后BOSS连你的名字可能都没记住,尴尬!!可是一旦线上遇到问题,可能直接就形成很大的经济损失,全项目组甚至全公司都在关注的时候,你敢于站出来完美的解决了该问题,收获的成就会是至关大的。固然在这个过程当中,咱们也要学会“合理地甩锅”,具体的“甩锅”请听我直播。


实录:《杨彪:线上游戏卡死问题实战解析》


彩蛋

重磅 Chat 分享:《如何在三年内快速成长为一名技术专家》

分享人:
方腾飞 并发编程网创始人,支付宝架构师
Chat简介:
工做前三年是职业生涯中成长最快的几年,在这段时间里你会充满激情,作事专一,也容易养成良好的习惯。
在咱们公司有些同窗在前三年中就快速成为某一个领域的技术专家,有些同窗也可能止步不前。本场Chat和你们一块儿探讨下如何在三年内快速成长为一名技术专家。
学习方法:
掌握良好的学习心态 掌握系统化的学习方法
知识如何内化成能力
实战技巧:
你须要学会的编码习惯 如何在普通项目中提升本身的能力
在业务团队作
引用文字开发如何成长

想要免费参与本场 Chat ?很简单,公众号后台回复「技术专家」

这里写图片描述

相关文章
相关标签/搜索