GitChat 做者:杨彪
原文:一次线上游戏卡死的解决历程
关注微信公众号:GitChat 技术杂谈 ,一本正经的讲技术html
【不要错过文末活动】java
故事是发生在几个月前的线上真实案例,我将在本文中以故事形式为你们还原此次解决游戏卡死的经历过程,其中有不少线上实战经验和技巧都值得分享借鉴的,也有做者自创的处理线上问题“四部曲”--望问闻切,还有最经典的“甩锅”秘诀。无论白猫黑猫,能立马解决线上问题的就是好猫,线上问题实战经验最重要。下来就让我先来回顾下此次事故发生的背景吧。git
公司的游戏得到了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中保存现场快照分为两种:
- 保存当前运行线程快照,可使用
jstack [pid]
命令实现,一般状况下须要保存三份不一样时刻的线程快照,时间间隔在1-2分钟。- 保存JVM内存堆栈快照,可使用
jmap –heap
、jmap –histo
、jmap -dump:format=b,file=xxx.hprof
等命令实现。
快速恢复服务的经常使用方法:
- 隔离出现问题的服务,使其退出线上服务,便于后续的分析处理。
- 偿试快速重启服务,第一时间恢复系统,而不是完全解决问题。
- 对服务降级处理,只使用少许的请求来重现问题,以便咱们能够全程跟踪观察,由于以前可能没太注意这个问题是如何发生的。
经过上面一系列的操做后,保存好现场环境、快照和日志后,咱们就须要经过接下来的具体分析来定位问题了。
第三步,分析日志定位问题
这一步是最关键的,也是须要有不少实战经验的,接下来我将一步步还原当时解决问题的具体操做步聚。
诊断服务问题,就像比医生给病人看病同样,须要先查看一下病人的脸色如何、摸一摸有没有发烧、或再听听心脏的跳动状况等等。一样的道理,咱们须要先查看服务器的“当前症状”,才能进一步对症下药。
load average一共有三个平均值:1分钟系统负荷、5分钟系统负荷,15分钟系统负荷。哪咱们应该参考哪一个值?
若是只有1分钟的系统负荷大于1.0,其余两个时间段都小于1.0,这代表只是暂时现象,问题不大。
若是15分钟内,平均系统负荷大于1.0,代表问题持续存在,不是暂时现象。因此,你应该主要观察"15分钟系统负荷",将它做为服务器正常运行的指标。
说明:咱们当时服务器负载显示并不高,因此当时第一反应就排除了承载压力的问题。
咱们主要关注红框中指标,它表示当前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也会形成用户线程卡住服务变慢的状况。
S0:幸存1区当前使用比例
S1:幸存2区当前使用比例
E:伊甸园区使用比例
O:老年代使用比例
M:元数据区使用比例
CCS:压缩使用比例
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
说明:当时服务也没有出现大量的FGC状况,因此排除了有OOM致使的用户线程卡死。
PID:进程的ID
USER:进程全部者
PR:进程的优先级别,越小越优先被执行
NInice:值
VIRT:进程占用的虚拟内存
RES:进程占用的物理内存
SHR:进程使用的共享内存
S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
%CPU:进程占用CPU的使用率
%MEM:进程使用的物理内存和总内存的百分比
TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。
COMMAND:进程启动命令名称
说明:经过查看线程%CPU指标,明显能看到某个java线程执行很是占用CPU,所以判定该线程当时出现了问题。那么咱们接下来如何找到这个线程当时在干吗呢?请看如下三步聚。(图片只是示意图,不是当时线上截图)
查看进程中各线程占用cpu状态, 选出最繁忙的线程id,使用命令top -Hp pid
把线程id转成16进制,使用命令printf “%x\n”{线程id}
打印当前线程运行的堆栈信息,查找线程id为0x766B的线程堆栈信息
说明:线上经过打印繁忙线程,查看线程的执行堆栈,并无找到被卡住的业务代码,每次都是执行成功的。当时就很是纳闷,为何一直只是这一个线程在不停地消耗着CPU,忽然一个编程的小技巧帮我找到了问题的罪魁祸首——线程中任务分配不均致使的服务响应变慢。
小技巧:为不一样的业务线程自定义名称,好比打印日志的线程为
log_xxx
,接收消息请求的线程为msg_xxx
,游戏业务线程为game_xxx
等。java中具体如何为线程命令以下图所示:
经过上面红框中的代码咱们能够看到,线程任务的分配规则是经过用户的uuid模上线程池的长度,这样实现的目的是想让同一个用户的全部请求操做都分配到同一个线程中去完成(线程亲和性),这样的实现是为了从用户角度保证线程的安全性,不会出现多线程下数据的不一致性。
而问题就出如今这个uuid取模上了,咱们使用的是Twitter的分布式自增ID算法snowflake,而它生成的全部id恰好与我设置的线程池大小64取模后为0(具体缘由不明),致使全部用户的全部请求所有分配到了一个线程中排队执行了。这也是为何在查看线程堆栈信息时感受都在正常执行,而打印的全部线程中只看到编号为0的线程在执行,其它都空闲等待。
说明:此功能实现是在上线前两天,运营同窗告诉说,有玩家反馈前一刻领取到的钻石在下一刻莫名消失了,个人第一反应确定是多线程形成的,因此就临时采起了这种线程亲和方式统一解决了线程安全的问题。如今找到了问题产生的缘由,接下来看是如何解决的。
第四步,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
示例输出:
脚本源码见《分布式服务架构:原理、设计与实战》书中241页,更多服务化治理脚本请参照书中的第六章“Java服务的线上应急和技术攻关”。
在技术方面,线上问题大体分为如下三类。
CPU繁忙型
top
、jstack [pid]
和Btrace
等工具排查解决。内存溢出型
jmap –heap
、jmap –histo
、jmap -dump:format=b,file=xxx.hprof
等查看JVM内存状况的。IO读写型
vmstat
、lsof –c -p pid
等。netstat –anp
、tcpdump -i eth0 ‘dst host 239.33.24.212’ -w raw.pcap
和wireshark工具等。以上只是简单的介绍了下相关问题分类和经常使用命令工具,因为篇幅有限更多内容请参照《分布式服务架构:原理、设计与实战》书中“线上应急和技术攻关”一章,详细介绍了各类状况下技术命令的使用。
制定事故的种类和级别
说明:每一个公司定义的事故种类和级别都不同,具体状况具体分析,只要公司有了统一化的标准,当咱们遇到线上问题时才不会显的杂乱无章,知道事情的轻重缓急,以及如何处理和何时处理。
对待事故的态度
说明:当线上出现问题后,大多数人第一反应多是“这不关我事,我写的东西没问题”,面对线上问题不要怕承担责任,反而正是咱们表现我的能力的最好时机。平时你们可能作了很是多的工做,勤勤恳恳的努力奉献着,最后BOSS连你的名字可能都没记住,尴尬!!可是一旦线上遇到问题,可能直接就形成很大的经济损失,全项目组甚至全公司都在关注的时候,你敢于站出来完美的解决了该问题,收获的成就会是至关大的。固然在这个过程当中,咱们也要学会“合理地甩锅”,具体的“甩锅”请听我直播。
重磅 Chat 分享:《如何在三年内快速成长为一名技术专家》
分享人:
方腾飞 并发编程网创始人,支付宝架构师
Chat简介:
工做前三年是职业生涯中成长最快的几年,在这段时间里你会充满激情,作事专一,也容易养成良好的习惯。
在咱们公司有些同窗在前三年中就快速成为某一个领域的技术专家,有些同窗也可能止步不前。本场Chat和你们一块儿探讨下如何在三年内快速成长为一名技术专家。
学习方法:
掌握良好的学习心态 掌握系统化的学习方法
知识如何内化成能力
实战技巧:
你须要学会的编码习惯 如何在普通项目中提升本身的能力
在业务团队作
引用文字开发如何成长
想要免费参与本场 Chat ?很简单,公众号后台回复「技术专家」