1.1 架构设计的演进过程

业务驱动着技术发展是亘古不变的道理。最开始的时候,业务量少、复杂度低,采取的技术也相对简单,能够基本满足用户对功能的需求。随着 IT 信息化的普及,更多交易被放到了网络上,增加的信息量和频繁的业务访问就变成了需要解决的问题。因此,逐渐产生了缓存、集群等技术手段,同时对业务扩展性和伸缩性的要求也变得越来越高。高并发、高可用、可伸缩、可扩展、够安全一直都是架构设计所追求的目标。下面我们来看一下架构设计经历了哪些阶段,以及每个阶段分别解决了哪些问题,又引出了哪些新问题。

1.1.1 应用与数据一体模式

最早的业务应用以网站、OA 等为主,访问人数有限,单台服务器就能轻松应付,利用 LAMP(Linux、Apache、MySQL、PHP)技术就可以迅速搞定,并且这几种工具都是开源的。在很长一段时间内,有各种针对这种应用模式的开源代码可以使用,但这种模式基本上没有高并发的特性,可用性也很差。有些服务器采用的是托管模式,上面安装着不同的业务应用,一旦服务器出现问题,所有应用就都罢工了,不过其开发和部署成本相对较低,适合刚刚起步的应用服务。图 1-1 描述了单个应用和数据库运行在单台服务器上的模式,我们称这种模式为应用与数据一体模式。

图 1-1 应用与数据一体模式

1.1.2 应用与数据分离模式

随着业务的发展,用户数量和请求数量逐渐上升,服务器的性能便出现了问题。一个比较简单的解决方案是增加资源,将业务应用和数据分开存储,其架构图如图 1-2 所示。其中,应用服务器由于需要处理大量的业务请求,因此对 CPU 和内存有一定要求;而数据服务器因为需要对数据进行存储和索引等 IO 操作,所以更多地会考虑磁盘的转速和内存。这种分离模式解决了性能问题,相应地,也需要扩展更多硬件资源让两种服务器各司其职,使系统可以处理更多的用户请求。虽然业务应用本身没有进行切分,业务应用内部的业务模块依旧存在耦合,但硬件层面的分离在可用性上比一体式设计要好很多。

图 1-2 应用与数据分离模式

1.1.3 缓存与性能的提升

随着信息化系统的发展和互联网使用人数的增多,业务量、用户量、数据量都在增长。同时我们还发现,用户对某些数据的请求量特别大,例如新闻、商品信息和热门消息。在之前的模式下,获取这些信息的方式是依靠数据库,因此会受到数据库 IO 性能的影响,久而久之,数据库便成为了整个系统的瓶颈。而且即使再增加服务器的数量,恐怕也很难解决这个问题,于是缓存技术就登场了,其架构图如图 1-3 所示。这里提到的缓存技术分为客户端浏览器缓存、应用服务器本地缓存和缓存服务器缓存。

客户端浏览器缓存:当用户通过浏览器请求应用服务器的时候,会发起 HTTP 请求。如果将每次 HTTP 请求都缓存下来,就可以极大地减小应用服务器的压力。

应用服务器本地缓存:这种缓存使用的是进程内缓存,又叫托管堆缓存。以 Java 为例,这部分缓存存放在 JVM 的托管堆上面,会受到托管堆回收算法的影响。由于它运行在内存中,对数据的响应速度很快,因此通常用于存放热点数据。当进程内缓存没有命中时,会到缓存服务器中获取信息,如果还是没有命中,才会去数据库中获取。

缓存服务器缓存:这种缓存相对于应用服务器本地缓存来说,就是进程外缓存,既可以和应用服务部署在同一服务器上,也可以部署在不同的服务器上。一般来说,为了方便管理和合理利用资源,会将其部署在专门的缓存服务器上。由于缓存会占用内存空间,因此这类服务器往往会配置比较大的内存。

图 1-3 缓存技术的加入

图 1-3 描述了缓存的请求次序,先访问客户端浏览器缓存,然后是进程内的本地缓存,之后是缓存服务器,最后才是数据库。只要在其中任意一个阶段获取到了缓存信息,就不会继续往下访问了,否则会一直按照这个次序获取缓存信息,直到访问数据库。

用户请求访问数据的顺序为客户端浏览器缓存→应用服务器本地缓存→缓存服务器缓存。如果按照以上次序还没有命中数据,才会访问数据库获取数据。

加入缓存技术后,系统性能得到了提高。这是因为缓存位于内存中,而内存的读取速度要比磁盘快得多,能够很快响应用户请求。特别针对一些热点数据,优势尤为明显。同时,在可用性方面也有明显改善,即使数据服务器出现短时间的故障,在缓存服务器中保存的热点数据或者核心数据依然可以满足用户暂时的访问。当然,后面会针对可用性进行优化。

1.1.4 服务器集群处理并发

经过前面三个阶段的演进,系统对用户的请求量有了很好的支持。实际上,这些都是在逐步提高系统的性能和可用性,这一核心问题会一直贯穿于整个系统架构的演进过程中。可随着用户请求量的增加,另外一个问题又出现了,那就是并发。把这两个字拆开了来看:并,可以理解为“一起并行”,有同时的意思;发,可以理解为“发出调用”,也就是发出请求的意思。合起来,并发就是指多个用户同时请求应用服务器。如果说原来的系统面对的只是大数据量,那么现在就需要面对多个用户同时请求。此时若还是按照上一个阶段的架构图推导,那么单个应用服务器已经无法满足高并发的要求了。此时,服务器集群加入了战场,其架构图如图 1-4 所示。服务器集群说白了,就是多台服务器扎堆的意思,用更多服务器来分担单台服务器的负载压力,提高性能和可用性。再说白一点,就是提高单位时间内服务处理请求的数量。原来是一台服务器处理多个用户的请求,现在是一堆服务器处理,就好像银行柜台一样,通过增加柜员的人数来服务更多的客户。

图 1-4 服务器集群的加入

这次的架构演进与上次相比,增加了应用服务器的个数,用多台应用服务器形成服务器集群,单台应用服务器中部署的应用服务并没有改变,在用户请求与服务器之间加入了负载均衡器,以便将用户请求路由到对应的服务器中。这次解决的系统瓶颈是如何处理用户的高并发请求,因此对数据库和缓存都没有做更改,仅通过增加服务器的数量便能缓解并发请求的压力。服务器集群通过多台服务器分担原来一台服务器需要处理的请求,在多台服务器上同时运行一套系统,同时处理大量并发的用户请求,有点三个臭皮匠顶个诸葛亮的意思,因此对集群中单台服务器的硬件要求也随之降低。此时需要注意负载均衡器采用的均衡算法(例如轮询和加权轮询)要能保证用户请求均匀地分布到多台服务器上、属于同一个会话的所有请求在同一个服务器上处理,以及针对不同服务器资源的优劣能够动态调整流量。加入负载均衡器之后,由于其位于互联网与应用服务器之间,负责用户流量的接入,因此可以对用户流量进行监控,同时对提出访问请求的用户的身份和权限进行验证。

1.1.5 数据库读写分离

加入缓存可以解决部分热点数据的读取问题,但缓存的容量毕竟有限,那些非热点的数据依然要从数据库中读取。数据库对于写入和读取操作的性能是不一样的。在写入数据时,会造成锁行或者锁表,此时如果有其他写入操作并发执行,就会出现排队现象。而读取操作不仅比写入操作更加快捷,并且可以通过索引、数据库缓存等方式实现。因此,推出了数据库读写分离的方案,其架构图如图 1-5 所示。这种模式设置了主从数据库,主库(master)主要用来写入数据,然后通过同步 binlog 的方式,将更新的数据同步到从库(slave)中。对于应用服务器而言,在写数据时只需要访问主库,在读数据时只用访问从库就好了。

图 1-5 数据库读写分离

数据库读写分离的方式将数据库的读、写职责分离开来,利用读数据效率较高的优势,扩展更多的从库,从而服务于请求读取操作的用户。毕竟在现实场景中,大多数操作是读取操作。此外,数据同步技术可以分为同步复制技术、异步复制技术和半同步复制技术,这些技术的原理会在第 6 章中为大家介绍。体会到数据库读写分离带来的益处的同时,架构设计也需要考虑可靠性的问题。例如,如果主库挂掉,从库如何接替主库进行工作;之后主库恢复了,是成为从库还是继续担任主库,以及主从库如何同步数据。这些问题的解决方案在第 6 章会讲述。

1.1.6 反向代理和 CDN

随着互联网的逐渐普及,人们对网络安全和用户体验的要求也越来越高。之前用户都是通过客户端直接访问应用服务器获取服务,这使得应用服务器暴露在互联网中,容易遭到攻击。如果在应用服务器与互联网之间加上一个反向代理服务器,由此服务器来接收用户的请求,然后再将请求转发到内网的应用服务器,相当于充当外网与内网之间的缓冲,就可以解决之前的问题。反向代理服务器只对请求进行转发,自身不会运行任何应用,因此当有人攻击它的时候,是不会影响到内网的应用服务器的,这在无形中保护了应用服务器,提高了安全性。同时,反向代理服务器也在互联网与内网之间起适配和网速转换的作用。例如,应用服务器需要服务于公网和教育网,但是这两个网络的网速不同,那么就可以在应用服务器与互联网之间放两台反向代理服务器,一台连接公网,另一台连接教育网,用于屏蔽网络差异,服务于更多的用户群体。图 1-6 中的公网客户端和校园网客户端分别来自公网与校园网两个不同的网络,由于两者访问速度不同,因此会分别设置公网反向代理服务器和校园网反向代理服务器,通过这种方式将位于不通网络的用户请求接入到系统中。

图 1-6 加入反向代理服务器

聊完反向代理,再来说 CDN,它的全称是 Content Delivery Network,也就是内容分发网络。如果把互联网想象成一张大网,那么每台服务器或者每个客户端就是分布在这张大网中的节点。节点之间的距离有远有近,用户请求会从一个节点跳转到另外一个节点,最终跳转到应用服务器获取信息。跳转的次数越少,越能够快速地获取信息,因此可以在离客户端近的节点中存放信息。这样用户通过客户端,只需要跳转较少的次数就能够触达信息。由于这部分信息更新频率不高,因此推荐存放一些静态数据,例如 JavaScript 文件、静态的 HTML、图片文件等。这样客户端就可以从离自己最近的网络节点获取资源,大大提升了用户体验和传输效率。加入 CDN 后的架构图如图 1-7 所示。

图 1-7 加入 CDN

CDN 的加入明显加快了用户访问应用服务器的速度,同时减轻了应用服务器的压力,原来必须直接访问应用服务器的请求,现在不需要经过层层网络,只要找到最近的网络节点就可以获取资源。但从请求资源的角度来看,这种方式也有局限性,即它只对静态资源起作用,而且需要定时对 CDN 服务器进行资源更新。反向代理和 CDN 的加入解决了安全性、可用性和高性能的问题。

1.1.7 分布式数据库与分表分库

经历了前面几个阶段的演进后,软件的系统架构已经趋于稳定。可是随着系统运行时间的增加,数据库中累积的数据越来越多,同时系统还会记录一些过程数据,例如操作数据和日志数据,这些数据也会加重数据库的负担。即便数据库设置了索引和缓存,但在进行海量数据查询时还是会表现得捉襟见肘。如果说读写分离是对数据库资源从读写层面进行分配,那么分布式数据库就需要从业务和数据层面对数据库进行分配。

•对于数据表来说,当表中包含的记录过多时,可将其分成多张表来存储。例如,有 1000 万个会员记录,既可以将其分成两个 500 万,分别放到两张表中存储,也可以按照业务对表中的列进行分割,把表中的某些列放到其他表中存储,然后通过外键关联到主表。注意被分割出去的列通常是不经常访问的数据。

•对于数据库来说,每个数据库能够承受的最大连接数和连接池是有上限的。为了提高数据访问效率,会根据业务需求对数据库进行分割,让不同的业务访问不同的数据库。当然,也可以将相同业务的不同数据放到不同的数据库中存储。

如果将数据库资源分别放到不同的数据库服务器中,就是分布式数据库设计。由于数据存储在不同的表/库中,甚至在不同的服务器上面,因此在进行数据库操作的时候会增加代码的复杂度。此时可以加入数据库中间件来实现数据同步,从而消除不同存储载体间的差异。架构如图 1-8 所示,将数据拆分以后分别放在表 1 和表 2 中,两张表所在的数据库服务器也不相同,库与库之间还需要考虑数据同步的问题。因为数据的分散部署,所以从业务应用获取数据时需要依靠数据库中间件的帮忙。

图 1-8 分布式数据库与分表分库

数据库的分布式设计以及分表分库,会给系统带来性能的提升,同时也增大了数据库管理和访问的难度。原来只需访问一张表和一个库就可以获取数据,现在需要跨越多张表和多个库。

从软件编程的角度来看,有一些数据库中间件提供了最佳实践,例如 MyCat 和 Sharding JDBC。此外,从数据库服务器管理的角度来看,需要监控服务器的可用性。从数据治理的角度来看,需要考虑数据扩容和数据治理的问题。

1.1.8 业务拆分

解决了大数据量存储问题以后,系统就能够存储更多的数据,这意味着能够处理更多的业务。业务量的增加、访问数的上升,是任何一个软件系统在任何时期都要面临的严峻考验。通过对前面几个阶段的学习,我们知道系统提升依靠的基本都是以空间换取时间,使用更多的资源和空间处理更多的用户请求。随着业务的复杂度越来越高,以及高并发的来临,一些大厂开始对业务应用系统进行拆分,将应用分开部署,此时的架构图如图 1-9 所示。如果说前面的服务器集群模式是将同一个应用复制到不同的服务器上,那么业务拆分就是将一个应用拆成多个部署到不同的服务器中。此外,还有的是对核心应用进行水平扩展,将其部署到多台服务器上。应用虽然做了拆分,但应用之间仍旧有关联,存在相互之间的调用、通信和协调问题。由此引入了队列、服务注册发现、消息中心等中间件,这些中间件可以协助系统管理分布到不同服务器、网络节点上的应用。

图 1-9 业务拆分

业务拆分以后会形成一个个应用服务,既有基于业务的服务,例如商品服务、订单服务,也有基础服务,例如消息推送和权限验证。这些应用服务连同数据库服务器分布在不同的容器、服务器、网络节点中,它们之间的通信、协调、管理和监控都是我们需要解决的问题。

1.1.9 分布式与微服务

近几年,微服务是一种比较火的架构方式,它对业务应用进行了更加精细化的切割,使之成为更小的业务模块,能够做到模块间的高内聚低耦合,每个模块都可以独立存在,并由独立的团队维护。每个模块内部可以采取特有的技术,而不用关心其他模块的技术实现。模块通过容器的部署运行,各模块之间通过接口和协议实现调用。可以将任何一个模块设为公开,以供其他模块调用,也可以热点模块进行水平扩展,增强系统的整体性能,这样当其中某一个模块出现问题时,就能由其他相同的模块代替其工作,增强了可用性。

大致总结下来,微服务拥有以下特点:业务精细化拆分、自治性、技术异构性、高性能、高可用。它像极了分布式架构,从概念上理解,二者都做了“拆”的动作,但在下面这几个方面存在区别,直观展示见图 1-10。

拆分目的不同:提出分布式设计是为了解决单体应用资源有限的问题,一台服务器无法支撑更多的用户访问,因此将一个应用拆解成不同的部分,然后分别部署到不同服务器上,从而分担高并发的压力。微服务是对服务组件进行精细化,目的是更好地解耦,让服务之间通过组合实现高性能、高可用、可伸缩、可扩展。

拆分方式不同:分布式服务架构将系统按照业务和技术分类进行拆分,目的是让拆分后的服务负载原来单一服务的业务。微服务则是在分布式的基础上进行更细的拆分,它将服务拆成更小的模块,不仅更专业化,分工也更为精细,并且每个小模块都能独立运行。

部署方式不同:分布式架构将服务拆分以后,通常会把拆分后的各部分部署到不同服务器上。而微服务既可以将不同的服务模块部署到不同服务器上,也可以在一台服务器上部署多个微服务或者同一个微服务的多个备份,并且多使用容器的方式部署。

图 1-10 分布式与微服务的区别

虽然分布式与微服务具有以上区别,但从实践的角度来看,它们都是基于分布式架构的思想构建的。可以说微服务是分布式的进化版本,也是分布式的子集,因此它同样会遇到服务拆分、服务通信、协同、管理调度等问题,这也是我在后面要给大家讲解的内容。