APP上传文件到云端的正确姿势:据说值三万美金

APP上传文件到云端的正确姿势

近几年云存储和CDN的普及给多媒体文件存储和分发带来了诸多便利。如今要上线一个基础功能视频、图片网站,使用CDN厂商提供的服务,转码、存储、分发,甚至简单的访问控制都一站式搞定。想想以前个人站长时代,需要自己加硬盘、买带宽,自己使用ImageMagick和ffmpeg转码?‍♀️,真是酸爽。

云储存带来便利的同时,有很多安全问题往往容易被忽视。前段无人机独角兽大疆创新DJI爆出SSL 密钥和AWS key泄露问题,这使得黑客可以直接访问用户的私有视频内容。更有意思的是,泄露的原因居然是因为大疆把key放在了github上的开源项目的固件firmware中,而且已经这么放了4年,四年……?。这件事有意思的是,发现该密钥的哥们是一名叫做KF的白客,并把这个问题报告给了大疆,准备领取$30000的奖励,还预定了Tesla Model 3, 结果大疆法务把这花小钱就能解决的大事给谈炸了,KF的特斯拉没有了,但是大疆损失的可就不止这点小钱了。喜欢看故事的同学可以移步大疆 VS “白帽子”,到底谁威胁了谁?

如今移动互联网的天下,几乎每个APP都会上传文件到云端。这里,我们谈一下上传文件到云端的错误姿势背后的原因,以及正确姿势是什么样的。

错误的姿势

将云端的key保存在APP,然后APP直接调用接口上传文件。

很惊讶?我们的独角兽公司大疆同学就是这么干的呀。其实,不需要嘲讽大疆,你可以问一下你身边的互联网公司工作的同学,结果会让你更惊讶。

就我了解的情况,走上这条不归路有这几类原因:

  1. 创业公司野蛮生长的技术债。
  2. 部门墙的原因,有些公司的云端账号可能掌握在客户端开发小组的手里,而客户端同学对于服务器端的安全问题相对欠缺知识背景和敏感度。
  3. 程序员偷懒,不走云端标准交互流程,并且有严重的侥幸心理。

正确的姿势

正确的姿势其实也是一句话:

密钥保存在服务器,客户端每次向服务器申请一个一次性的临时token或signature,然后上传文件。

据我们的使用情况看,国内的CDN厂商都支持这种授权三方上传方式。比如又拍云的认证授权,阿里云OSS的授权给第三方上传

当然,实际的系统不可能这么简单。下面以我们前端时间设计和实施的上传流程为例,介绍我们在设计文件上传时的考量因素:

名词解释:cds, content delivery service, 这是上述流程中唯一自己开发的服务,感谢CDN的普及?

  1. 根据我们以往的客户端文件上传监控,客户端上传文件第一次尝试的失败率约为4%(这其实是一个比较可怕的数字,同时你也可以感受一下4G网络的复杂性)。因此,客户端申请上传签名时,可以指定多种类型。在我们的系统中,我们实际使用了两个CDN厂商承载上传请求:阿里OSS和云拍云。默认通过阿里OSS上传,又拍云作为灾备上传。
  2. 为了能达到高于CDN厂商之上的可用性,我们使用了两家CDN厂商(又拍云和七牛)进行内容分发。访问每一个文件的完整url地址是可配置的,因此我们可以在任一CDN出现问题的时候,在后台切换CDN。
  3. 可能有同学发现了我们使用了不太主流的又拍云。没有什么特殊原因,只是因为给的价格折扣比其他两家要低。
  4. 所有的云端数据都保存在阿里OSS,主要是基于阿里OSS可用性是跨可用区的。另一方面,这也实现了云端储存与内容分发的剥离。这意味着我们可以无痛的更换又拍云或者七牛云。
  5. 客户端上传文件成功以后,cds会收到一个回调通知,这里我们做了文件内容的合规检查和内容审计。当然,你可以在这里做一些数据挖掘的事情。

小结

他山之石,可以攻玉。据说,大疆犯错的程序员已经被劝(开)退(除)了。如果你所在的公司还存在类似的问题,赶紧行动起来吧。

微服务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.

扩展阅读