微服务troubleshooting利器——调用链

微服务troubleshooting利器——调用链

Docker、k8s以及「微服务」是过去两年(2016~2017)对互联网系统架构影响最深远的技术和理念。自己非常幸运,全称参与了公司新架构的设计、研发与迁移,目前公司整个架构已经完成了微服务1.0的建设,正在朝着微服务2.0的方向继续前进(关于微服务1.0与2.0的关系和差异,后面有空了单独在写)。期间有非常多有意思的事情值得与大家分享。今天先聊一聊微服务中的调用链系统。

微服务的喜与悲

从以前的巨服务完成微服务化后,服务的数量大幅增加,系统进化为完全的分布式系统。对运维、上线的自动化的要求自不必说,对研发troubleshooting也有了更高的要求。troubleshooting面临的挑战主要体现在:

  1. 服务日志分散化。以前在一个巨服务里面大多时候可以顺利的将一次调用的所有日志查完,但是现在日志分散在各个服务之中。
  2. 服务之间的日志需要某种手段进行关联查询。
  3. 由于日志量巨大,直接从原始日志进行关联查询没有普适性。因此,需要对这种关联数据进行索引单独存储。

这是一个有意思的挑战。更有意思的是Google早在2010年发表的论文Dapper, a Large-Scale Distributed Systems Tracing Infrastructure中就已经系统的阐述了如何在分布式系统中使用调用链来解决该问题,更更有意思的是,国内几乎所有介绍调用链系统的文章都会提到这篇论文,也会引用下面这张图?‍♀️……

这个问题虽然在国内已经被谈及了多次,但是我们发现大部分文章都是介绍如何基于open-trace, zipkin建立一个跟踪系统,即使有自建系统的分享,也相对缺乏对构建适合自己的调用链系统时的考量和分析。因此,这里主要谈谈我们在做调用链系统时的设计理念和取舍。

cdapper调用链系统

我们的调用链系统取名cdapper, 显然基本思想来自Google Dapper论文,对此我们毫不避讳。字母c则是公司首字母。

我们没有采用开源的dapper系统,而是完全自己实现。主要有以下原因:

  1. 开源的dapper系统为了顾及通用行和普适性,代码都非常臃肿。这与我们一贯主张的「大系统小做」原则不符。
  2. dapper的思想并不复杂,甚至可以说非常简单,因此自己实现这套系统并不是「没问题创造问题去解决」。事实上,我们的dapper数据采集核心代码(golang实现)也就400行以内。
  3. 我们的基础服务组件有很多定制功能,直接使用开源dapper系统不可避免设计相关系统改造,而我们自己实现系统,可以从设计之初就与已有基础组件整合。

在我们的系统中,各微服务主要通过http API进行交互,下面以API调用为例进行说明:

  1. 流量通过gateway服务进入系统,gateway会在每一个http API请求的header中生成一个trace_id,然后将该请求分发给各个微服务。
  2. 微服务接收到请求后,会生成一个子节点span_id。以service_0为例,生成子节点span_id_0, service 0调用service 1, 则继续生成子节点span_id_1. trace_id, span_id_0, span_id_1,...形成链式关系。
  3. 节点的粒度粗细无须局限在http API级别,各个微服务可以根据自身业务特点,进一步将子节点细化到每一个redis调用、每一次mysql查询等。
  4. 我们使用自己深度定制的gin作为http 微服务的基础框架,通过中间件的方式来集成调用链。大部分时候,添加一行代码即可完成调用链集成。此外,我也提供redis, mysql, rpc等其他基础组件的低侵入集成方式。
  5. 调用链数据统一输出到我们的data bus的基础组件。data bus是一个架构上的逻辑概念,目前实现层面主要通过文件日志和kafka承载。我们不推荐使用tcp网络传输调用链数据。主要有以下考虑:
    1. 我们使用的是阿里云,而阿里云内网抖动几乎是家常便饭。使用网络传输调用链数据稳定性差。
    2. 由于业务性质,流量存在突发性,我们需要一个高吞吐的的消息队列来缓冲这种尖峰流量。在这方面,我们非常信赖kafka.
  6. data bus是我们整个分布式系统的数据总线,我们在这个总线上安插了很多子系统,其中一个子系统是log sink日志数据汇聚系统(其他子系统后面有空了介绍)。该系统对调用链数据进行收集,然后存储在cassandra中。在这方面,我们非常信赖cassandra, 尤其是其优异的写入性能。如果你对我们如何存储调用链数据感兴趣,可以看上一篇文章LSM Tree/MemTable/SSTable基本原理了解细节。
  7. 每一个http API请求的响应中都会在header中返回trace_id。研发通过trace query进行调用链查询:

小结

微服务不是银弹,在带来很多好处的同时,也对整个系统架构的其他基础提出了更高的要求。微服务系统要先运作起来,一个简单、高效的系统诊断工具对系统微服务化的落地有着非常重要的促进作用。调用链就是一把这样的瑞士军刀。当然,调用链其实已经是8年前的理念了,如果你关心这个选题的最新发展方向,你还需要持续保持对世界上最大的分布式系统公司——Google的关注?!

LSM Tree/MemTable/SSTable基本原理

LSM Tree/MemTable/SSTable基本原理

时光飞逝,截至今天,2018的进度条已经毫不留情的燃烧掉了8.5%。

2017接触了很多新事物,也实践和落地了一些有意思的技术、产品和框架。要想走得快,一个人走,要想走得远,得学会多回头看,多总结。这也是接下来一系列文章的初衷。当然,前提是自己能够坚持写下去,?

是为记。


背景

2017年,做调用链服务的时候,为了存储整个系统的调用事件数据,遇到了一个存储上的问题:数据每天的写入量大概在10亿级别,也就是1.1w rps(record per second), 加上高峰期流量波动和系统冗余,我们把及格线定为3w rps。这是一个典型的写多读少的场景,自然直接放弃了关系型数据库;同时考虑到写入的时序特性,选型基本锁定到基于LSM Tree为存储引擎的数据库上。挑战依然在,但是基于LSM Tree的数据库一大把(HBase, Cassandra, RockDB, LevelDB, SQLite…),解决问题无非是时间问题。

我们先尝试了ssdb,号称可以替代redis, 一些指标上快过redis. 结果被坑得体无完肤:

  1. key不支持过期(2017.04);
  2. 写入性能压测只有2w qps,如果数据记录增大,性能迅速下降;
  3. 翻了下代码实现,虽然很失望,但是感觉因此避开了一个定时炸弹而庆幸?

在同事推荐下,我们尝试了Cassandra. 虽然国内用得不多,但是在《微服务架构》中,看到了奈飞(Netflix)的大规模使用案例,信心还是有的。实际压测结果:单节点写入性能在8w qps,超出预期。此外,系统上线后,同事花了大量时间调优参数,目前线上的单节点性能应该远超8w qps.

基本概念

LSM Tree (Log-structured merge-tree) :这个名称挺容易让人困惑的,因为你看任何一个介绍LSM Tree的文章很难直接将之与树对应起来。事实上,它只是一种分层的组织数据的结构,具体到实际实现上,就是一些按照逻辑分层的有序文件。

MemTable: LSM Tree的树节点可以分为两种,保存在内存中的称之为MemTable, 保存在磁盘上的称之为SSTable. 严格讲,MemTable与SSTable还有很多细节区别,这里不展开讨论。

基本原理

  • 写操作直接作用于MemTable, 因此写入性能接近写内存。
  • 每层SSTable文件到达一定条件后,进行合并操作,然后放置到更高层。合并操作在实现上一般是策略驱动、可插件化的。比如Cassandra的合并策略可以选择SizeTieredCompactionStrategyLeveledCompactionStrategy.

  • Level 0可以认为是MemTable的文件映射内存, 因此每个Level 0的SSTable之间的key range可能会有重叠。其他Level的SSTable key range不存在重叠。
  • Level 0的写入是简单的创建-->顺序写流程,因此理论上,写磁盘的速度可以接近磁盘的理论速度。

  • SSTable合并类似于简单的归并排序:根据key值确定要merge的文件,然后进行合并。因此,合并一个文件到更高层,可能会需要写多个文件。存在一定程度的写放大。是非常昂贵的I/O操作行为。Cassandra除了提供策略进行合并文件的选择,还提供了合并时I/O的限制,以期减少合并操作对上层业务的影响。

  • 读操作优先判断key是否在MemTable, 如果不在的话,则把覆盖该key range的所有SSTable都查找一遍。简单,但是低效。因此,在工程实现上,一般会为SSTable加入索引。可以是一个key-offset索引(类似于kafka的index文件),也可以是布隆过滤器(Bloom Filter)。布隆过滤器有一个特性:如果bloom说一个key不存在,就一定不存在,而当bloom说一个key存在于这个文件,可能是不存在的。实现层面上,布隆过滤器就是key--比特位的映射。理想情况下,当然是一个key对应一个比特实现全映射,但是太消耗内存。因此,一般通过控制假阳性概率来节约内存,代价是牺牲了一定的读性能。对于我们的应用场景,我们将该概率从0.99降低到0.8,布隆过滤器的内存消耗从2GB+下降到了300MB,数据读取速度有所降低,但在感知层面可忽略。

Q&A

  • 基于LSM Tree存储引擎的数据适用于哪些场景?

    (key or key-range), 且key/key-range整体大致有序。

  • LSM Tree自从Google BigTable问世后,如此牛x, 为什么没有替代B Tree呀?

    LSM Tree本质上也是一种二分查找的思想,只是这种二分局限在key的大致有序这个假设上,并充分利用了磁盘顺序写的性能,但是普适性一般。B Tree对于写多读少的场景,大部分代价开销在Tree的维护上,但是具有更强的普适性。

  • 看起来,你们已经将Cassandra玩得很溜了,你们线上用了多大集群支持当前业务?

    其实……还可以吧,主要是队友给力。还有就是国外有独角兽奈飞领头,遇到问题其实还是容易解决的。我们目前线上用了3*(4 core, 16G), 系统冗余还很大。最近奈飞出了一篇关于Cassandra优化的深度博文,如果有对Cassandra有兴趣,可以阅读Scaling Time Series Data Storage.

扩展阅读

Yet Another GeoIP Alfred Workflow

Yet Another GeoIP Alfred Workflow

在浏览网站以及在选线路的时候,经常会习惯性的查看一下对方的IP及地理位置信息. 师兄的 ip.cn 以及 ipip.net 都是不错的选择。但无奈自己是小帽子 Alfred 控,本着少做体力活的原则,写了一个 Workflow.

ip me 查看本地的外网ip及位置信息

ip domain 查看域名ip及位置信息

ip url 查看url中域名ip及位置信息

如果觉得你也有这样需求,可以在这里下载,enjoy!

References

  • http://www.deanishe.net/alfred-workflow