高并发系统通用的设计方法为拓展、缓存及异步。
一般来讲,在我们系统设计初期会考虑使用 Scale-up 的方式,因为这种方案足够简单,所谓能用堆砌硬件解决的问题就用硬件来解决,但是当系统并发超过了单机的极限时,我们就要使用 Scale-out 的方式。
Web 2.0 是缓存的时代,这一点毋庸置疑。缓存遍布在系统设计的每个角落,从操作系统到浏览器,从数据库到消息队列,任何略微复杂的服务和组件中你都可以看到缓存的影子。我们使用缓存的主要作用是提升系统的访问性能,在高并发的场景下就可以支撑更多用户的同时访问。
Web 2.0 是缓存的时代,这一点毋庸置疑。缓存遍布在系统设计的每个角落,从操作系统到浏览器,从数据库到消息队列,任何略微复杂的服务和组件中你都可以看到缓存的影子。我们使用缓存的主要作用是提升系统的访问性能,在高并发的场景下就可以支撑更多用户的同时访问。
普通磁盘的寻道时间是 10ms 左右,而相比于磁盘寻道花费的时间,CPU 执行指令和内存寻址的时间都是在 ns(纳秒)级别,从千兆网卡上读取数据的时间是在μs(微秒)级别。所以在整个计算机体系中磁盘是最慢的一环,甚至比其它的组件要慢几个数量级。因此我们通常使用以内存作为存储介质的缓存,以此提升性能。当然,缓存的语义已经丰富了很多,我们可以将任何降低响应时间的中间存储都称为缓存。缓存的思想遍布很多设计领域,比如在操作系统中 CPU 有多级缓存,文件有 Page Cache 缓存,你应该有所了解。
异步也是一种常见的高并发设计方法,与之共同出现的还有它的反义词:同步。比如分布式服务框架 Dubbo 中有同步方法调用和异步方法调用,IO 模型中有同步 IO 和异步 IO。
以方法调用为例,同步调用代表调用方要阻塞等待被调用方法中的逻辑执行完成。这种方式下,当被调用方法响应时间较长时,会造成调用方长久的阻塞,在高并发下会造成整体系统性能下降甚至发生雪崩。
异步调用恰恰相反,调用方不需要等待方法逻辑执行完成就可以返回执行其他的逻辑,在被调用方法执行完毕后再通过回调、事件通知等方式将结果反馈给调用方。
异步调用在大规模高并发系统中被大量使用,比如我们熟知的 12306 网站。当我们订票时,页面会显示系统正在排队,这个提示就代表着系统在异步处理我们的订票请求。在 12306 系统中查询余票、下单和更改余票状态都是比较耗时的操作,可能涉及多个内部系统的互相调用,如果是同步调用就会像 12306 刚刚上线时那样,高峰期永远不可能下单成功。
而采用异步的方式,后端处理时会把请求丢到消息队列中,同时快速响应用户,告诉用户我们正在排队处理,然后释放出资源来处理更多的请求。订票请求处理完之后,再通知用户订票成功或者失败。处理逻辑后移到异步处理程序中,Web 服务的压力小了,资源占用的少了,自然就能接收更多的用户订票请求,系统承受高并发的能力也就提升了。
在系统从 0 到 1 的阶段,为了让系统快速上线,我们通常是不考虑分层的。但是随着业务越来越复杂,大量的代码纠缠在一起,会出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动一处就牵一发而动全身等问题。
软件架构分层在软件工程中是一种常见的设计方式,它是将整体系统拆分成 N 个层次,每个层次有独立的职责,多个层次协同提供完整的功能。
一种常见的分层方式是将整体架构分为表现层、逻辑层和数据访问层:表现层,顾名思义嘛,就是展示数据结果和接受用户指令的,是最靠近用户的一层;逻辑层里面有复杂业务的具体实现;数据访问层则是主要处理和存储之间的交互。
这是在架构上最简单的一种分层方式。其实,我们在不经意间已经按照三层架构来做系统分层设计了,比如在构建项目的时候,我们通常会建立三个目录:Web、Service 和 Dao,它们分别对应了表现层、逻辑层还有数据访问层。
工作中经常能用到 TCP/IP 协议,它把网络简化成了四层,即链路层、网络层、传输层和应用层。每一层各司其职又互相帮助,网络层负责端到端的寻址和建立连接,传输层负责端到端的数据传输等,同时相邻两层还会有数据的交互。这样可以隔离关注点,让不同的层专注做不同的事情。
Linux 文件系统也是分层设计的,从下图你可以清晰地看出文件系统的层次。在文件系统的最上层是虚拟文件系统(VFS),用来屏蔽不同的文件系统之间的差异,提供统一的系统调用接口。虚拟文件系统的下层是 Ext3、Ext4 等各种文件系统,再向下是为了屏蔽不同硬件设备的实现细节,我们抽象出来的单独的一层——通用块设备层,然后就是不同类型的磁盘了。
分层的设计可以简化系统设计,让不同的人专注做某一层次的事情。
单一职责原则规定每个类只有单一的功能,在这里可以引申为每一层拥有单一职责,且层与层之间边界清晰;迪米特法则原意是一个对象应当对其它对象有尽可能少的了解,在分层架构的体现是数据的交互不能跨层,只能在相邻层之间进行;而开闭原则要求软件对扩展开放,对修改关闭。它的含义其实就是将抽象层和实现层分离,抽象层是对实现层共有特征的归纳总结,不可以修改,但是具体的实现是可以无限扩展,随意替换的。
在这个分层架构中主要增加了 Manager 层,它与 Service 层的关系是:Manager 层提供原子的服务接口,Service 层负责依据业务逻辑来编排原子接口。
3.小结
关于系统的设计,罗马不是一天建成的,系统的设计也是如此。不同量级的系统有不同的痛点,也就有不同的架构设计的侧重点。如果都按照百万、千万并发来设计系统,电商一律向淘宝看齐,IM 全都学习微信和 QQ,那么这些系统的命运一定是灭亡。
一般系统的演进过程应该遵循下面的思路:
关于系统分层,任何事物都不可能是尽善尽美的,分层架构虽有优势也会有缺陷,它最主要的一个缺陷就是增加了代码的复杂度。另外一个可能的缺陷是,如果我们把每个层次独立部署,层次间通过网络来交互,那么多层的架构在性能上会有损耗。这也是为什么服务化架构性能要比单体架构略差的原因,也就是所谓的“多一跳”问题。
但是分层带来的好处也很多,在高并发系统中可以很好地对各个组成系统进行水平拓展,而不用牵一发而动全身。