腾讯即时通讯技术共享:安卓手q线程死锁监控系统的技术实践

 1.问题的背景

 

 R&D学生将在每个版本的问答发布后收到各种问题的反馈。

 

 在跟进问内部用户的反馈时,发现了很多问题,其表现和原因如下:

 

 1)问题出现:“没有阅读它不会消失”,“画面不会显示”,“菊花不停地转动”。。。

 2)问题原因:死锁导致的函数不可用。

 

 这种由死锁引起的功能不可用问题具有外观简单但影响严重的特点。遇到这样的问题后,普通用户除了终止进程并重新启动之外,没有其他方法继续使用应用程序。由此可见,死锁问题对产品有着巨大的影响,那么有没有一种有效的方法来监控安卓应用的死锁呢?

 

 第一个想法是使用代码规范来避免死锁。Q中有250多个业务模块和400多行代码,所以很多业务代码是交叉调用的,仅仅通过代码规范很难避免死锁。

 

 然后我想到了代码狗的代码工具扫描。在与CodeDog同事交流后,发现Converity静态扫描无法识别嵌套调用中锁的使用,锁的嵌套调用是死锁场景中常见的场景。显然,代码的静态扫描不能解决问题。

 

 既然现成的静态代码分析工具不能完全解决这个问题,我们只能尝试构建自己的轮子来监控安卓线程的死锁。

 

 本文将详细介绍Android版本q中线程阻塞监控系统的设计思想和技术实践总结(注意这里使用的词是“卡住”而不是死锁)。死锁只是线程阻塞原因中的一个重要分类。除了死锁,还有许多其他问题会导致线程阻塞。)

 2.相关文章

 

 “微信团队原创分享:iOS版微信内存监控系统技术实践”

 3.总体计划概述

 

 以下是手Q自建安卓线程阻塞监控的总体方案:

 腾讯技术共享:安卓手机线程死锁监控系统的技术实践

 

 Android线程监控方案分为客户端和后台两部分。

 

 1)客户端:由监控线程和监控线程组成。

 

 客户端线程监控的核心主要采用线程活套方法,监控线程监控从被监控线程的消息队列中取出的消息的执行情况。当监控线程发现队列外的消息已经有一段时间(3分钟,可以自己设置)没有执行时,可以认为被监控线程被卡住了。此时,监控线程获取线程持有并等待的阻塞锁信息,并将该信息报告给后台,客户端完成其任务。

 

 2)背景:用自动分析工具分析螺纹堵塞的原因。原因分析完成后,就可以进行提货单,这样就可以解决每一个卡住的问题。

 

 整个方案的关键是上图中红色标记的两部分:客户端报告线程阻塞的关键信息,后台自动分析线程阻塞的原因。下面将详细描述这两个部分的实现。

 4.客户对方案实施的报告

 

 4.1停滞信息

 

 在安卓线程阻塞监控中,客户端报告线程阻塞的关键信息,那么哪个信息是关键信息呢?答案是显而易见的:线程信息以及线程持有和等待锁信息。

 

 在如何获取这两种信息之前,先分析一下Java中锁的分类和特点。

 

 在Java中,锁分为旋转锁、重入锁、阻塞锁等。其中,只有阻塞锁会导致线程被卡住。

 

 有三种类型的锁定:

 腾讯技术共享:安卓手q线程死锁监控系统技术实践_2.jpg

 

 根据这三把锁中是否有送线器和持线器,为了达到分析粘扣的目的,需要获取以下信息:

 

 1)对于同步锁:需要获得它的保持线程和等待线程;

 2)对于锁支持锁:需要获取其保持线程和等待线程;

 3)对于对象锁:需要获取其等待线程。

 

 接下来的关键点是如何获取线程信息并报告锁信息。

 

 4.2报告方案1:抓取java堆栈-不可行

 

 第一个想法是抓取java堆栈并报告它。

 

 下面是捕获的java堆栈及其相应的代码:

 腾讯技术共享:安卓手q线程死锁监控系统技术实践_3.jpg

 

 上图右侧代码中的121行已经获得了精神病锁,但是相应锁的信息没有显示在左侧的java栈中,因此抓取java栈是不可行的。

 

 既然使用Java获取堆栈信息是不可行的,那么还有其他解决方案吗?回答:是的。

 

 4.3报告方案2:抓住系统踪迹

 

 由于爬行java堆栈是不可行的,我们只能寻求其他解决方案。

 

 当ANR出现时,我突然想到安卓有一个系统机制:

 

 1)当ANR出现在1)安卓应用中时,系统会向ANR进程发送退出信号;

 2)系统信号捕获线程触发输出/数据/anr/traces.txt文件,并记录问题产生的虚拟机和线程堆栈相关信息;

 3)该跟踪文件包含线程信息和锁信息,使用该跟踪文件可以分析卡的原因。

 

 因此,如果我们使用这个系统的原始机制来触发跟踪文件的形成,以便在线程被卡住时进行报告,我们就可以报告线程被卡住的关键。

 

 这种监控方案是利用系统机制来捕捉卡住的信息:

 

 1)当监控线程发现被监控线程被卡住时,它主动向系统发送SIGQUIT信号;

 2)等待生成/data/anr/traces.txt文件;

 3)生成文件后上报;

 4)当然,在一些情况下,跟踪文件的生成会失败,但是对于手动调Q监控,可以忽略它。

 

 由于该方案是可行的,因此有必要分析系统机制捕获的信息(线程堆栈中的所有线程信息和锁信息)是否满足要求。

 

 以下是使用系统机制继续抓取的示例:

 腾讯技术共享:安卓手q线程死锁监控系统技术实践_4.jpg

 

 右边代码中的同步锁信息已经在左边系统转储的堆栈中,因此可以看到这个堆栈可以用于阻塞分析。利用这些信息来分析线程阻塞的原因就足够了吗?

 

 世界上从来没有这么便宜的晚餐。

 

 4.4报告困难:跟踪中没有锁支持锁持有者信息

 

 通过将报告的跟踪文件与捕获的锁信息进行分析和比较,已经解决了精神病锁和对象锁的信息,但是锁支持锁的信息缺少保持线程。

 

 使用系统机制抓取的堆栈,可以获得锁信息,如下表所示:

 腾讯技术共享:安卓手q线程死锁监控系统技术实践_5.jpg

 

 以下是锁支持锁无法获取线程信息的示例:

 腾讯技术共享:安卓手q线程死锁监控系统技术实践_6.jpg

 

 在右图的代码中执行lock.lock()之后,线程已经获得了LockSupport锁,但是在左侧的系统堆栈中没有关于该锁的信息。

 

 这将是未来自动分析的一个难题。有什么解决办法吗?通过对Q码的深入分析,找到了答案。

 

 4.5解决方案:主动记录锁支持锁的线程信息

 

 要解决这个问题,首先手动分析所有提交的跟踪文件,并找到关键功能:

 

 1)发现死锁发生时系统转储栈的关键特征;

 2)手工对卡住问题进行聚类分析,发现卡住堆栈中的锁支持锁的所有问题都是数据库问题;

 3)通过对hand Q数据库相关代码的分析,发现数据库事务锁的录入是统一的,可以记录采集和待采集的线程信息。

 

 在以上分析结论中,为了解决LockSupport不保存线程信息的困难,利用统一问题发现和统一程序代码录入的特点,采取了以下措施:

 在系统转储的跟踪文件中手动记录LockSupport锁定信息。

 

 解决方案:将以下记录代码添加到数据库相关代码中:

 腾讯技术共享:安卓手7.jpg线程死锁监控系统的技术实践

 

 在上述代码中,等待获取锁支持锁的线程被记录在等待列表中,并在获取锁支持锁后从等待列表中删除,当前线程(记录当前线程id和名称信息)被记录为持有锁支持锁的线程,在当前线程释放锁支持锁后记录被清除。

 

 利用上面记录的锁支持锁线程信息,只需要将该信息添加到由被卡住的文件形成的跟踪文件中,然后进行报告,从而解决了没有锁支持锁来保存线程信息的问题。

 

 下图显示了跟踪文件最后一行中添加的详细信息:

 腾讯技术共享:安卓手q线程死锁监控系统技术实践_8.jpg

 

 当然,为了便于后续的问题分析,在跟踪文件的最后一行添加了一些其他信息,例如被卡住的线程的名称、系统版本号、发生时间等等。

 

 目前,客户端已经解决了线程被卡住后报告的信息不完整的问题,所以下一个关键点是找出这些被卡住的原因,这将在下一章中详细解释。

 5.实施方案的服务器标识

 

 5.1识别方案:关键信息报告和自动分析

 

 服务器识别方案可以概括为:关键信息报告和自动分析。

 

 关键信息报告是自动化分析的前提。

 

 在详细说明自动化分析方案之前,首先查看迄今为止获得的信息:

 

 所有线程信息和系统转储锁信息(同步、对象、锁支持);

 2)主动记录锁支持锁定信息(保持线程);

 3)主动识别卡住的螺纹。

 

 有了这三个关键信息,下一步就是自动分析线程阻塞。

 

 以下是自动卡住分析的完整方案:

 腾讯技术共享:安卓手q线程死锁监控系统技术实践_9.jpg

 

 上图中整体识别方案的详细步骤如下:

 

 1)客户端将主动记录的锁支持锁定信息和锁定线程信息添加到系统转储的跟踪文件的最后一行进行报告;

 2)当服务器执行自动分析时,它首先进行预处理:锁支持锁等待线程信息预处理和线程堆栈程序恢复;

 3)从跟踪文件中提取所有线程持有并等待的锁信息,并记录给每个线程;

 4)提取跟踪文件最后一行记录的锁支持锁持有线程,从步骤13中分析的所有线程中找到线程,并将锁支持锁添加到持有锁的线程中;

 5)从跟踪文件最后一行提取被卡住的线程,并对被卡住的线程进行分析;

 6)被卡住的线程是否有等待锁,如果没有,则判断为非死锁,执行第12步分析被卡住线程的原因;

 7)如果有等待锁,找到持有等待锁的线程;

 8)保持线程是否有等待锁,如果没有,则判断为非死锁,执行第12步分析锁的原因;

 9)如果有等待锁,判断线程是否已经在遍历列表中;

 10)如果遍历了列表,则判断是否有锁列表循环,如果有,则判断为死锁;

 11)如果没有遍历列表,将线程添加到遍历列表中,并转到步骤7进行循环;

 12)非死锁原因分析。目前,它分为:网络、文件IO、哈希映射、系统IPC、堆栈抓取、GC、数据库、进程管理器、PB和后续扩展。

 

 完成上述自动分析后,输出线程阻塞问题列表。对于什么是死锁,下面将给出一个例子。

 

 5.2死锁示例:两个线程等待对方获得锁,导致死锁

 

 首先,看看发生死锁的两个线程堆栈。

 

 1)MSF-接收器线程堆栈:

 腾讯技术共享:安卓手q10 . jpg线程死锁监控系统的技术实践

 MSF-接收器线程已获取数据库的锁支持锁,正在等待获取锁0x18087549。

 

 2)线程堆栈:

 腾讯技术共享:安卓手机线程死锁监控系统的技术实践

 QQ_DB线程已获取0x18087549锁,正在等待获取数据库的锁支持锁。

 

 如下表所示,列出两个线程已获取和等待获取的锁:

 腾讯技术共享:安卓手q12 . jpg线程死锁监控系统的技术实践

 

 从上表可以看出,MSF-Receiver线程和QQ_DB线程等待对方获得锁,如果它们之间有锁列表环,则判断为死锁。

 

 从这个死锁的列中可以发现,如果我们想自动分析死锁,只要我们能发现线程之间存在锁列表循环,我们就可以将其判断为死锁。那么自动化分析像想象的那样简单吗?事实上,在自动分析的过程中,会遇到几个难题。

 

 5.3识别困难1:锁支持等待锁的不同阻塞地址

 

 在自动分析过程中,发现了以下问题:

 

 1)系统转储的线程堆栈中有锁支持锁对象信息等待数据库事务;

 2)但是,对于同一个LockSupport锁,当不同的线程阻塞时,对象地址是不同的。

 

 以上两点是什么意思?

 

 或者按照惯例,先看两个线程堆栈:

 腾讯技术共享:安卓手q13 . jpg线程死锁监控系统的技术实践

 

 q代码中的所有数据库操作都由同一个LockSupport锁控制。但是,从上面的系统堆栈来看,被最近处理程序线程阻止的锁支持锁定对象的地址是0x43810d48,而被QQ_SUB线程阻止的锁支持锁定对象的地址是0x41fcb538。

 

 因此,对于同一个锁支持锁,不同的线程会阻止不同的对象地址。对于自动分析,如果必须将同一个锁标识为同一个对象,则上面的堆栈表示无法完成此任务。怎么做?

 

 5.4解决方案:提取特征并将其判断为相同的锁

 

 锁支持锁提供了调度线程阻塞和唤醒的功能。不同的线程与其锁支持锁的许可证相关联,因此不同的线程会阻止不同的对象地址。

 

 由于这个问题是由锁支持锁的实现原理决定的,有没有解决方案?答案是:是的。也就是说,如果有办法让这些不同的地址指向同一个地址,只要能做到,问题就会迎刃而解。

 

 具体解决方案的分析思路如下:

 

 1)由于被阻止的LockSupport锁对象的地址不同,您能否找出系统堆栈中LockSupport锁对象之前是否有任何共同的功能?

 2)对所有转储栈进行分析后,发现系统栈中的LockSupport锁对象前有相同的函数调用,可以提取这些键串并对它们的统一特征进行分类。

 3)当执行自动分析时,只要在系统堆栈中找到该字符串特征,就将具有相同地址的人工构造的LockSupport锁添加到当前分析线程锁列表中。

 

 具体结构如下:

 腾讯技术共享:安卓手q14 . jpg线程死锁监控系统的技术实践

 

 在上图中,字符串“sqliteconnectionpool。waitforconnection”被提取为等待LockSupport锁的常见功能,相同的“dbconnection”锁被添加到线程的等待锁中。这样,解决了不同线程在同一个锁支持锁中阻塞时不同对象地址的困难。

 

 5.5识别难度2:非死锁问题

 

 对于非死锁问题,在对所有报告进行手动分析后,发现死锁原因有很多种类,大致可以分为:

 1)网络、文件IO、哈希映射、系统IPC、堆栈抓取、GC、数据库、进程管理器和Pb;

 2)分类以后仍然可以扩展。

 

 对于非死锁问题的判断,提出了一种基于栈关键字匹配的问题分类判断方案:

 

 首先,预设堆栈关键字与问题分类(如Scoket)的对应关系;

 其次,对未识别类别的堆栈进行聚类,并手动分析顶层问题,找出要扩展的关键字符串;

 第三,改进堆栈关键字和问题类别之间的对应关系。

 

 下表显示了到目前为止总结的卡住问题和相应的判断关键字:

 腾讯技术共享:安卓手q15 . jpg线程死锁监控系统的技术实践

 

 关键情况:哈希映射被卡住(分析系统转储时发现多线程访问哈希映射会导致被卡住,具体原因如下):

 

 1)插入太多元素时,哈希映射需要调整大小;

 2)调整大小步骤包括扩展和重新刷新;;

 3)当它同时发生时,再散列可以形成链表环;

 4)当访问哈希表链表环的这个位置时,会导致卡片死亡。

 

 解决方案:

 

 1)确保只有一个线程访问HashMap同时;

 2) ConcurrentHashMap用于多线程场景。

 

 6.监控方案的运行效果

 

 在对干扰进行自动分析后,将输出干扰问题的概览列表。

 

 以11月7日为例,在自动Q分析的线程被卡住后,输出卡住问题的以下概述:

 腾讯技术共享:安卓手q16 . jpg线程死锁监控系统的技术实践

 

 通过对上述问题的概述,我们可以看到每类卡住问题的数量和比例,便于总结问题。

 

 这些问题包括:

 

 1)死锁比例最高,达到35.6%,已经完全解决。

 2)其他非死锁问题:

 -已解析:IO、哈希映射、网络调用;

 -未解决:进程间接口、进程管理器、内存、堆栈抓取、气相色谱、文件输入输出等。

 

 通过几个版本的监控和问题解决,取得了良好的效果,如下所示:

 腾讯技术共享:安卓手q17 . jpg线程死锁监控系统的技术实践

 

 如上图所示:

 

 1)1)MSF线卡率由0.3%降至0.1%,普通线卡率由0.51%降至0.18%;

 2)730-灰色APM使用普通线程,导致严重的阻塞问题,可以及时发现并解决。

 

 总的来说,通过线程阻塞的监控和自动分析,以及发现阻塞问题的及时解决,手Q线程的阻塞率从版本到版本都有所下降,阻塞问题得到了有效控制。

 7.后续规划

 

 目前,在线卡监控方面还有一些需要改进的地方:

 

 1)目前,在自动分析问题后,仍然需要进行手工提单,后续需要自动提单;

 2)LockSupport锁持有的信息没有完全记录,但目前只记录数据库,其他使用LockSupport的锁不受监控。在后续工作中,采用钩子锁支持锁的方案来记录完整的信息,提高死锁监控问题的定位能力。



----------------------------------------------------------------------------------

哇谷im_im即时通讯_私有云_公有云-哇谷云科技官网-JM沟通

IM下载体验 - 哇谷IM-企业云办公IM即时聊天社交系统-JM 沟通下载

IM功能与价格 - 哇谷IM-提供即时通讯IM开发-APP搭建私有化-公有云-私有化云-海外云搭建

新闻动态 - 哇谷IM-即时通讯热门动态博客聊天JM沟通APP

哇谷IM-JM沟通热门动态博客短视频娱乐生活

关于哇谷-哇谷IM-提供企业即时通讯IM开发-语音通话-APP搭建私有化-公有云-私有化云-海外云搭建

联系我们 - 哇谷IM-即时通讯IM私有化搭建提供接口与SDK及哇谷云服务

即时通讯IM融云世界

IM即时通讯钉钉技术:企业IM钉钉在后端架构上的优越之处

新的市场叫板环信、融云、腾讯云!开源版IM即使聊天工具

企业IM即时通讯聊天办公APP钉钉技术分析交流

哇谷云-怎么样正确认识海外云服务器

公有云和私有云之间有什么区别?类似融云、环信云、网易云、哇谷云?