时序异常数据检测从理论到落地

最近在解决一个挺有意思的问题,该问题可以抽象简化为:如何检测外部依赖接口发生异常。如果是在中小公司,那么这个问题其实是不需要解决的,从研发层面规范接口的格式,并规定依赖接口必须达到要求的可用性,只要调用成功率低于规范就进行告警。不幸的是,这个方法在当前的BU中是推不动的,是为背景。

简单的对接口调用失败量对齐时间轴看了一下,这是一个典型的时序数据异常检测问题。

从分类看,当前发展阶段的时序异常检测算法和模型可以分为一下几类:

  • 统计模型:优点是复杂度低,计算速度快,泛化能力强悍。因为没有训练过程,即使没有前期的数据积累,也可以快速的投入生产使用。缺点是准确率一般。但是这个其实是看场景的,并且也有简单的方法来提高业务层面的准确率。这个后面会提到。
  • 机器学习模型:鲁棒性较好,准确率较高。需要训练模型,泛化能力一般。
  • 深度学习模型:普遍需要喂大量的数据,计算复杂度高。整体看,准确性高,尤其是近段时间,强化学习的引入,进一步巩固其准确性方面的领先优势。

而我们希望在9月份就能够上线运行,并且没有历史数据,更不要提打标数据了。因此,只能选择统计模型作为一期落地的方案。而在统计模型中,twitter 在2015年发布的 AnomalyDetection 自然是翘楚。如果你正好使用 R 语言,那么直接上手就可以用。如果你需要 pure python 版本,推荐使用 Twitter’s Anomaly Detection in Pure Python.

S-H-ESD 原理

twitter 公开的异常检测算法的核心是使用了S-H-ESD异常检测算法。这种算法的思想是将时序数据使用 STL 分解,然后将分解的余项使用 Grubbs’ Test 进行异常点的检测(实际使用的算法考虑了极值异常点对整体的影响,实际使用的是的Grubbs’ Test变形)。关于算法的细节可以参看 twitter 发布的论文 Automatic Anomaly Detection in the Cloud Via Statistical Learning. 显然,这个算法之所以有效的两个关键就是 STL 和 Grubbs’ Test。

STL 将时序数据分解为 趋势 + 周期 + 余项:

直观上,可以将趋势项理解为时序数据的骨骼;周期数据是数据的振幅;余项是则是消除趋势和周期数据后,相对平滑稳定的“皮毛”。而这种皮毛数据是符合 Grubbs’ Test 假设中正常数据正态分布的。反之,则被 Grubbs’ Test 认为是异常数据。

因此,S-H-ESD 只适用于周期性数据。对于无周期性或数据变化特别剧烈的时序数据,S-H-ESD都不是好的选择。

S-H-ESD 用于生产环境

S-H-ESD 原理简单,理论效果也非常不错,基本起手能达到 40% ~ 60% 的准确率。但是实际应用中经常会遇到以下典型的误报情况:

而这种误报其实很难从算法本身消除,即使消除了其实也没有泛化性。一种简单的思路是引入更多数据和规则:

  • 上图中我们只是用的接口的报错量作为时序数据,单村在报错量上提高准确性在统计模型这个大前提下边际成本已经很高了。因此,可以考虑引入接口调用量和成功率来综合判断该点是否真正异常。
  • 结合实际业务,设定一些简单的规则减少误报量。对于我们的场景,可以设定的规则有报错量的基础阈值、报错点的持续时长等。

S-H-ESD 不是银弹,结合多维数据和业务规则以后,准确率基本达到了我们的预期。S-H-ESD 也不是终点,确切说是我们顺便解决当前问题,同时收集异常数据的手段。未来,我们会尝试结合深度学习模型提高异常检测点上的准确性,同时融合多维数据,将点上的异常检测逐步整合为线和面上的检测能力。

扩展阅读

GopherChina 2019 keynote 点评

今年的 GopherChina 大会如期而至,没能亲临现场,但是 keynote 绝不会错过。一如往常,谢大第一时间放出了今年的keynote。今年的 keynote 中有不少老面孔,不知道以后大会是否会把固定若干老面孔作为惯例。如果你错过了去年的 keynote, 可以参见鄙人拙文《GopherChina 2018 keynote 点评》

整体上,今年的演讲主题跟往年所涉及的领域和覆盖的范围区别不大,无论你是关注架构、微服务、语言细节,还是数据库、存储、业务及应用系统构建,都能从中找到自己感兴趣的内容。

1.1 大型微服务框架设计实践 – 杜欢

如果你曾经想用比较hack的方式获取goroutine id, 那么你有很大可能性使用过杜欢的goroutine. 也因为写Golang 获取 goroutine id 完全指南的缘故,跟杜欢结识。看到这个keynote,心里还是有种从未谋面,但是久违的熟悉感。在大概3年前,我其实也做过类似的框架设计和开发。很多理念和原则的确是 cant't agree more. 其中,“框架和业务正交”的原则也是充分发挥了golang自带的正交特性。

在框架中,隔离层的思想很朴素,但是很实用。我曾经因为在设计之初没有引入隔离层,自己手动修改了多个数据库驱动库,以满足框架某个特性的引入。如今想想,真的是血与泪的教训。

1.2 用Go打造Grab的路径规划和ETA引擎

不得不感慨,Grab 的业务才是真的大型生活类服务。从形态上看,已经约等于国内滴滴+美团+顺丰组合了。演讲内容偏向算法。对地图路径规划(无论是游戏地图还是现实地图)感兴趣的同学,可以看看算法到实际工程落地之间的gap如何弥补和解决。

1.3 Go practices in TiDB – 姚维

印象中,PingCAP 出来的speaker分享质量一直都挺高。姚维老师的这次分享也保持了PingCAP一如既往的高水准,深入浅出,以小见大。一直比较好奇TiDB这种对软件质量要求极高且分布式的领域是如何做测试的,看了其Schrodingergofail 的介绍,无论从主观体感还是技术信赖都TiDB加分不少。failpoint 在实现层面是基于golang AST 做的,编译时被转换为一个 IF 语句,整体设计简单直接有效,是我喜欢的风格。

另外一个比较有意思的点是使用 chunk 来优化内存使用。以前只知道使用整块连续的内存分配策略比碎片化的内存分配更有效率,但是不知道连续内存带来的矢量化执行优势。如果你是做高性能数据库的,这个点一定不能不知道。

1.4 Testing; how, what, why – Dave

Golang官方人员 Dave 大胡子老师出品,必属精品。关于golang如何做测试的资料,看这一个就够了。

1.5 Go 业务开发中 Error & Context – 毛剑

在 golang 1.x 中,错误处理一直是一个不太舒服点。因此才有去年 Rethinking Errors for Go 2 对golang 2.x 错误处理的预览和展望。但是,golang 2 是没有具体时间表的,当前阶段,如果你在实际业务系统中对错误处理有疑惑,可以看看毛剑的处理方式。

Context 其实算是一个老生常谈的话题了,但是毛剑总结了很多实际使用中的最佳实践,分享内容还是诚意满满的。

1.6 Go并发编程实践 – 晁岳攀

从源码级别探究Go在并发层面的基础库实现。跟去年的深入CGO编程一个风格,内容非常全面和丰富,有细节有深度。如果想深入golang源码,一定不可以错过。

1.7 百度APP Go 语言实践 – 陈肖楠

从ppt内容看,算是一个大厂在小场景的golang实践。涉及的问题,以鄙人浅见:使用golang落地1年以内的创业公司都会遇到。给出的解决方案和踩过的坑已经远看不到国内巨头的风范了。如果百度再被扣上技术不行的标签,那就是哪都不行了……

1.8 Golang to build a real-time interactive SaaS Cloud – 董海冰

golang 在 WebRTC 场景下的工程实践。以前对 WebRTC 比较模糊,细致看了分享内容以后,才发现这块的内容和涉及的技术如此广博。前端时间,提供视频会议解决方案的 zoom 上市了,日后我们应该有很大概率看到更多 golang 和 WebRTC 的落地方案。

2.1 基于MINIO的对象存储方案在探探的实践 – 于乐

作者用 golang 撸了一个支持多集群的分布式对象存储系统。有两个技术细节值得技术投资和持续关注:

  1. Reed-Solomon,一种低冗余,高可靠的纠删码。golang 版本的实现可以参见reedsolomon.
  2. The Linux Storage Stack Diagram. 能让你系统全面的了解 IO,并且知道 Direct IO, page cache 的本质。

2.2 从零开始用 Go 实现 Lexer & Parser – 何源

作者编译原理的底子还是在的。想当年,我们该课程的期末课程设计就是编写一个编译器。不过大部分时候,如作者所言,如果不是万不得已,不要自己写 parser. 毕竟,在不使用正则表达式的前提下,golang 提供了非常完善易用的 AST 基础库支持。

2.3 高性能高可用的微服务框架TarsGo的腾讯实践 – 陈明杰

golang和微服务经过这几年的演进发展,无论是基础框架还是周边生态,已经达到了水乳交融的程度。鹅厂的这个实践从当前时间点看,没有什么亮点,更没有什么突破。本以为会有一些 service mesh 方面的尝试,但是比较遗憾,这方面从分享内容看还走得比较靠后。

2.4 闪电网络—BTC小额支付解决方案 – 方圆

不知道这个方圆老师跟去年代表罗辑思维做分享的speaker是不是同一个人?如果是的话,真的是选错了行业风口呀。币圈有风险,跳巢需谨慎。

2.6 用Go构建高性能数据库中间件- 徐成选

一个使用golang打造中间件的实践。文末提到了一些优化方案和细节,挺受用。

2.7 花椒直播基于golang的中台技术实践 – 周洋

周洋老师也是老面孔了,第一次出现在gopher大会应该是大表360做IM长连接的分享。听那一次分享自己几乎是跪着听完的,因为在那之前自己要解决的问题和场景跟其非常类似,只是碍于当时的人手和自己的技术栈储备,我没能做出周洋那样的方案和架构,而是用了一个比较trick的方案。晃眼间,4年过去了,周洋对于中台的思考又给了自己很多启发。感谢 GopherChina 这样的平台,感谢周洋老师的分享。

2.8 知乎社区核心业务 Golang 化实践 – 杜旭

作者分享了知乎从 python 迁移到 go 的历程。巧合的是,三年前,我们也做了同样的事情,同样是从 python 迁移到 go. 不过作者有几点做得比当时的我们更好:

  1. 在接口验证环节上,我们当时希望靠尽可能覆盖全面的单元测试和QA验证来保证;知乎在额外还引入了python和go版本的接口交叉校验。test case的丰富和覆盖程度应该比我们当年更好。
  2. 引入了静态代码检查。如果用强类型语言不适用静态代码检查,那么就损失了强类型语言一般的优势。道理都知道,但是碍于当时CI/CD流程不够完善,我们这个环节一直是缺失的。

注意

以上内容只是看完keynote以后的个人观感。因为没有去现场,细节肯定有所缺失,有些观点也未必跟现场同学的反馈吻合。希望后面放出大会现场视频以后,自己能够进一步完善以上内容。

TLS 1.3 当前(2018.10)支持与部署之现状

今年8月10日,历经三年有余,TLS 1.3 最终版本终于得以发布——RFC 8446. 关于 RFC 的详细介绍可以进一步阅读 A Detailed Look at RFC 8446 (a.k.a. TLS 1.3). TLS 1.3 因为其在握手延迟以及安全性上的改进 (可以参考拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》),毫不夸张的说,这是一件将深刻而长远影响互联网发展的技术里程碑。那么将 TLS 1.3 尽快平滑应用到线上环境无疑是一件势在必行的事情了。

在我日常的工作中,对于 TLS 1.3 的支持和部署主要关注两个层面: 编程语言(Go, Java)、 API gateway (Nginx) 和浏览器. 下面分别介绍一下这三个层面 TLS 1.3 的支持部署现状。(因为 RFC 8446 已经发布,因此本文中说的支持如无特殊说明,都是指对最终版本 RFC 8446 的支持。)

编程语言对 TLS 1.3 的支持

Go 方面,官方在 TLS 1.3 draft 阶段一直没有跟进。因此,有一个关于对 TLS 1.3 支持的 issue 从 2015 年 open 至今都没有关闭:crypto/tls: add support for TLS 1.3. 其实 Go 发布版本和改进标准库的效率还是挺高的,对于 TLS 1.3 上的“不作为”更多是因为 Go 在兼容性上的承诺导致其并适合在最终版发布前实现互不兼容的 draft 方案。

而 Go 1.11 的发布时间(2018.08.24)与 RFC 8446 的发布时间比较接近,没有足够时间实现并发布该特性。从 golang-dev 小组讨论 Re: crypto/tls and TLS 1.3 看,由于 1.11 没有实现 TLS 1.3 ,那么 1.12 实现 TLS 1.3 基本是板上钉钉的事了:

The key scheduling fact is that the Go 1.11 feature freeze is just a week away, so we decided that it would be too rushed to merge the 1.3 patches for it. I definitely aim to have TLS 1.3 in Go 1.12.

根据惯例, Go 1.12 的发布时间将会是 2019.02~03. 如果期间你实在想用 Go 编程测试 TLS 1.3, 可以尝试使用 CloudFlare 的 tls-tris 库。根据 Go net/http 标准库维护者 Brad Fitzpatrick 的消息,这个库将会被合并到标准库作为 Go 官方 TLS 1.3 的实现。因此,如果你不得不用这个库干一些生成环境的活也大可放心,即使日后升级 Go 1.12, 接口兼容性还是有保证的。

Java 方面,由于 Java 11 出生时间好(2018.09.25), 因此是出生就支持 TLS 1.3 RFC 8446, 具体可以参见 JEP 332: Transport Layer Security (TLS) 1.3. Java 11 是 LTS 版本,因此,如果有条件升级到 11, 推荐使用 Java 11 实现的 TLS 1.3 以及配套的 HttpClient;如果生产环境暂无法升级 Java 版本,推荐使用 OkHttp. 关于 Java Http Client 选型可以参见Java HTTP 组件库选型看这篇就够了

Nginx 对 TLS 1.3 的支持

准确讲应该是 Nginx 所使用 SSL lib 对 TLS 1.3 的支持。在这方面,Boring SSL 跟进速度飞快,在 RFC 发布后第4天实现了对最终版本的支持。OpenSSL 虽然很早就跟进了 draft 的实现,但是对最终版本的支持需要 1.1.1-pre9 及以后的版本:

The OpenSSL git master branch (and the 1.1.1-pre9 beta version) contain our development TLSv1.3 code which is based on the final version of RFC8446 and can be used for testing purposes (i.e. it is not for production use). Earlier beta versions of OpenSSL 1.1.1 implemented draft versions of the standard. Those versions contained the macro TLS1_3_VERSION_DRAFT_TXT in the tls1.h header file which identified the specific draft version that was implemented. This macro has been removed from 1.1.1-pre9 and the current master branch.

TLSv1.3 is enabled by default in the latest development versions (there is no need to explicitly enable it). To disable it at compile time you must use the “no-tls1_3” option to “config” or “Configure”.

Although the latest 1.1.1 versions support the final standard version, other applications that support TLSv1.3 may still be using older draft versions. This is a common source of interoperability problems. If two peers supporting different TLSv1.3 draft versions attempt to communicate then they will fall back to TLSv1.2.

而第一个 OpenSSL 1.1.1 release 是在 2018.09.11, 因此如果你跟我一样是 OpenSSL 的死忠粉,当前阶段 Nginx 支持 TSL 1.3 的最佳方式是 Nginx 1.15.5 + OpenSSL 1.1.1. 而这种脏活、苦活、累活当然是交给 Docker 解决了:从源代码编译 nginx docker 镜像开启 TLS 1.3,项目地址可以参见 docker-nginx.

配置 Nginx 支持 TLS 1.3 需要注意一点:默认情况下 Nginx 因为安全原因,没有开启 TLS 1.3 0-RTT,可以通过添加 ssl_early_data on; 指令开启 0-RTT. 完整配置可以参考 nginx.conf.

浏览器对 TLS 1.3 的支持

当前阶段,Chrome 69 和 Firefox 62 都只支持到 draft 28, 而 draft 28 与最终版本是不兼容的。因此,要测试体验 TLS 1.3 final 需要使用 Chrome Beta 测试版。然后在 chrome://flags/#tls13-variant 开启 TLS 1.3 final:

扩展阅读