思路排查
Dump 堆栈很重要
关键思路
终于找到你
思路整理
发现蛛丝马迹
完整的推理流程
思考总结
分享一个线上遇到的死锁问题,什么, golang 也会有死锁?
线上某个环境发现 S3 上传请求卡住,请求不返回,卡了30分钟,长时间没有发现有效日志。一般来讲,死锁问题还是好排查的,因为现场一般都在。类似于 c 程序,遇到死锁问题都会用 pstack 看一把。golang 死锁排查思路也类似(golang 不适合使用 pstack,因为 golang 调度的是协程,pstack 只能看到线程栈),我们其实是需要知道 S3 程序里 goroutine 的栈状态。golang 遇到这个问题我们有两个办法:
2 . 方法二:如果你提前开启 net/pprof 库的引用,开启了 debug 接口,那么就可以调用 curl 接口,通过 http 接口获取进程的状态信息;
需要注意到,golang 程序和 c 程序还是有点区别,goroutine 非常多,成百上千个 goroutine 是常态,甚至上万个也不稀奇。所以我们一般无法在终端上直接看完所有的栈,一般都是把所有的 goroutine 栈 dump 到文件,然用 vi 打开慢慢分析。
调试这个 core 文件,意图从堆栈里找到些东西,由于堆栈太多了,所以就使用 gorouties -t -u
这个命令,并且把输出 dump 到文件;
curl xxx/debug/pprof/goroutine
成千上万个 goroutine ,直接显示到终端是不合适的,我们 dump 到文件 test.txt,然后分析 test.txt 这个文件。去查找发现了一些可疑堆栈,那么什么是可疑堆栈?重点关注加锁等待的堆栈,关键字是 *runtime_notifyListWait
、semaphore
、`sync.(Cond).Wait、
Acquire` 这些阻塞场景才会用到的,如果业务堆栈上出现这个加锁调用,就非常可疑。**
划重点:
runtime_notifyListWait
、semaphore
、sync.(*Cond).Wait
、Acquire
;2 . 业务堆栈(非 runtime 的一些内部堆栈)
统计分析发现,有 11 个这个堆栈都在这同一个地方,都是在等同一把锁 blockingKeyCountLimit.lock
,所以基本确认了阻塞的位置,就是这个地方阻塞到了所有的请求,但是这把锁我们使用 defer 释放的,使用姿势如下:
// do someting
lock.Acquire(key)
defer lock.Release(key)
// 以下为锁内操作;
blockingKeyCountLimit 是我们封装针对 key 操作流控的组件。举个例子,如果 limit == 1,key为 "test" 在 g1 上 Acquire 成功,g2 acquire("test") 就会等待,这个可以算是我们优化的一个逻辑。如果 limit == 2,那么就允许两个人加锁到,后面的人都等待。
从代码来看,函数退出一定会释放的,但是偏偏现在锁就卡在这个地方,所以就非常奇怪。我们先找哪个 goroutine 占着这把锁不释放,看看能不能搞清楚怎样导致这里抢不到锁的原因。
通过审查业务代码分析,发现可能的源头函数(这个函数是向后端请求的函数):
api.(*Client).getBytesNolc
确认是 getBytesNolc
这个函数执行的操作,那么大概率就是卡在这个地方了。用这个 getBytesNolc
字符串搜索堆栈,找下是哪个堆栈 ?搜索到这个堆栈 goroutine 19458
大概率就是第 1 个堆栈了,也就是其他的 11 个 goroutine 都在等这 goroutine 19458
来放锁,仔细看这个堆栈。那么为啥这个堆栈不放锁呢?这里有个细节要注意下,这里是卡到 gihub.com/golang/groupcache/singleflight/singleflight.go:48
这一行:
这是一个开源库,singleflight 实现了缓存防击穿的功能。
简单减少下 singleflight 的功能,这是一个非常有效的工具。在缓存大量失效的场景,如果针对同一个 key ,其实只需要有一个人穿透到后端请求数据,其他人等待他完成,然后取缓存结果即可。这个就是 singleflight 实现的功能。具体实现就是:来了请求之后,把 key 插入到 map 里,后面的请求如果发现同名 key 在 map 里面,那么就等待它完成就好;
截屏显示卡到 c.wg.Wait()
这一行,那么说明 map 里面肯定有已经存在的 key,说明 goroutine 19458
不是第一个人?但是外面还有一个 blockingKeyCountLimit
的互斥呢,按道理其他的人也进不来(因为 limit == 1),这里这么讲来肯定要是源头才对?
伪代码显示如下:
func xxx () {
// 大部分协程都卡在这里(11个)
// 这个锁的效果主要是流控,limit 值初始化赋值,可以是 1,也可以是其他;
// locker 为 blockingKeyCountLimit 类型
limitLocker.Acquire( key )
defer limitLocker.Release( key )
// 获取数据
getBytesNolc( key , ...)
}
func getBytesNolc () {
// ...
// 下面就是 singleflight.Group 的用法,防穿透
// 同一时间只允许一个人去后端更新
ret, err = x.Group.Do(id, func() (interface{}, error) {
// 去服务后台获取,更新数据;
})
// ...
}
图示显示当前的现状:
现状小结:
blockingKeyCountLimit
这把锁释放;goroutine 19458
持有 blockingKeyCountLimit
这把锁;goroutine 19458
却在等一个相同 key 名字的任务的完成( singleflight 一个防击穿的库,同一时间相同 key 只允许放到一个后端去执行),却永远没等到,协程因此呈现死锁;当前的疑问就是第一个 key 的任务为啥永远完不成,堆栈也找不到了,去哪里了?
我们再仔细审一下 singleflight 的代码:
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
// 如果找到同名 key 已经存在;
if c, ok := g.m[key]; ok {
g.mu.Unlock()
// 等待者走到这个分支:等待第一个人执行完成,最后直接返回它的结果就行了;
c.wg.Wait()
return c.val, c.err
}
// 如果同名 key 不存在(第一个人走到这个分支)
c := new(call)
c.wg.Add(1)
// map 里放置 key
g.m[key] = c
g.mu.Unlock()
// 执行任务
c.val, c.err = fn()
// 唤醒所有的等待者
c.wg.Done()
g.mu.Lock()
// 删除 map 里的 key
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err
}
发现有个线索,我们的 S3 服务程序一个 http 请求对应一个协程处理,为了提高服务端进程的可用性,在框架里会捕捉 panic,这样确保单个协程处理不会影响到其他的请求。基于这个前提,我们假设:如果 fn()
执行异常,panic 掉了,那么就不会走 delete(g.m, key)
的代码,那么 key 就永远都残留在 map 里面,而进程却又还活着。恍然大悟。
完整的推理流程
blockingKeyCountLimit
锁,然后准备穿透到后端,调用函数 getBytesNolc
获取数据,并走进了 singlelight
,添加了一个 key:x, 准备干活;a . 干活发生了一些不可预期的异常(后面发现是配置的异常),nil 指针引用之类的, panic 堆栈了,panic 导致后面 delete key 操作没有执行;
b . 虽然 g1 现在 panic 了,但是由于在函数 func xxx 里面 blockingKeyCountLimit
是 defer 执行的,所以这把锁还是,但是 singlelight
的 key 还存在,于是残留在 map 里面;
c . 但是由于我们服务程序为了高可用是 recover 了 panic 的,单个请求的失败不会导致整个进程挂掉,所以进程还是好好的;
2 . 第二个 goroutine 19458
协程来了,blockingKeyCountLimit
加锁,然后走到 singlelight
的时候,发现有 key: x 了,于是就等待;
a . 并且等待的是一个永远得不到的锁,因为 g1 早就没了;
3 . 后续的 11 个 协程来了,于是被 blockingKeyCountLimit
阻塞住,并且永远不能释放;
实锤:后续基于这个猜想,再去搜索一遍日志,发现确实是有一条 panic 相关的日志。这个时间点后面的请求全部被卡住。
一般来讲 c 语言写程序容易出现死锁问题,因为各种异常逻辑可能会导致忘记放锁,从而导致抢一个永远都不可能得到的锁。golang 为了解决这个问题,一般是用 defer 机制来实现,使用姿势如下:
func test () {
mtx.Lock()
defer mtx.Unlock()
/* 临界区 */
}
golang 的 defer 机制是一个经过经验沉淀下来的有效功能。我们必须要合理使用。defer 实现原理是和所在函数绑定,保证函数 return 的时候一定能调用到( panic 退出也能),所以 golang 加锁放锁的有效实践是写在相邻的两行。
其实思考下,singleflight 作为一个通用开源库,其实可以把 delete map key 放到 defer 里,这样就能保证 map 里面的 key 一定是可以被清理的。
还有一点,其实 golang 是不提倡异常-捕捉这样的方式编程,panic 一般不让随便用,如果真是严重的问题,挂掉就挂掉,这个估计还好一些。当然这是要看场景的,还是有一些特殊场景的,毕竟 golang 都已经提供了 panic-recover 这样的一个手段,就说明还是有需求。这个就跟 unsafe 库一样,你只有明确知道自己的行为影响,才去使用这个工具,否则别用。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/LhP4Yom1NdgmQi-0kwkRfQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。