有一段时间住在花园路,最难忘的就是路边的煎饼果子。老板每天晚上出来,正好是我加班回去的时间。
一勺面糊洒在锅上,刮子转一圈,再打一个蛋,依然刮平。然后啪的一下反过来,涂上辣酱,撒上葱花。空出手来,剥一根火腿肠。最后放上薄脆,咔咔咔三铲子断成三边直的长方形,折起来正好握在手中。烫烫的,一口咬下去,蛋香、酱辣、肠鲜,加上薄脆的声音和葱花的惊喜,所有的疲劳都一扫而光。
这种幸福感让我如此迷恋,以至于会在深宅的周末,穿戴整齐跑出去,就为了吃上一个。也因为理工科的恶习,我也情不自禁地开始思考这份执迷的原因,直到最后,我发现了它的秘密。
作为街头小吃的杰出代表,能够经历众口的挑剔而长盛不衰的秘密,所有的一切全因为其模式。而这模式与大多数互联网服务的架构如出一辙,那就是分层架构。
分层的设计意味着,每一层都独立承担单一的职责,这在根本上降低了制作的难度。做饼的时候专心控制火候,做酱的时候专注在味道。每层职责的单一化也让优化变得简单,因为它是自然可伸缩的。你要是想多吃点蛋就多加一个,你要多吃点肠就多加一根,完全取决于你的胃口。
它又是可以扩展的。你可以不要蛋,你可以加根肠,你可以不要薄脆,你可以加上辣酱。而且每一层又是可定制的。葱花可以少一点,辣酱可以多一点,肠可以要两根,鸡蛋可以加三个。
你也可以把面饼换成面包,把鸡蛋换成煎蛋,把辣酱换成甜酱。你已经知道这是什么了吧?是的,你好,这里是赛百味,请问你要什么口味的三明治?
煎饼果子和三明治,其实本质上是相通的。而加一个蛋更香,也不是因为对蛋的追求,在根本上是因为煎饼果子模式的强大。
因为这种模式,一千个人可以有一千种煎饼果子。
而有了对模式的理解,对小吃的评估也就变得更加容易。比如肉夹馍只有馍和肉,二维切换单调不可长久;比如烤冷面,干脆就是满嘴的热烈混在一起,没有煎饼这样的表现层,整个面都散发着原始的不讲究。
这种对比上的简化,也让我们有了新的选择,可以随时放弃对细节的追究,保存宝贵的精力。
就像在我们谈论女人时(抱歉博主是男人),我们在意胸、在意腿、在意风情、在意温柔,因为女人是有区别的。当我们欣赏电影的时候,我们在意男人、在意女人、在意老人、在意孩子,因为角色是有区别的。当我们走在路上的时候,我们在意行人、在意车辆、在意商店、在意餐厅,因为物体是有区别的。
我们在设计和优化系统的时候,其中的每个服务都是自行运转,做着自己份内的事,但是在不同的维度里,作用却变得不尽相同。这也是我们讲优化要分层次和级别,架构、算法、库和OS,而讲架构的时候,我们首先讲的是整体的模式,然后是具体的权衡,实现的细节则是最不重要的。
架构的模式
谈起这个,是因为Mark Richards写了一本架构模式的书《Software Architecture Patterns》。总结对比了五种模式的优缺点,包括了Layered、Event-Driven、Microkernel、Microservices、Space-Based。书写得简单精致,推荐大家去阅读。
http://www.oreilly.com/programming/free/software-architecture-patterns.csp
还有一种模式,因为在越来越多的系统中用到,是书中没有的(与Space-Based有所区别),但我觉得也有必要专门介绍下。我们开始在群发系统中实现,后来的抢购、红包和火车票的场景中也屡屡看到它的身影。
13年的时候,我们在微博做粉丝服务平台,一个类似微信公众号的群发系统。然而比后者更困难的是,当时在产品设计上并没有像微信一样新建用户体系,而是直接基于微博的粉丝关系,这就意味着一篇文章要能能在很短时间内支持亿级的用户推送。这个数量级的订阅用户,即使看今天的微信公众号依然是难以想象的。
当时有一套老的群发系统,都是基于MySQL的收件箱设计,在更换了SSD硬盘,又批量化数据库操作之后,整体写入性能依然只在每秒几万的级别,这就意味着一亿用户只能在17分钟内发完,我们意识到这套系统需要进行重新设计。
最终我们我们使用了一种新的架构方式,达到了每秒百万级别的速度,而且还可以更高。这种模式就是单元化架构。
下文介绍我参照了架构模式的说明方式,希望能够让大家有个对比,喜欢你可一定要说好!
单元架构
如前所述,我们选择单元化的一个重要目的是为了性能,为了极高的性能。这比起一般的分层架构来讲,会获得更经济的结果,但也因此,牺牲了分层架构的一些特性,因为它的容量取决于单元的大小(关于单元等名词介绍,我会在下文介绍)。
虽然它支持按照单元扩容,但在单元内基本上每层的性能都是固定的。这更适合容量可预期的场景,比如大多数已经趋于稳定的业务。像前面的粉丝服务平台,虽然他下发消息量级巨大,但是在整体层面,使用平台的用户由于是VIP用户,其规模基本在在数百万级别,而粉丝量级也不太可能过亿。
重要的是,基于当时的业务数据,我们已经知道平均粉丝数在什么量级。而业务数据是架构选型的重要依据。商品秒杀、火车抢票等等都是一样。
当然也有例外。因为单元化架构作为一种思想,它不会局限在一台机器,一个机架,它也适用一个机房。当它的层次变大时,单元内自然就可以有变化的空间。每一层服务都可以分开伸缩。而到了这个层面,它的追求可能就完全不一样了。像阿里的双十一服务改造,会为了流量的分离,像QQ的聊天,会为了接入的速度。它们的基本思想是一致的。
至于单元化架构和煎饼果子的关系,我会在文后回答。
核心概念 Key Concepts
分区(Shard)是整体数据集的一个子集。如果你用尾号来划分用户,那么相同尾号的用户可以认为是同一分区。
单元(Cell)是满足某个分区所有业务操作的自包含的安装。我们从并行计算领域里借鉴了这个思想,也就是计算机体系结构里的Celluar Architecture,在那里一个Cell是一个包含了线程部件、内存以及通讯组件的计算节点。
https://en.m.wikipedia.org/wiki/Cellular_architecture
单元化(Cellize)这是我的自造词,描述一个服务改造成单元架构的过程。
模式描述 Pattern Description
单元架构最重要的概念,就是单元和单元的自治。
你可以将其想象成细胞,如之前所述,每个细胞都是自成一体,功能明确。你也可以将其想象成小隔间,就像你去了一个按摩院,每个隔间里都有技师和所有设备。我没有用前面那个名字,因为其有太强的生物学含义,也没有用后者,因为其有太多的服务性暗示。但是如果你有足够的想象力,其实什么名字都可以的。
说到单元的自治,即单元的自我协调和之间的隔离。单元既然做到了自包含,那么其中的所有组件,不管是否在物理上分离成了独立的服务,都是在一个单元内互相支持的,也就是跟其他单元内的同类和非同类组件都不会有任何交流。这也是跟基于空间的架构的重要区别,后者的处理单元之间还是会互相通信并同步信息。
这里的挑战就在于分区的算法。一个单元内的组件会很多,如果业务复杂,涉及到的数据也会很多,为了隔离,每一个组件都要能按照同样的算法进行分区。
本质上每个单元都是相似的,单元之间的区别或者取决于请求,或者取决于数据。而且越到大的层面,区分度越低,用户甚至是可以在不同单元间漫游的。
模式动力学 Pattern Dynamics
单元架构的最典型目的,还是为了极高的性能,为了获得经济的高速度。这里一方面,是因为我们发现其他架构实际上是浪费了很多资源,每一层服务都运行在单独的操作系统上,而且都要通过局域网或者城域网中转。
与此同时,传统的互联网服务还是希望用一堆计算能力普通的节点来服务大量用户,而随着摩尔定律的推进,单机性能越来越高,网络通讯的成本随之变得耗费显著。这使得我们有机会也有动力在垂直方向进行扩展。
当你把更多的组件放在同一个地方的时候,你也在物理上获得了计算本地化的优势。这是我们获得性能提升的根本原因。
服务分成了很多单元,但总要跟外界通讯,这个事情是交给协调者Coordinator的。你可以在内部增加存储、缓存,增加队列和处理机,这些所有不交互的组件,理论上都不是外部资源可以访问的。
前面我们提到,单元化过程也是分区算法的应用过程。而这个分区算法放在哪里就是个问题。
我们可以封装运行库交给客户端,也可以做个代理层,内置算法。也有一些服务因为业务需要,请求需要复制到每个单元去。这就是典型的Scatter-Gatter模型,那么你还可能需要一个作业管理系统。这些都是可选择的使用方式。
模式分析 Pattern Analysis
总体敏捷度低,易部署性低,可测试性高,性能高,伸缩性高,易开发性低。
基于篇幅原因,不再详述每一个方面,相信大家都能自行分析。唯一需要强调的是运维要求比较高。
单元化之后,所有的服务放在一起,在请求失败的情况下需要快速定位某个单元,这跟分层排除的思路是不一样的。如果运维团队不够高效,面对这样集群数量的暴涨(每个单元的服务数量相当于原来一个集群的服务数量),有可能是会被大量的工作压垮;如果运维团队分离比较明显,每种组件都是专门的团队来维护(这是我们在微博遇到的),那就会有排异反应的风险,因为每一个团队都有自己的权限和服务管理习惯,这里需要相当的协调工作来防止相互干扰。
后记
前面留的一个问题,煎饼果子跟单元化架构的关系。答案说起来很简单,你问问煎饼摊怎么看就知道了。
煎饼是分层的,煎饼摊是单元的。消息发送服务是单元的,但是索引维护是分层的。看模式要确定系统的范畴,从不同角度看,同样的东西是有不同意义的。这也是架构师要做的思考。
其实IT系统千百万,模式肯定不会止于这几种。但有了基础的模式,了解它们之间的相似和区别,对于我们设计自己的系统,思考其中的权衡都是有帮助的。
祝一切顺利。