架构亦非玄学
它是一个唯有我们自己才能带秩序的过程,它不可能被求取,但只要我们顺应它,它便会自然而然地出现。 《建筑永恒之道》
套用一句杜甫的话 架构本天成,妙手偶得之
。而现在的互联网仿佛在神话架构师这样的角色。在这里不是要去否定 martinfowler
这样伟大的架构师,但是martin
老师实在是向社会输出了太多 New Concept
,虽然本意是好的,然而在在多数的从业人眼中,架构变成了一个玄之又玄的东西。
我们就从最常见的高并发的秒杀场景入手,看看架构是如何演化的。
情景分析
商品秒杀、商品抢购、群红包、抢优惠券、抽奖等等。秒杀商品价格低廉、抢购商品很好|抢手、大幅推广|广为人知、瞬时售空、一般是定时上架、持续时间短、瞬时并发量高等。
- 秒杀前:用户不断刷新商品详情页,页面请求达到瞬时峰值。
- 秒杀开始:用户点击秒杀按钮,下单请求达到瞬时峰值。
- 秒杀后:一部分成功下单的用户不断刷新订单或者产生退单操作,大部分用户继续刷新商品详情页等待退单机会。
转换为技术问题
那我们用技术来定问题
- 秒杀前:多读少写(无写)
- 秒杀中:多写多读
- 秒杀后:多读少写
秒杀前:多度少写
对于多度少写的场景,是最容易解决的,对于大部分的秒杀场景,将页面静态化即可,页面静态化 + CDN分发。
秒杀中
问题开始变的复杂起来,我们先从最简单的架构体系看起来。
应用直接怼到服务端,服务端直接将数据落盘。这大概就是最简单的系统架构了,我们看看秒杀系统是如何一步步的迭代的。
扩容
最简单的方式,肯定是扩容。
一套系统不够,两套,三套。看起来很美好,但是这种方案需要考虑
- 数据分片:不同的实例如何将数据放置于不同的实例
- 请求路由:这样的架构势必要求用户能够在存有自己数据的那一套上进行访问
- 弹性困难
因此根据这套系统略微的变形一下。
让应用自行进行数据分区的选择,简单来说,比如我们可以根据 用户手机号
取模,然后将数据定位某一台机器。
对于这样的规则可以选择的余地还是比较的多,用户ID
,生日
,只要数据均衡都比较好。对于这条路,我们发现社区已经有了 shardingsphere 这样的数据库中间件可以帮助我们。
*扩容遇见的问题
当我们在支撑更大规模的场景的时候,原有的系统不足够支撑的时候,我们又需要增加更多的 Datebase
的时候,我们遇见了一个问题。
因为多了一个数据节点,所有的数据需要重新锚定节点,这时候如何尽可能少的迁移数据,这时候就提出了 一致性Hash
当然扩容并非是一条最好的选择,我们还要面临数据库中其实还有大量没有那么高频的数据,在这样的架构下,我们不就不得将一些同样的数据存多,当然也可以单独出一个 Metadata
的数据库。这套方案最为致命的问题应该是我们这样拆分之后,对于未来的系统开发有着非常大的限制,所有的批处理都要考虑多节点。因此在这套 应用分库分表
的基础上,会进化为出下一一个版本。
我们将分发的逻辑抽离出来,这样不就好了,如果可以的话最好还可以直接将这个分片逻辑变成业务无关性的。等等,此时是不是想到了我们熟悉的 tidb
了。
我们将整个 Database Proxy
和 Database
独立出来行成了 NewSQL Database
,弹性扩容和伸缩都搞定了,岂不是很爽。对于开发者来说,再也不用担心数据库所带来的压力。
异步化
除了扩容之外,异步化也是一个很好的思路,因为对于数据库系统需要保证 Atom
特性,并且因为本身的数据结构的复杂,因此单一处理速度的上限并不算高。此时我们可以将用户的行为记录下来然后再顺序处理。
但是值得注意的是,异步化本身不能提高秒杀的最终写入速度,但是因为保护的系统(不会导致系统雪崩)整体上会让秒杀系统变的健壮,也变相让系统处理更快。
异步化问题
异步化本身会带来一个非常不舒服的体验,就是你可以只是拿到一个一个排队的序号,但是你并没有真正的落单。就像是彩票系统,我们买了事先就存在的号码,等到最终的摇号。
缓存
上面的方案完美了么?显然不是,虽然 NewSQL
带来的更大的并发,但是并发并非不是没有成本的,因为数据库系统从单机变成分布式,系统内的协调耗时增加,整个处理的链路会变的更加的慢,用户的体验不是非常的好,对于真の瞬时并发也是可能击穿的。因此我们还是需要额外的技能:缓存化。
因为缓存多数工作在 内存
中,并且不一定提供的可靠的 持久化
方案,换来的是更快的读写速度。
缓存分片
当一个Redis不够用的时候,自然需要需要多个 Redis 来支撑系统,不过由于 Redis
的发展较晚也比较的完善,自带了数据分片的 Cluster
组件 参考3。
架构就自然变成了
不过因为绝大多数的时候,我们都是混合使用 DB in file
/DB in memory
这两种技术,绝大多数的时候我们的最终数据依然要落入 Mysql
这样的数据内,因此无论是在 写入时刻
将数据存入 Mysql
或者还是 秒杀结束
将数据存入 Mysql
都是可以的。
秒杀后
秒杀后的场景是:当某些客户进行秒杀成功之后就会疯狂的刷新自己的订单页面,而此时其他的客户可能仍在秒杀活动中。
其实这段时间场景是简单的,可以直接将所有的数据都在 Redis 中进行处理,等到彻底所有的数据都刷新到磁盘上再切换至平时的系统,也可以增加缓存的方式都是可解的。
场景工具箱
想来我最喜欢的一句话
技术就是对现象的有目的的编程。
对于我们来说,我们就像是在一套工具箱中选择最适合的工具然后将目标达成。
当个工程师的意义也在于如何选择好的工具将我们的系统完成。
聊一聊
不得承认,现在的中国的程序员行业由于过度竞争,导致了面试的水涨船高,无论你之前是做什么行业什么工种,都喜欢问关于 高并发
系统的设计(大概也只有前端可幸免),导致慢慢的风气也从踏实解决问题变成了背诵式的架构面试。而真正的架构是一门实践技术,无论是看了多少网上的博客,最终如果不能在实践中获得反馈,那注定是收益有限的,因此也为什么大厂的场景是那么的值钱了。
最初是基于 现象
(内存比磁盘快,异步更稳定)的一系列物理现象,演化成了特定环境解决问题的解决方案(A pattern is a successful or efficient solution to a recurring problem within a context)称之为 模式
,当 模式
反复使用,我们就付出一些代价分割为 功能单元
(redis/mysql) ,然后再基于一些联系(即:工作架构 working architecute),将所有的模块/功能单元集在一起完成一个有目的的事情,也就是我们架构师所应尽的职能。
功能单元
因为随着时代在不断的迭代,因此架构师们也需要不断的和时代进步,才能完成一个正确的架构设计。