Golang 获取 goroutine id 完全指南

在Golang中,每个goroutine协程都有一个goroutine id (goid),该goid没有向应用层暴露。但是,在很多场景下,开发者又希望使用goid作为唯一标识,将一个goroutine中的函数层级调用串联起来。比如,希望在一个http handler中将这个请求的每行日志都加上对应的goid以便于对这个请求处理过程进行跟踪和分析。

关于是否应该将goid暴露给应用层已经争论多年。基本上,Golang的开发者都一致认为不应该暴露goid(faq: document why there is no way to get a goroutine ID),主要有以下几点理由:

  1. goroutine设计理念是轻量,鼓励开发者使用多goroutine进行开发,不希望开发者通过goid做goroutine local storage或thread local storage(TLS)的事情;
  2. Golang开发者Brad认为TLS在C/C++实践中也问题多多,比如一些使用TLS的库,thread状态非常容易被非期望线程修改,导致crash.
  3. goroutine并不等价于thread, 开发者可以通过syscall获取thread id,因此根本不需要暴露goid.

官方也一直推荐使用context作为上下文关联的最佳实践。如果你还是想获取goid,下面是我整理的目前已知的所有获取它的方式,希望你想清楚了再使用。

  1. 通过stack信息获取goroutine id.
  2. 通过修改源代码获取goroutine id.
  3. 通过CGo获取goroutine id.
  4. 通过汇编获取goroutine id.
  5. 通过汇编获取goroutine id.

在开始介绍各种方法前,先看一下定义在src/runtime/runtime2.go中保存goroutine状态的g结构:

其中goid int64字段即为当前goroutine的id。

1. 通过stack信息获取goroutine id

原理非常简单,将stack中的文本信息”goroutine 1234″匹配出来。但是这种方式有两个问题:

  1. stack信息的格式随版本更新可能变化,甚至不再提供goroutine id,可靠性差。
  2. 性能较差,调用10000次消耗>50ms。

如果你只是想在个人项目中使用goid,这个方法是可以胜任的。维护和修改成本相对较低,且不需要引入任何第三方依赖。同时建议你就此打住,不要继续往下看了。

2. 通过修改源代码获取goroutine id

既然方法1效率较低,且不可靠,那么我们可以尝试直接修改源代码src/runtime/runtime2.go中添加Goid函数,将goid暴露给应用层:

这个方式能解决法1的两个问题,但是会导致你的程序只能在修改了源代码的机器上才能编译,没有移植性,并且每次go版本升级以后,都需要重新修改源代码,维护成本较高。

3. 通过CGo获取goroutine id

那么有没有性能好,同时不影响移植性,且维护成本低的方法呢?那就是来自Dave Cheney的CGo方式:

文件id.c:

文件id.go:

完整代码参见junk/id.

这种方法的问题在于你需要开启CGo, CGo存在一些缺点,具体可以参见这个大牛的cgo is not Go. 我相信在你绝大部分的工程项目中,你是不希望开启CGo的。

4. 通过汇编获取goroutine id

如果前面三种方法我们都不能接受,有没有第四种方法呢?那就是通过汇编获取goroutine id的方法。原理是:通过getg方法(汇编实现)获取到当前goroutine的g结构地址,根据偏移量计算出成员goid int的地址,然后取出该值即可。

项目goroutine实现了这种方法。需要说明的是,这种方法看似简单,实际上因为每个go版本几乎都会有针对g结构的调整,因此goid int64的偏移并不是固定的,更加复杂的是,go在编译的时候,传递的编译参数也会影响goid int64的偏移值,因此,这个项目的作者花了非常多精力来维护每个go版本g结构偏移的计算,详见hack目录。

这个方法性能好,原理清晰,实际使用上稳定性也不错(我们在部分不太重要的线上业务使用了这种方法)。但是,维护这个库也许真的太累了,最近发现作者将这个库标记为“DEPRECATED”,看来获取goroutine id是条越走越远的不归路?

5. 通过汇编获取goroutine id

虽然方法4从原理和实际应用上表现都不错,但是毕竟作者弃坑了。回到我们要解决的问题上:我们并不是真的一定要获取到goroutine id,我们只是想获取到goroutine的唯一标识。那么,从这个角度看的话,我们只需要解决goroutine标识唯一性的问题即可。

显然,上面作者也想清楚了这个问题。他新开了一个库go-tls, 这个库实现了goroutine local storage,其中获取goroutine id的方式是:用方法4的汇编获取goroutine的地址,然后自己管理和分配goroutine id。由于它获取到的并不是真正的goroutine id,因此我将之称为goroutine id。其实现的核心代码如下:

  1. 获取g结构地址。
  2. 分配伪goroutine id.

这种方式基本没有什么不能接受的hack实现,从原理上来说也更加安全。但是获取到不是你最开始想要的goroutine id,不知你能否接受?

小结

获取goroutine id是一条不归路,目前也没有完美的获取它的方式。如果你一定要使用goroutine id,先想清楚你要解决的问题是什么,如果没有必要,建议你不要走上这条不归路。尽早在团队中推广使用context, 越早使用越早脱离对goroutine id的留恋和挣扎。

Credit

理工男是如何选择iPhone 8 (Plus)和iPhone X的

上一台手机是iPhone 6 Plus 64G, 使用到现在将近快3年半。6p在升级iOS 10以后,速度就算不上流程了,使用微信和支付宝的时候尤其明显。特别是使用支付宝付款的时候,经常卡在story board画面,没少受后面排队哥们白眼。

现在(2018.03)并不算是购买苹果这代手机最好的时机,因为一般最好的价格都是出现在双十一。而且本着早买早享受的原则,现在入手其实体验周期也间接变短了。这次考虑换机原因只有一个:6p实在是不能战斗下去了。虽然并不喜欢这代的iPhone,但是,也只能矮子里面拔将军了:选iPhone 8 (Plus)还是iPhone X?

习惯性的打开哈乎看了一下,纠结着两个选项的人好像还挺多的:

  • 你会买 iPhone 8 还是 iPhone X?
  • 你会选择买iPhone X还是iPhone 8,或者其他手机?
  • iPhone 8 (Plus) 和 iPhone X 你会买哪个?为什么?
  • iphone X 和 iphone 8/8plus 哪个更值得买?
  • iPhone 8/8p/X,不知道选哪个?

其实上面的问题只会越看越不知道自己选什么,因为每个人都有自己的需求和背景。

这里我就不卖关子,直接给出自己的选择:iPhone 8 Plus, 容量看自己的需要,因为自己的6p是64G且容量将满,因此选的是256G。主要理由如下:

face id解锁交互用在了错误的场景

face id解锁是一个自己无论如何也无法接受的交互方式。我仔细体验过同事的iPhone X,成功率尚可,但是做不到「存在于无形之中」。人脸解锁如果做不到这一点,就是一个半成品。

这一点类似于汽车行业之前大规模使用触屏代替物理按键(呃,好吧,马斯克同学还在坚持在特斯拉车型中大规模使用触屏……),不是说触屏控制本身存在技术障碍,而是在这个场景中使用了一个高成本且不合适的技术。

我甚至无法想象晚上在被窝中要解锁iPhone X时,必须要让手机的主动探照光闪瞎狗眼的酸辣情景。那么,什么场景是适合face id的呢?我认为是在结合其他id因子的安全领域,比如在大额支付时,使用touch id和face id进行双重校验。

iPhone X的全面屏是个笑话,全面屏最终都会变成笑话

齐刘海问题肯定算不上优点,但是问题并不大(好吧,其实苹果开发者还是挺吐槽这个刘海带来的额外开发工作量的……)。重点是,全面屏在当前阶段并不是一个手机尺寸上能够提升用户体验的发展方向。

以前手机尺寸的发展方向是变薄,有些厂家甚至以此作为卖点。但是几年下来,还有哪个厂家将自家机器薄作为卖点?就连以前乔帮主健在时,发布会现场直击从文件袋里拿出来的iPad Air也不在突出它薄的数值了。

空口无凭,我们看一下苹果历代16款手机的厚度数据对比:

结果是不是很意外?

  • 从iPhone 5开始,苹果手机的厚度基本就没有太大变化。
  • iPhone 6是迄今为止,苹果最薄的手机。
  • iPhone 6开始,苹果手机厚度是逐年上升的!
  • 更加有意思的是,iPhone X是水果这5年来最厚的手机?

那么这个问题跟全面屏有什么关系呢?我们先看一下全面屏的定义:

全面屏从字面上解释就是手机的正面全部都是屏幕,手机的四个边框位置都是采用无边框设计,追求接近100%的屏占比。但其实到目前为止没有一个严格意义上的全面屏幕定义,而比较主流的看法是要有18:9的屏幕,正面取消任何实体或者触摸按键,只有一块完整的屏幕,而且屏幕占比至少要达到80%以上。显然目前有很多手机虽然也号称全面屏幕,但屏幕占比却没有达到这个要求,所以看上去边框很宽,并没有特别好的效果。

所以,仔细想想,你就发现,在现阶段各家手机屏占比越做越高的今天,全面屏已经基本成为或接近事实,并不需要为了迎合这个概念而调整手机本来已经验证多年的设计,尤其是正面的按键和触感器排列。这一点上,锤子手机罗永浩是深谙其道的,他发布的坚果 2 Pro手机宣传语就很准确「Almost 全面屏」?

因此,当前阶段,行业对全面屏的疯狂追捧跟当年恨不得手机能够用来切菜的追逐是如此神似。当然,也有头脑清醒的人只是跟着喊口号,但是并没有真的跳进坑里,比如老罗。

手机是需要握持使用的,两边的边框是一定会有所保留的,无论是iPhone X还是小米的MIX系列。有意思的是,这两个厂家疯狂宣传他们全面屏的同时,却没有告诉你,他们把手机两旁的边框做了轻微加高和立面加厚处理,当你进行从屏幕边缘划入屏幕操作时,感觉手指被一个圆铁丝顶了一下一样,还不如以前「正常屏」手机顺滑。而iPhone X的全面屏还有一个额外的槽点:话说你提高屏占比是为了显示更多的信息吧,但是你看看iPhone X现在的屏幕显示情况,很多情况下屏幕下方很大一截都是空的,要这铁棒有何用?

小结苹果的全面屏:

  • 全面屏是一个伪概念,在当今屏占比越来越高的今天,全面屏就是个概念和幌子。
  • 在屏占比提到到一定程度以后,对用户体验影响的提高就很小了,甚至会降低用户体验。
  • 如果一定要为了全面屏而改变正面的一些布局,无疑是舍本逐末、因小失大的做法。

iPhone X的优点

但并不是说iPhone X就全无优点。

结合其深度传感器使用的Animoji可能如当初Emoji一样影响整个行业,前提是深度传感器能够迅速降低成本并普及。

摄像头双重防抖对于喜欢拍照的同学来说更是锦上添花。

OLED屏幕显示效果主观观感的确非常赞。

选择iPhone 8 Plus的困扰

  • 没有辨识度。其实换手机有好几天了,但是同事根本没有发现我换了手机。但是,iPhone早就过了可能装X的年代了,所以也没觉得损失什么。
  • 多年使用同一个设计ID的产品。6p用了三年多,8p估计也会用2年以上,意味着6年都用着一个外观的产品。这点自己并不排斥,要知道设计不是一直向前的,有时候还会轻微倒退。正如我一直觉得ThinkPad T61/T400的设计是一代经典,因此我现在还在用着这10年前的笔记本电脑做一些简单的文档处理和网页浏览。
  • 手机重量历代之最,用久了手腕疼。但是有些人觉得这才是质感
  • 玻璃后盖易碎。但是有些人认为这才叫温润如玉。

其实上面说的都谈不上困扰,只是你选了8p以后需要考虑能否接受的小妥协。

为什么不选择iPhone 7 (Plus)?

选iPhone 7 (Plus)的唯一原因当然是价格。但是,作为一名理工男+参数党,通过研究苹果这一代的芯片发现,iPhone 7可能并不适合自己。

从上图可以发现,这两代CPU的制程一个是16nm, 一个是10nm. (苹果历代CPU参数可以参见这里。 无论是理论还是实际测试,A11芯片都比A10芯片能效更高,续航时间更长。而iPhone 8 Plus更是做到了在电池容量比iPhone 7 Plus少10%左右的情况下,续航反而增加了一个多小时。

另一方面,A11采用的是两个高性能+4个能效核的设计,比A10多两个效能核。理论上A11的CPU跑分会更高。使用Geekbench 4也应证了这一点:

单核性能提升23%,多核性能提升76%!

之于iPhone 8 Plus加入的无线充电,聊胜于无吧。倒是快充功能在紧急场合能够发挥一点作用。

另一方面,由于自己换机频率不高,因此,对于自己而言,iPhone 8 Plus更加适合自己。而且这几天购买正好碰到3.8女王节活动,直降1000,性价比也不算低。

总结

不选iPhone X有很多理由,选择iPhone 8 Plus则是因为被动选择。虽然当前的iPhone X只是一个半成品,但是库克肯定认为它是以后苹果手机的发展方向,自己对下一代iPhone X也没有任何期待。因此iPhone 8就是苹果以iPhone 6为设计语言的一代产品的绝唱了,这也算是自己选8的一个原因。

微服务架构下的立体监控系统设计和实现

背景

GOPS全球运维大会(北京站)听到了不少干货。特别受益的是来自腾讯SNG事业部聂鑫分享的
《从0到1到N,腾讯监控体系全透视》

在他的主题分享中,他将腾讯这些年的监控系统的发展历程概括为点监控-->面监控-->深度监控

看到他这页幻灯片的时候,有一种醍醐灌顶的感觉。因为在听他分享的时候,我们的系统才刚刚完成架构微服务化没多久,我们上线了调用链:分布式追踪系统来解决在微服务分布式系统中排查跟踪特定问题,但我们的监控系统还没有针对架构微服务化后进行相应的进化。比如,大部分监控系统停留在点监控的层面,少数进行关联多个服务的面监控也做得比较初级,需要人工分析和干预。

点监控比较好理解,就是对系统布置监控点,根据阈值触发告警。

面监控则是对告警信息进行时间和空间关联,有效消除毛刺告警,使告警更加准确。因为告警本身有时效性,时效性源于告警延时,连续性可能是干扰,因此只进行时间关联是不够的。链路相关性(空间相关性)和时间相关性一起决定准确性。

深度监控其实有点追深度学习的热点,从分享看,实际就是对面监控的链路相关性进一步完善,以及根据收集到的系统进行使用机器学习进行简单的分类。

参加会议回来以后,我们明确了自己监控系统的进化方向,根据自身系统的特点进行了一些取舍,确定了立体监控的方案。

立体监控方案目标

所谓「立体监控」即指在我们当前系统点监控为主的情况下,尽可能复用当前监控的探针,进行时间和空间(服务链路之间)维度上的扩展,实现对整个系统时空上的监控。

立体监控需要消除点监控带来的监控毛刺,如服务存在依赖情况下,级联告警通过立体监控分析融合后,应该只对最后一级进行告警。

立体监控可以快速的定位系统故障,定位粒度根据不同监控类型可以做到微服务级别、接口级别、数据库实例级别、缓存实例级别等。

立体监控方案设计和实现

对于微服务,我们通过data bus将需要进行监控的信息发送到kafka进行收集。这种方式在调用链分布式追踪系统也有使用。不得不说,data bus是架构微服务化后非常重要和实用的基础组件。

为了尽可能降低各个微服务集成监控组件的侵入性,我们通过修改基础库的方式进行集成。比如,微服务使用了MySQL数据库,那么我们就修改微服务使用的数据库驱动来对数据库进行监控,一旦发生错误或warning信息,将消息写入data bus;某个微服务需要调用腾讯的某个接口,我们就对修改我们的http客户端基础库,将错误和超时消息写入data bus实现对该接口的监控。

因此,我们的微服务监控集成几乎不需要研发人员的介入,只需要运维人员更新服务依赖库,然后重新发布上线即可。事实上,我们在微服务监控集成上就没有安排独立的发布上线时间,都是在研发上线feature或hotfix时搭车上线的,将近两百个微服务在2周内完成集成。需要注意的是,基础库的修改一定是由团队中相对资深的开发人员来做,并且测试一定要做到位,否则会引起大规模的问题。

对于k8s集群、数据库、redis等基础设施的监控沿用以前的点监控数据,只是将事件统一上报到了Event Collector事件收集服务。Event Collector除了收集事件数据,也对一些不符合通用规则的数据进行过滤。

Event Analyzer事件分析服务根据Event Collector收集到的事件,进行时间和空间上的分析监控结果,并发送告警通知。

在事件分析上,时间维度非常简单,选取一个时间窗口内的事件信息即可(我们当前根据经验设定的是1min)。空间维度方面,则相对麻烦一下。我们没有采用腾讯使用的全链路分析算法。主要是因为该算法需要预生成链路拓扑图。而微服务架构中,各个微服务的增加、减少和变更是非常频繁的,预生成拓扑图有点反模式,也会产生一定的成本。我推测腾讯之所以觉得预生成拓扑图不是问题,跟它当前架构没有完全微服务化以及内部严格的管理流程有关。

因为不想预生成链路拓扑图,分享中给出的链路面积计算公式也就无法使用。另一方面,因为腾讯给出的全链路分析算法其实没有完备的理论证明,在我们数据量没有腾讯庞大的情况下,我们是否能用该算法取得同样效果是缺乏信心的。

但是,思想是可以借鉴的。通过讨论,我们一致认为所谓的链路分析其实就是关联性分析,而关联性分析那就毫不犹豫的使用Google Page Rank算法:

如上图所示,我们将服务(1, 2, 3, 4)之间的告警事件作为一个超级链接指向,然后计算PR值,那么,PR值最大的是我们认为出现问题可能性最大的服务。理论证明这里略过,这个锅我们扔给Google背即可。

需要说明的是,任何一个复杂的系统在出现故障时,往往不是一个组件或服务出现问题,很可能是多个服务同时出现问题。那么在计算PR时,可能就是面对多个独立的PR有向图。这个时候独立图之间的关系处理就可以根据历史数据进行机器学习,以进一步给出故障原因,然后根据预案快速处理故障,恢复服务。

Event Analyzer也提供了一个页面,可以查看历史的告警信息链路:

也可以对链路流量(边越粗流量越大)进行监控和分析:

总结

立体监控上线后,运维方面以前只能从点入手排查问题转变为直接根据Event Analyzer的聚合告警信息联系对应服务的开发者解决问题。同时,发现了很多以前被经验标记为系统抖动没有重视的潜在问题。至此,我们的微服务架构在调用链和立体监控的双重加持下,又完成了一次进化。