macOS无法打开app应用程序的解决办法

macOS Sierra以后(EI Capitan, High Sierra),默认只允许打开来自Mac App Store或苹果的有效开发者发布的应用。你可以在「System Preferences -> Security & Privacy」查看当前状态:

如果是从网络下载安装的app,在打开时可能会遇到这两个问题:

  • YOUR_APP is from an unidentified developer, Are you sure you want to open is?
  • YOUR_APP is damaged and can’t be opened. You should move it to the Trash.

可以尝试在终端中执行sudo spctl --master-disable(需要输入密码)以允许打开任意来源的app:

如果执行成功,「Anywhere」选项为选中状态。「Security & Privacy」状态如下:

如果需要关闭「Anywhere」选项,则在终端执行sudo spctl --master-disable.

注:打开该「Anywhere」选项有安全风险,请确保你在打开该选项是了解app的来源,只信任自己清楚来源的app.

低延迟与用户体验杂谈

最近在做系统设计梳理的时候,明显感觉到「低延迟」已经成为被提及越来越频繁、考量权重越来越大的因素。 并且,越是靠用户近的系统,对延迟越敏感,对用户体验影响越大,对低延迟要求越高。

HTTP/2如今(2018.02)已经逐渐普及,其设计的第一目标就是降低延迟。主要采用了两个手段来解决:

  1. TCP连接复用。连接复用减少了TCP每次握手带来的延迟,同时避免了每次新建TCP连接的窗口慢启动带来的数据吞吐开启延迟。
  2. 使用数据分帧解决队头阻塞问题。当然,这个问题HTTP/2解决得不彻底,具体可以参见《当我们在谈论HTTP队头阻塞时,我们在谈论什么?》了解细节。

HTTP/2毕竟只能解决应用层的低延迟问题。如果要继续降级延迟,就需要下潜到传输层。因此,Google的QUIC和TLS 1.3应运而生。QUIC目前主要是Google主导,除去其自家的服务,如Google搜索首页,G+等,支持的网站还非常少。TLS 1.3则由标准化组织加持,目前在最新的OpenSSL、nginx已经支持。TLS 1.3能够做到新连接3RTT,恢复连接2RTT(TLS 1.2分别是4RTT, 3RTT;而2RTT已经与http的RTT持平!),的确非常吸引人。

回顾WEB技术过去十几年的发展,很多的特性引入和改进都是基于降低延迟。有些是技术层面的,比如上面提到的HTTP/2的低延迟设计、DNS查询缓存、HTTP1.1并发多个TCP连接请求资源、雪碧图等;有些是用户体验层面的,比如异步加载/预加载js资源、优先加载影响首屏渲染的CSS资源、避免使用大表布局、图片渐变加载、过渡动画等。

而这些年技术层面的发展其实都是受物理定律限制的。祭出程序员延迟心经Latency Numbers Every Programmer Should Know:

软件工程师无论是做什么职位和方向,心里都应该对此有B树。

有了这些当前人类所认识的物理极限,才能做到在「在边界内做事情」(这句话不是我说的,第一次看到这句话是吴军老师的《硅谷来信》)。

举个例子,当前人类认知范围内最快的速度是光速,这是一个上限,而广泛使用的光纤通信速度大概是光速的2/3。那么,要想优化上海到加州的网络延迟,无论你如何优化线缆布设以及质量,RTT都不可能低于127ms. 因此,如你所见,这些年虽然新架设了不少新的跨太平洋光缆,但是最优网络延迟没有什么太大变化,反倒是这些光缆带来的扩容让网络拥塞得到了缓解,让我们感觉出口网络「好像」更快了。

那么在光速这个物理边界的限制下,我们要如何降低延迟呢?显然,固定的两点之间的网络延迟是无法突破该边界的(这里不讨论空间扭曲力场🤦‍♀️)。但是,很多时候,我们要解决的问题是「让用户感觉延迟低」就可以了。

在这个思想下,CDN应运而生。将内容分发到距离用户近的网络节点来降低用户访问延迟。这个idea非常简单,甚至简陋,但是非常有效,并且廉价。

顺着这个思路下去,如果把算力分发到距离用户近的节点,那是不是也可以让用户觉得计算任务也变快了呢?在一定程度上,这是可以做到的。比如,我们使用了数十年运行于浏览器的javascript,以及我们的多IDC、多主架构方案,都有这方面的考量。近来流行的serverless、边缘计算其实也可以是认为是将算力部署到离用户尽可能近的物理位置或业务流程中。

有时候,技术参数指标的提升在短期内是难以低成本解决的。这个时候,尽量避免死磕参数,投入100%的精力去换取1%的性能提升。可以尝试从设计交互上给用户形成反应很快的体验,降低体感延迟。比如,1)耗时的任务后台化并给出进度条;2)区块处理的逻辑任务尝试修改成流处理,加速部分处理结果的输出,典型应用如以视频直播服务为代表的流媒体服务全力优化首屏播放延迟;批处理任务每处理完一个子任务就反馈结果等。

不仅软件系统的延迟影响着用户体验,硬件更是如此。现在人们普遍认为iPhone的用户体验是优于安卓的。因素有很多,那些复杂的系统参数普通用户未必搞得懂,但是很多用户却承认在滑动屏幕的时候iPhone比安卓更加「跟手指」,打开应用也「感觉更快」。「跟手指」这个体验跟苹果的软件+硬件的技术优化有关,目前安卓阵营也没有赶上。打开app快则是典型的交互优化:苹果打开app未必真的比安卓快,但是从点击app图标到显示story board的确非常快。无论苹果是否鸡贼,至少从这个层面看,它的确是非常了解延迟与用户体验之间的奥义的。至于苹果每次升级系统「故意」让老设备变卡……咳,咳,同学,你怎么有又抬杠呢😅

喜欢汽车并且喜欢驾驶的朋友一定知道甚至深入研究过这几款车:马自达3/6/BRZ/86-宝马M2/M4-保时捷718/911。排名分先后,每一辆都是不同级别的驾驶者之车,也是很多玩车朋友的玩车升级路线。这些车都是「运动取向」,这是一个非常抽象的概念,普通消费者未必明白,但是你会发现他们的消费者大多会用「操控好」「响应快」来评价这些车。如果你是参数党,你会发现其实这些车在同级别中都不是最好的,但却是给人愉悦感最强的。

由物及人,当上司交给你一个任务的时候,有反馈好过无反馈,快反馈好过慢反馈。

总结

在节奏日益变快的今天,低延迟很多时候意味着良好的用户体验。而做到低延迟可以通过技术优化,也可以通过一些产品的交互方式和有选择的强调某些方面来让用户感觉很快。内容比较分散,但是都是顺着这根主线,希望你也有所思考和收获。

参考文献

Introducing Zero Round Trip Time Resumption (0-RTT)

从答题赚钱APP谈谈接口防抓包

从答题赚钱APP谈谈接口防抓包

这段时间,答题赚钱APP真的是红遍了神州大地:国民老公王思聪的冲顶大会、芝士超人、西瓜视频,甚至连老司机韩路也玩起了这个游戏😅。有利益就有动力,毕竟每场奖金都在十万、百万,虽然官方可以在最终答对人数上面掺水稀释每人的奖金,但是的确可能拿到奖金。人类,对这种可预期、强反馈的东西跟吸毒一样……于是,市面上出现了很多所谓的自动AI答题工具。大概可以分为两个流派:

  1. 通过OCR识别题目,然后用搜索引擎搜索答案告诉用户选什么。答案选择策略各异。做得比较简单的就是选搜索结果最多的那个为正确答案。
  2. 直接抓包这些APP下发的题目,然后用搜索引擎搜索答案。

可以看出,主要区别就是如何获取题目的文本。顺便吐槽一句,其实这些工具跟AI有半毛钱关系呀。对于第一种方式,是比较难防的。第二种方式解决办法主要有两种:

  1. 设计之初,使用图片下发题目,而不是直接下发题目文本;
  2. 如果已经走上了文本下发题目这条不归路,那么至少要做到接口防抓包。

这里我们谈一下接口如何防抓包。

为何可以成功抓包?

  1. 接口走HTTP,那么直接是明文传输,也不存在传输层的认证和加密问题,所见即所得。随着大家安全意识的增强,直接这么裸奔的产品越来越少。但是会有人因为各种原因就是不迁移到HTTPS,然后自己实现一个民科版的加密。我们极其不推荐这种做法,主要有以下几个原因:
    1. 大部分工程师都没有密码学背景,设计的算法基本很难完备的考虑数据机密性、完整性和不可抵赖性。
    2. 将应用层与数据传输层进行耦合不仅限制了通用性,如无法进行透明的流量转发,而且会带来非常高的系统维护和迁移成本。
  2. 接口走HTTPS。嗯,数据加密了不是?但是,通过中间人攻击(MitM)也是可以成功抓包的。这背后的基本原理是:在手机上安装一个中间人CA证书(如Charles证书) --> APP信任手机上的证书 --> 中间人可以成功解密流量进行抓包

如何防止抓包?

对于HTTPS API接口,如何防止抓包呢?既然问题出在证书信任问题上,那么解决方法就是在我们的APP中预置证书。在TLS/SSL握手时,用预置在本地的证书中的公钥校验服务器的数字签名,只有签名通过才能成功握手。由于数字签名是使用私钥生成的,而私钥只掌握在我们手上,中间人无法伪造一个有效的签名,因此攻击失败,无法抓包。

同时,为了防止预置证书被替换,在证书存储上,可以将证书进行加密后进行「嵌入存储」,如嵌入在图片中或一段语音中。这涉及到信息隐写的领域,这个话题我们有空了详细说。

预置证书/公钥更新问题

这样做虽然解决了抓包问题,但是也带来了另外一个问题:我们购买的证书都是有有效期的,到期前需要对证书进行更新。主要有两种方式:

  1. 提供预置证书更新接口。在当前证书快过期时,APP请求获取新的预置证书,这过渡时期,两个证书同时有效,直到安全完成证书切换。这种方式有一定的维护成本,且不易测试。
  2. 在APP中只预埋公钥,这样只要私钥不变,即使证书更新也不用更新该公钥。但是,这样不太符合周期性更新私钥的安全审计需求。一个折中的方法是,一次性预置多个公钥,只要任意一个公钥验证通过即可。考虑到我们的证书一般购买周期是3~5年,那么3个公钥,可以使用9~15年,同时,我们在此期间还可以发布新版本废弃老公钥,添加新公钥,这样可以使公钥一直更新下去。

谈谈“五级工程师和职业发展”的思考

再过几个小时,春节长假就结束了。我想,不管过去的一年过的怎样,大部分工程师们对开年的的工作和生活都应该是有所憧憬和计划的,我也不例外。只不过我选择了把前年在吴军老师《硅谷来信》上看到的《五级工程师和职业发展》重温了一下(版权原因,我这里无法提供原文的链接,但这一定阻挡不了聪明、求知若渴的你)。如果你看过原文,我依然觉得你不妨看看这里的思考,因为我所思考的可能更贴近实际的你。

什么是“五级工程师”?

著名前苏联物理学家朗道曾经给出过一个五级物理学家的划分,吴军老师在此基础上,给出了“五级工程师”的划分:

  • 第五级:能独立解决问题,完成工程工作;
  • 第四级:能指导和带领其他人一同完成更有影响力的工作;
  • 第三级:能独立设计和实现产品,并且在市场上获得成功;
  • 第二级:能设计和实现别人不能做出的产品,也就是说他的作用很难取代;
  • 第一级:开创一个产业。

你处在哪一级?

五级工程师的划分非常简洁,但是却有点抽象。我举几个大家熟悉的人物,方便大家进行定位:

  • 云风, 第三级;
  • 阮一峰,第三级;
  • 范凯,第四级;
  • 王垠,第五级;
  • 池建强,在用友度过了第五级和第四级,到锤子科技,以及后来加入极客帮逐步升级到第三级;
  • 冯大辉,在支付宝时为第四级(阿里P8),后来加入丁香园,现在创业无码科技输出产品后到了第三级。如果无码在医疗搜索上能独树一帜,我觉得应该能到2.5级。
  • Jeff Dean, Linus, 丹尼斯.里奇, 肯·汤普逊,第二级;
  • 爱迪生、福特、贝尔、香农、理查德.斯托曼,第一级。
  • 爱因斯坦, 第0级;

需要说明的是,以上只是我自己的一个主观划分,上面的大牛在每个人心中的位置因为了解程度和和主观倾向不同,划分的结果可能有区别。这里只是参照,方便大家自我定位。

尤其要指出的是,我这里并不是要黑王垠。细说起来我还请垠兄吃过饭,应该对他还算了解,只是对于大众来说,他至今(2018.02)还没有向人们展示一个有足够影响力的产品(无论是面向开发者的技术产品还是面向普通用户的应用产品)。近期他把自己的博客删除了,关于他的消息逐渐少了,也许当他回归的那天,他会直接从第五季跃迁到第三级。 王垠博客在这篇博文发表没几天就恢复访问了,他最近发表的博文《真相》道出了他准备认真写书的计划,采取自愿付费的方式。有兴趣的同学可以支持一下他,希望他能带了一点点difference.

严格的说,第五级并不是一个轻而易举就能达到的级别。但是,只要是一个善于思考,手脚轻快的新晋工程师在工作一两年以后,达到是没有问题的。如果你每天做的事情都是一些简单重复性的事情,那么我想其实是不能算为第五级工程师的。

如何打怪升级?

对于工程师来说,确定性的输入和输出是最容易解决的问题。那么,我们知道了这个划分以后,如何来指导自己的职业发展进行打怪升级呢?我觉得需要想清楚以下几件事。

级别之间人与人的能力、能力圈的差距是数量级的

以前跟同事一起饭后散步的时候,我经常说人和人之间的差距是无法想象的,如果一个人内心做不到真正的谦逊,那一定是他没有见过真正优秀的人。对于工程师级别的划分也是一样的,优秀工程师与普通工程师的差距是数量级的,而不是线性的,更不是想象的只差一点点。回到自己身上,抬头看一下上面👆列出的代表性工程师,客观评估一下与他们的差距,是否有种路漫漫而修远兮的感觉?但是,我们并不用失望,要知道,对于大航海时代的船长来说,正式知道了自己的位置才支撑着他们战胜了风浪和败血症,最终发现了新大陆。

这个数量级我觉得不用定太大,《The Rise of Developeronomics》提到的“10倍效率工程师”应该是比较合适的。即每个级别相差10倍。

想清楚了这一点,能大大缓解程序员之间天然的鄙视链,内心也变得不那么天生骄傲。你身边一定会有你觉得是“菜鸟”的同事。对于这类同事,说你的工作效率和工作输出是他们的3~5倍,可能你不会反对,但是扪心自问你的工作能力是他们的10倍吗?如果这里你需要掂量一下,那么就默默放下你心中对他们的相对优越感吧,毕竟你们之间还是在同一个级别,你们之间的差距很可能只是时间经验的积累罢了。

此外,级别之间的差距不仅要求的专业能力要有数量级的提升,对于能力圈的大小也需要有数量级的提升。你身边一定有薪酬和工作效率跟刚毕业不久的年轻人差不多的老同事。这类同事工作勤恳,保质保量,人也很nice, 但是每当想考虑晋升机会的时候,总是拿不出像样的亮点帮他一把。这部分人很多是工作能力提升以后,能力圈没有跟上导致的。这里并没有对老同事不敬的意思,我也承认有些老同事是因为性格或自主选择的因素在一个职位上做很长一段时间。

级别之间的数量级差距是挑战也是巨大的机遇。因为这也意味着每提高一级,会筛选掉大约等数量级的人,这也就意味着你具备了竞争更好的职位和薪酬的机会。近几年,很多人都感慨说「IT行业的红利要逐步过去了,我们会不会成为下一代失业的民工?」,也有很多开发者不无担心的说「AI都会写代码了,会不会让程序员失业?」其实,这种担心至少目前来说是多余的。要知道,在人类发展历史上,无论是第一次工业革命还是第二次工业革命,淘汰人的不是机器而是更优秀的人。至于AI,现在只能说是「智能的人工」,依然任重道远。

第五级工程师容易犯的错误

上面说了五级工程师其实不是一个容易达到的级别。一般来说,主程、核心开发人员可以认为是这个级别。对应到医疗体系的话,至少是主治医师才能算第五级。吴军老师在文中举了一个第五级工程师的例子,方便大家自我评估:

这个人在京东公司任职,老板让他做一个工具,找出那些不断帮助女(男)朋友买书的读者。他自己知道在公司内找谁去要数据,如何确认两个人可能是男女朋友,而且经常买书。也知道自己在京东公司的环境里,应该使用什么样的开发工具。以及为了方便客户使用,这个工具应该有什么样的基本功能。

如果你达到了第五级,恭喜你已经登堂入室,可以欢快的向第四级工程师进发啦。但是就我身边的观察,很多达到这一级别向后发展的路上,往往容易走一些弯路:

  1. 逐渐进入舒适区,从脑力劳动者变成体力劳动者。「擅长主动学习和喜欢迎接挑战」说起来容易,其实在长久的工作中是一个特别难坚持的事情。练级之路没有捷径,学习和成长本来就是痛苦的,习惯这种痛苦吧。
  2. 喜欢研究武器,但是却不上场杀敌。我身边C++背景的人尤其容易犯这个错误(此处感觉要被C++开发者喷):喜欢研究因为语言历史和设计问题导致的一些晦涩且trick的用法,自觉高深,但是工作输出上却缺乏善可陈。在公司的整个商业行为中,技术是其中的一个环节,如果你不是那么确定自己要在这个技术方向上发展十年,那么你花一定时间研究这些技术是可以的,但是一定不能忘了公司是有成本和效率要求的。
  3. 容易陷入「技术做了N年,要不要转管理」的怪圈不能自拔。上面提到了,每升一级,需要能力和能力圈的同步提升。因此「转管理」对于不准备丢掉技术的工程师来说本身就是一个伪命题。你的影响力在第五级达到一定程度的时候,即使你没有职位的授权,你推动很多事情都是流畅和自然的。工程师喜欢扁平文化,而且个个骨子里闷骚、桀骜不驯,如果不能服众的话,转不转又有什么意义呢?喜欢看热闹的同学可以围观一下v2ex上的隔壁组的小兵集体情愿 要炒了 team leader.
  4. 喜欢用跳巢来涨薪酬。准确讲,这种行为并没有对错之分,只是一种个人选择。但是,我希望你是因为自己能力全方位提升以后,公司暂时没有适合你的职位和项目供你晋升,而不是仅仅作为一种手段。

第四级工程师容易面临的瓶颈

如果你成功完成了第五级的修炼来到了第四级,那么我觉得你至少从薪酬上已经超越了80%的IT从业者。这个级别的典型的工程师有:巨头里的小组负责人、高级工程师,中小互联网公司里的技术总监、CTO、架构师、DBA等。对应到医疗体系的话,至少需要是医院的科室主任。

这个级别是我们日常工作中经常打交道,同时心里默默认为是聪明人的最多的群体。这个群体聪明、勤奋、好学,是其负责的具体工作的佼佼者。很多人,发展到了这个阶段也就是其职业生涯的最终阶段了。除去自主选择停留在这个等级的那部分人,更多的人是卡在了对整个商业行为的理解,或者说是认知升级没有跟上。

很多工程师,脱离了公司或者组织,是不知道如何赚钱的。如果希望继续升级,一定要建立对整个商业行为完整的认知。具体到操作层面,可以在公司内部孵化项目或者产品,然后推向市场。整个过程中,你自然会学会如何争取资源、利用资源,如何在修海湾大桥还是修浮桥之间决断,如何做获取种子用户、如何营销、如何做市场放大推广,如何调整模式验证模型,最终实现变现。最终变现的规模不需要达到一个亿的小目标,公司项目或产品能到100w流水就够了(如果你在巨头工作,你当然可以花半天时间就刷到100w,但是聪明的你一定知道我不是这个意思……)。如果公司暂时不能提供资源进行项目孵化,那么可以尝试独立开发者做side project(但是不应该占用工作时间). 个人项目变现规模我觉得10w就够了。这个数字并不难做到,如果你感兴趣的话,可以随便看看INDIE HACKERS 上面开发者的经验之谈。当然,这个过程不会非常顺利,大部分时候,你可能最终是学会了「如何cancel项目」,但是,谁TM care呢?少年,勇敢站起来继续lu…

如何成为第三级工程师

很遗憾,鄙人也还在仰望这个级别,更多是思考这个层级人的特质,见贤思齐。如果你觉得自己做到了这个级别,欢迎发表您的高见。

这个层级的典型人物有巨头的部门leader, 产品leader(微信张小龙应该算第2.5级),中小公司的CEO。也就是我们大部分时候泛指的换联网那部分「财务自由」的人。对应到医疗系统,应该是院长这个级别。一路上,接触了不少这类人,他们身上也的确有一些特质非常吸引人:

  1. 敏感,且往往是个不错的产品经理。
  2. 喜欢并擅长深入思考,具有良好的思维体力,并且将思考作为日常的马拉松。
  3. 迫切,近乎疯狂的要结果。
  4. 谈情怀,也谈使命感,但是执行上非常务实。

复利思维+正态分布

不仅是作为一名工程师,其他各方面的发展和成长都免不了挫折。但是一定记住复利的魔力:如果我们选定了做一名优秀的工程师,在做计划和选择的时候争取做到自己的每一步都是在自己之前积累的前一步上的。否则,在原地周围折腾是难以发生质变的。时代发展太快,可能很多人心中都缺乏安全感,但是试想你手里如果握着一个几年以后可以增长数十倍的筹码,心里也就不再慌张了。

另一方面,中国的国情之一是人口基数大。天朝的工程师群体虽然占人口比例不算大,但是基数已经非常大了(千万级)。改变不了规则就尝试去争取自己的位置。只要你超过了平均水平之后,越往前,惯性越大,竞争的人越少,看到的机会越多。所以,不要抱怨房价一直上涨了,房子毕竟是要卖给能够支付得起售价的人,如果我们当前买不起房,很可能只是我们当前的位置拖了社会的后腿😂

成为稀缺

任何时候,稀缺的人才都不愁机遇。最近也在思考一个工程师的核心竞争力和不可替代性是什么。看到了刘未鹏的一篇分享,挺有感触,摘录如下:

个人的核心竞争力是他独特的个性知识经验组合。这种组合1)绝无仅有;2)在实践中有价值;3)具有可持续发展性。

具体到工程师来说:

  1. 专业领域技能;
  2. 跨领域的技能;
  3. 学习能力;
  4. 性格要素。

小结

我所说的,必然不可能都对,取走你所需要的。新的一年,祝大家开工大吉,升级顺利!

Golang多级内存池设计与实现

Golang多级内存池设计与实现

上个月,牙膏厂intel因为MeltdownSpectre两个bug需要给CPU固件和系统打了补丁。我们生产环境使用的是阿里云,打完补丁后,几台IO密集型的机器性能下降明显,从流量和cpu load估计,性能影响在50%左右,不是说好的最多下降30%麽😭

在跑的业务是go写的,使用go pprof对程序profiling了一下,无意中发现,目前的系统gc和malloc偏高。其中ioutil.ReadAll占用了可观的CPU时间。

ioutil.ReadAll为什么慢?

这个函数的签名原型是func ReadAll(r io.Reader) ([]byte, error). 团队的小伙伴非常喜欢用这个函数,其中一个原因是这个函数可以将r中的数据一次性读完返回,不需要关心内存如何分配、如果分配的内存不够了,如何进行内存扩张等。作为一个util函数,这样设计是完全没问题的。但是,IO密集场景下,这个函数的开销就是你需要关心的了。这个函数实际调用realAll读取数据:

其中,capacity是常量值512. realAll函数在调用buf.ReadFrom进行数据读取:

看到这里,原因就非常清楚了:如果要读取的数据大小超过了初始buf大小(默认初始大小为512 bytes), 则会重新分配内存,并拷贝内容到新的buffer中。如果要读取的数据非常大,则会重复多次上述操作。那么优化的问题就转化为如何降低内存重分配和拷贝。

多级内存池的设计和实现

  1. 内存池被按照大小被分为多级。如上图所示,(0, 1024]使用level 0, (1024, 2048]使用level 1. 内存池分级有两个好处:
    1. 可以灵活的规划不同级别内存池的总大小和item数量,适应不同业务。
    2. 实现层面上,可以将一把内存池大锁拆分成多个小锁,减少锁争抢。
  2. 当已分配的内存池耗尽需要扩张时,一次性申请一大块内存,提高扩张效率。如level 0所示。
  3. 代码实现gmmpool,bench结果显示性能提高约19倍:

小结

对于频繁进行内存分配和释放的场景,使用内存池可以显著降低golang运行时的开销。同时也要注意,内存池的内存交给了用户管理,你需要小心检查是否存在内存泄露问题。如果你对性能要求没有这么苛刻,只是想复用一些小对象,那么我们推荐你使用标准库的sync.Pool.

另外,开头提到的阿里云性能问题,即使使用了内存池优化,结果还是非常悲剧。最后阿里云帮我们更换了没有打牙膏厂补丁的机器解决。是不是非常惊喜?😅

当我们在谈论HTTP队头阻塞时,我们在谈论什么?

近来访问网站,明显感觉支持HTTP/2的网站越来越多了,对行业来说是个好趋势。HTTP/2的RFC虽然写的很厚,但是总结起来就做了以下几件事:

  1. 通过TCP多路复用降低延迟;
  2. 单个TCP连接上允许乱序request-response,解决队头堵塞问题;
  3. 实现层面上,大部分浏览器要求HTTP/2必须开启TLS,一定程度上解决数据安全问题。

其中,队头阻塞问题真的被解决了吗?

HTTP/1.1为什么会队头阻塞?

HTTP/1.1通过pipelining管道技术实现一次性发送多个请求,以期提高吞吐和性能,如上图中的序列2。然而,这种技术在接收响应时,要求必须按照发送请求的顺序返回。如果,第一个请求被堵塞了,则后面的请求即使处理完毕了,也需要等待,如上图中的序列3。

那么,HTTP/2是怎么解决这个问题的呢?那就是数据分帧:多个请求复用一个TCP连接,然后每个request-response都被拆分为若干个frame发送,这样即使一个请求被阻塞了,也不会影响其他请求,如上图序列4所示。问题完美解决了?准确说,只解决了一部分。

如果队头阻塞的粒度是http request这个级别,那么HTTP/2 over TCP的确解决了HTTP/1.1中的问题。但是,HTTP/2目前实现层面上都是基于TCP(没错,HTTP从来没有说过必须通过TCP实现,你可以用它其他传输协议实现哟),因此HTTP/2并没有解决数据传输层的对头(包)阻塞问题。

如上图所示,当第一个数据包发生丢包的时候,TCP协议会发生阻塞会进行数据重传。虽然TCP有快速重传等机制来缓解这个问题,但是只能是缓解。无法完全避免。

如何解决传输层的队头阻塞问题?

应用层无法解决传输层的问题。因此要完全解决队头阻塞问题,需要重新设计和实现传输层。目前而言,真正落地在应用的只看到Google的QUIC. 它的原理简单讲,就是使用UDP实现了一个可靠的多路复用传输层。我们知道UDP是面向数据报文的,数据包之间没有阻塞约束,QUIC就是充分利用这个特性解决传输层的队头阻塞问题的。当然,QUIC的协议实现有非常多的细节,而这方面Google确实做得非常好,如果你想进一步了解,可以关注他们的开源实现

需要说明的是,当前的QUIC实现使用HPACK压缩http header, 受限于当前HPACK算法实现,在QUIC中的header帧也是受队头阻塞的。但是粒度已经降低到了帧这个级别,并且仅会在header帧中出现。实际使用中,出现的概率已经非常低了。

小结

  1. HTTP/2 over TCP(我们接触最多的HTTP/2)解决了http request级别的队头阻塞问题
  2. HTTP/2 over QUIC解决了传输层的队头阻塞问题(除去header frame),是我们理解的真正解决了该问题。

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的关注🙂!