架构亦非玄学

UbEWe.png

它是一个唯有我们自己才能带秩序的过程,它不可能被求取,但只要我们顺应它,它便会自然而然地出现。 《建筑永恒之道》

套用一句杜甫的话 架构本天成,妙手偶得之。而现在的互联网仿佛在神话架构师这样的角色。在这里不是要去否定 martinfowler 这样伟大的架构师,但是martin 老师实在是向社会输出了太多 New Concept,虽然本意是好的,然而在在多数的从业人眼中,架构变成了一个玄之又玄的东西。

我们就从最常见的高并发的秒杀场景入手,看看架构是如何演化的。

情景分析

商品秒杀、商品抢购、群红包、抢优惠券、抽奖等等。秒杀商品价格低廉、抢购商品很好|抢手、大幅推广|广为人知、瞬时售空、一般是定时上架、持续时间短、瞬时并发量高等。

UF2tU.png

  • 秒杀前:用户不断刷新商品详情页,页面请求达到瞬时峰值。
  • 秒杀开始:用户点击秒杀按钮,下单请求达到瞬时峰值。
  • 秒杀后:一部分成功下单的用户不断刷新订单或者产生退单操作,大部分用户继续刷新商品详情页等待退单机会。

转换为技术问题

那我们用技术来定问题

  • 秒杀前:多读少写(无写)
  • 秒杀中:多写多读
  • 秒杀后:多读少写

秒杀前:多度少写

对于多度少写的场景,是最容易解决的,对于大部分的秒杀场景,将页面静态化即可,页面静态化 + CDN分发。
UF3zO.png

秒杀中

问题开始变的复杂起来,我们先从最简单的架构体系看起来。

UFKIw.png

应用直接怼到服务端,服务端直接将数据落盘。这大概就是最简单的系统架构了,我们看看秒杀系统是如何一步步的迭代的。

扩容

最简单的方式,肯定是扩容。
UFhU8.png

一套系统不够,两套,三套。看起来很美好,但是这种方案需要考虑

  • 数据分片:不同的实例如何将数据放置于不同的实例
  • 请求路由:这样的架构势必要求用户能够在存有自己数据的那一套上进行访问
  • 弹性困难

因此根据这套系统略微的变形一下。

UFyxk.png

让应用自行进行数据分区的选择,简单来说,比如我们可以根据 用户手机号 取模,然后将数据定位某一台机器。

UFT6e.png

对于这样的规则可以选择的余地还是比较的多,用户ID生日,只要数据均衡都比较好。对于这条路,我们发现社区已经有了 shardingsphere 这样的数据库中间件可以帮助我们。

*扩容遇见的问题

当我们在支撑更大规模的场景的时候,原有的系统不足够支撑的时候,我们又需要增加更多的 Datebase 的时候,我们遇见了一个问题。

UknjD.png

因为多了一个数据节点,所有的数据需要重新锚定节点,这时候如何尽可能少的迁移数据,这时候就提出了 一致性Hash


当然扩容并非是一条最好的选择,我们还要面临数据库中其实还有大量没有那么高频的数据,在这样的架构下,我们不就不得将一些同样的数据存多,当然也可以单独出一个 Metadata 的数据库。这套方案最为致命的问题应该是我们这样拆分之后,对于未来的系统开发有着非常大的限制,所有的批处理都要考虑多节点。因此在这套 应用分库分表 的基础上,会进化为出下一一个版本。

UkS48.png

我们将分发的逻辑抽离出来,这样不就好了,如果可以的话最好还可以直接将这个分片逻辑变成业务无关性的。等等,此时是不是想到了我们熟悉的 tidb 了。

UkoPt.png

我们将整个 Database ProxyDatabase 独立出来行成了 NewSQL Database,弹性扩容和伸缩都搞定了,岂不是很爽。对于开发者来说,再也不用担心数据库所带来的压力。

异步化

除了扩容之外,异步化也是一个很好的思路,因为对于数据库系统需要保证 Atom 特性,并且因为本身的数据结构的复杂,因此单一处理速度的上限并不算高。此时我们可以将用户的行为记录下来然后再顺序处理。

但是值得注意的是,异步化本身不能提高秒杀的最终写入速度,但是因为保护的系统(不会导致系统雪崩)整体上会让秒杀系统变的健壮,也变相让系统处理更快。

Umfqf.png

异步化问题

异步化本身会带来一个非常不舒服的体验,就是你可以只是拿到一个一个排队的序号,但是你并没有真正的落单。就像是彩票系统,我们买了事先就存在的号码,等到最终的摇号。

缓存

上面的方案完美了么?显然不是,虽然 NewSQL 带来的更大的并发,但是并发并非不是没有成本的,因为数据库系统从单机变成分布式,系统内的协调耗时增加,整个处理的链路会变的更加的慢,用户的体验不是非常的好,对于真の瞬时并发也是可能击穿的。因此我们还是需要额外的技能:缓存化。

UgHafP.png

更多时候是成本问题,用内存能解决的问题换做磁盘,就会带来架构和复杂的上升,但是实际上架构是类似的。

因为缓存多数工作在 内存 中,并且不一定提供的可靠的 持久化 方案,换来的是更快的读写速度。

缓存分片

当一个Redis不够用的时候,自然需要需要多个 Redis 来支撑系统,不过由于 Redis 的发展较晚也比较的完善,自带了数据分片的 Cluster 组件 参考3

Uv4uU.png


架构就自然变成了

UvB4O.png

不过因为绝大多数的时候,我们都是混合使用 DB in file/DB in memory 这两种技术,绝大多数的时候我们的最终数据依然要落入 Mysql 这样的数据内,因此无论是在 写入时刻 将数据存入 Mysql 或者还是 秒杀结束 将数据存入 Mysql 都是可以的。

秒杀后

秒杀后的场景是:当某些客户进行秒杀成功之后就会疯狂的刷新自己的订单页面,而此时其他的客户可能仍在秒杀活动中。
其实这段时间场景是简单的,可以直接将所有的数据都在 Redis 中进行处理,等到彻底所有的数据都刷新到磁盘上再切换至平时的系统,也可以增加缓存的方式都是可解的。

场景工具箱

想来我最喜欢的一句话

技术就是对现象的有目的的编程。

对于我们来说,我们就像是在一套工具箱中选择最适合的工具然后将目标达成。

Uv29k.png

当个工程师的意义也在于如何选择好的工具将我们的系统完成。

Uv0ki.png

聊一聊

不得承认,现在的中国的程序员行业由于过度竞争,导致了面试的水涨船高,无论你之前是做什么行业什么工种,都喜欢问关于 高并发 系统的设计(大概也只有前端可幸免),导致慢慢的风气也从踏实解决问题变成了背诵式的架构面试。而真正的架构是一门实践技术,无论是看了多少网上的博客,最终如果不能在实践中获得反馈,那注定是收益有限的,因此也为什么大厂的场景是那么的值钱了。


最初是基于 现象(内存比磁盘快,异步更稳定)的一系列物理现象,演化成了特定环境解决问题的解决方案(A pattern is a successful or efficient solution to a recurring problem within a context)称之为 模式,当 模式 反复使用,我们就付出一些代价分割为 功能单元(redis/mysql) ,然后再基于一些联系(即:工作架构 working architecute),将所有的模块/功能单元集在一起完成一个有目的的事情,也就是我们架构师所应尽的职能。

功能单元因为随着时代在不断的迭代,因此架构师们也需要不断的和时代进步,才能完成一个正确的架构设计。

参考