系统架构演进过程
随着 IT 信息化的普及,更多的交易放到了网络上,信息量增加和访问次数频繁就是要解决的问题了。因此,逐渐加入了缓存、集群等技术手段。同时对业务的扩展性和伸缩性的要求也越来越高。高并发、高可用、可伸缩、可扩展、够安全的软件架构一直是架构设计追求的目标。今天我们来看一下架构设计经历了哪些阶段,每个阶段都解决了哪些问题,又引出了哪些新问题。主要是引起大家的思考,在不同的业务发展阶段采取合适技术手段,用变化拥抱变化是 IT 人追求的目标。
应用与数据一体模式
最早的业务应用以网站、OA 等为主,访问的人数有限,单台服务器就能够应付。
通常,将应用程序和数据库部署到一台服务器上面,如图 1 所示:
图 1:应用与数据一体模式
在这一阶段,我们利用 LAMP(Linux Apache MySQL PHP)技术就可以迅速搞定,并且这些工具都是开源的。很长一段时间内,有各种针对这种应用模式的开源代码可以使用。这种模式基本上没有高并发的要求,可用性也很差。有的服务器采用托管模式,上面就安装了不同的业务应用,一旦服务器出现问题,所有的应用就罢工了。不过其开发和部署成本相对较低,适合刚刚起步的应用服务。图 1 就描述了单个应用和数据库运行在单台服务器的模式,我们称这种模式为应用与数据一体模式。
应用与数据分离模式
随着业务的发展,用户数和请求数逐渐上升,服务器的性能出现了问题。其中比较简单的解决方案就是增加资源,将业务应用和数据存储分开。
其架构图如图 2 所示:
图 2:应用与数据分离模式
其中,应用服务器需要处理大量的业务请求,对 CPU 和内存有一定要求;而数据库服务器需要对数据进行存储和索引等 IO 操作,对磁盘的转速和内存会考虑更多。
这样的分离解决了性能的问题,我们需要扩展更多的硬件资源让其各司其职,使系统可以处理更多的用户请求。
虽然业务上依旧存在耦和,但硬件层面的分离在可用性上比一体式设计要好很多。
缓存的加入
随着信息化系统的发展和使用互联网人数的增多,业务量、用户量、数据量都在增长。
我们同时发现,用户会对某些数据的请求量特别大,例如新闻、商品信息和热门消息。
之前这些信息的获取方式是依靠数据库,因此受到数据库 IO 性能的影响。此时数据库成为了整个系统的瓶颈。
如果再增加服务器的数量,恐怕也很难解决,于是缓存技术就登场了,其架构图如图 3 所示:
图 3:缓存的加入
这里提到的缓存技术分为客户端浏览器缓存、应用服务器本地缓存和缓存服务器缓存。
**①客户端浏览器缓存:**当用户通过浏览器请求服务器的时候,会发起 HTTP 请求。如果对每次 HTTP 请求进行缓存,那么可以减少应用服务器的压力。
**②应用服务器本地缓存:**它使用的是进程内缓存,又叫托管堆缓存。以 Java 为例,这部分缓存放在 JVM 的托管堆上面,同时会受到托管堆回收算法的影响。
由于它运行在内存中,对数据的响应速度很快,通常我们会把热点数据放在这里。
在进程内缓存没有命中的时候,会到缓存服务器中获取信息,如果还是没有命中,才会去数据库中获取。
**③缓存服务器缓存:**它相对于应用服务器本地缓存来说,就是进程外缓存,既可以和应用服务部署在同一服务器,也可以部署到不同的服务器。
一般来说,为了方便管理和合理利用资源,会将其部署到专门的缓存服务器上面。由于缓存会占用内存空间,因此这类服务器会配置比较大的内存。
图 3 描述了缓存请求的次序,先访问客户端缓存,之后是进程内的本地缓存,接下来是缓存服务器,最后才是数据。
如果在任意一层获取了缓存信息,就不再往下访问了,否则会一直按照这个次序获取缓存信息,直到数据库。
用户请求访问数据的顺序为客户端浏览器缓存→应用服务器本地缓存→缓存服务器缓存。
如果按照以上次序还没有命中数据,才会访问数据库获取数据。加入缓存的设计,提高了系统的性能。
由于缓存放在内存中,而内存的读取速度比磁盘要快得多,能够很快响应用户请求。
特别针对一些热点数据,优势尤为明显。同时,在可用性方面也有明显的改善。
即使数据库服务器出现短时间的故障,缓存服务器中保存的热点或者核心数据依旧可以满足用户暂时的访问。当然,后面还会对可用性进行优化。
服务器集群的加入
经过前面三个阶段的演进,系统对用户的请求量有了很好的支持。实际上,这都是在解决高性能和可用性的问题,这一核心问题会一直贯穿整个系统架构的演进过程中。
随着用户请求量的增加,另外一个问题又出现了,那就是并发。把这两个字拆开了来看:并,理解为“一起并行“,有同时的意思;发,理解为“发出调用”,也就是请求的意思。
合起来就是多个用户同时请求应用服务器。如果说原来的系统面对的仅仅只是大数据量的话,那么现在就需要面对多用户同时请求。
如果还是按照上一个阶段的架构图推导,单个应用服务器已经无法满足高并发的要求了。
此时,服务器集群就加入战场了,其架构图如图 4 所示:
*图 4:*服务器集群的加入
服务器集群也就是多台服务器扎堆的意思,用更多的服务器来分担单台服务器的负载压力,提高性能和可用性。
再说白一点,就是提高单位时间内服务处理请求的数量。原来是一个服务器处理,现在是一堆服务器来处理。就好像银行柜台一样,增加柜员的人数来服务更多的人。
这次架构演进与上次相比,增加了应用服务器的个数,用多台应用服务器形成集群。
应用服务器中所部署的应用服务没有改变,在用户请求与服务器之间加入了负载均衡器,帮助用户请求路由到对应的服务器中。增加服务器的举动表明,系统的瓶颈是在处理用户并发请求上。
针对数据库和缓存都没有做更改,这样仅仅通过增加服务器数量就能够缓解请求的压力。
服务器集群会通过多台服务器来分担原来一台服务器需要处理的请求,在多台服务器上同时运行一套系统,因此可以同时处理大量并发的用户请求。
有点三个臭皮匠顶个诸葛亮的意思,因此对集群中单个服务器的硬件要求也会降低。此时需要注意负载均衡均衡的算法,例如轮询和加权轮询。
我们要保证用户请求能够均匀分布到服务器上面,同一个会话的请求保证在同一个服务器上面处理,针对不同服务器资源的优劣动态调整流量。
负载均衡器加入之后,由于其位于互联网与应用服务器之间,负责用户流量的接入,因此可以对用户流量进行监控,同时对访问用户的身份和权限进行验证。
数据库读写分离
加入缓存可以解决部分热点数据的读取,但缓存数据的容量有限,那些非热点的数据依旧会从数据库中读取。数据库对于写入和读取的性能是不一样的。
在写入数据的时候,会造成锁行或者锁表,此时如果有其他写入操作并发执行,会存在排队现象。
而读取操作比写入操作更加快捷,并且可以通过索引、数据库缓存等方式实现。
因此,推出了数据库读写分离的方案,其架构图如图 5 所示:
图 5:数据库读写分离
此时设置了主从数据库,主库(master)主要用来写入数据,然后通过同步 binlog 的方式,将更新的数据同步到从库(slave)中。
对于应用服务器而言,在写数据的时候只需要访问主库,在读数据的时候只用访问从库就好了。
利用数据库读写分离的方式,将数据库的读/写职责分离。利用读数据效率较高的优势,扩展更多的从库,从而服务于读取操作的用户请求。毕竟在现实场景中,大多数操作都是读取操作。
此外,从数据同步技术的角度来说,又可以分为同步复制技术、异步复制技术和半同步复制技术。在数据库读写分离带来益处的同时,架构也需要考虑可靠性的问题。
例如,主库如果挂掉,从库如何接替主库进行工作。主库在恢复以后,是成为从库还是继续担当主库,以及如何同步数据的问题。
反向代理与 CDN
随着互联网的逐渐普及,人们对网络安全和用户体验的要求也越来越高。之前用户都是通过客户端直接访问应用服务器获取服务,应用服务器会暴露在互联网中,容易遭到攻击。
如果在应用服务器与互联网之间加上一个反向代理服务器,它接收用户的请求,然后再转发到内网的应用服务器,充当外网与内网之间的缓冲。
反向代理服务器只是做请求的转发,在它上面没有运行任何应用,因此当有人攻击它的时候,是不会影响到内网的应用服务器的。
这无形中保护了应用服务器,提高了安全性。同时,它也在互联网与内网之间起到适配和网速转换的作用。
例如,应用服务器需要服务公网和教育网,但是两个网络的网速不同,可以在应用服务器与互联网之间放上两台反向代理服务器,一台连接公网,另一台连接教育网,屏蔽网络差异,服务于更多的用户群体。
如图 6,公网客户端和校园网客户端分别来自公网与校园网两个不同的网络:
图 6:加入反向代理服务器
由于两个网络访问速度不同,因此会针对两个网络分别设置共网代理服务器和校园网代理服务器,通过这种方式将位于不通网络的用户请求接入到系统中。
聊完反向代理,再来说说 CDN,它的全称是 Content Delivery Network,也就是内容分发网络。
如果把互联网想象成一张大网的话,每个服务器或者客户端就是分布式在网中的一个节点。
节点之间的距离有远有近,用户请求会从一个节点跳转到另外一个节点,最终跳转到应用服务器获取信息。
如果跳转的次数越少,就能够更快地获取信息,因此可以在离客户端近的节点存放信息。
这样用户通过客户端,只需要较少的跳转次数就能够触达信息。由于这部分信息更新频率不高,推荐存放一些静态数据,例如 JavaScript 文件、静态的 HTML、图片文件等。
这样客户端就可以从离自己最近的网络节点获取资源,大大提高了用户体验和传输效率。
加入 CDN 后的架构图如图 7 所示:
*图 7:*加入 CDN
CDN 的加入明显加快了用户访问应用服务器的速度,同时也减轻了应用服务器的压力,原来必须直接访问应用服务器的请求,不用经过层层网络,而只用找到最近的网络节点就可以获取资源。
但从请求资源的角度上来看,这种方式也有局限性,它只能对静态资源起作用,需要定时对 CDN 服务器进行资源更新。反向代理和 CDN 的加入解决了安全性、可用性和高性能的问题。
分布式数据库与分表分库
经历前面几个阶段以后,软件的系统架构相对趋于稳定。随着系统运行时间的增加,数据库中累积的数据越来越多,同时系统还会记录一些过程数据,例如操作数据和日志数据,这些数据也会加重数据库的负担。
即便数据库设置了索引和缓存,但在海量数据查询的时候还会捉襟见肘。如果说读写分离,是将数据库从读写层面进行资源分配,那么分布式数据库就需要从业务和数据层面对资源进行分配。
**①对于数据表来说,**当表中包含的记录过多时,会将其分成多张表来存储。
例如:有 1000 万个会员记录,就可以将其分成两个 500 万,分别放到两个表中存储。
也可以按照业务将表中的列进行分割,把表中的某些列放到其他表中存储,然后通过外键关联到主表,被分割出去的列通常是不经常访问的数据。
**②对于数据库来说,**每个数据库能够承受的最大连接数和连接池是有上限的。为了提高数据访问效率,会根据业务需求对数据库进行分割,让不同的业务访问不同的数据库。当然,也可以将相同业务的不同数据放到不同的库中存储。
如果将这些数据库资源分别放到不同的数据库服务器中,就是分布式数据库设计了。
由于数据存储在不同的表/库中,甚至在不同的服务器上面,在进行数据库操作的时候会增加代码的复杂度。此时可以加入数据库中间件来消除这些差异。
图 8:分布式数据库与分表分库
架构如图 8 所示,将数据拆分以后分别放在表 1 和表 2 中,两张表所在的数据库服务器也各不相同,库与库之间还需要考虑数据同步的问题。
由于数据的分散部署,要从业务应用获取数据就需要依靠数据库中间件帮忙。
数据库的分表分库以及分布式设计,会带来性能的提升,同时也增大了数据库管理和访问的难度。原来只用访问一张表和一个库,现在需要跨越多张表和多个库。
从软件编程的角度来看,有一些数据库中间件提供了最佳实践,例如 MyCat 和 Sharding JDBC。
此外,从数据库服务器管理的角度来看,需要监控服务器的可用性。从数据治理的角度来看,需要考虑数据扩容和数据治理的问题。
业务拆分
当解决大数据量存储问题以后,系统就能够存储更多的数据,这意味着能够处理更多的业务。
业务量的增加,访问数的上升,是任何一个软件系统在任何时期都要面临的严峻考验。
通过前面几个阶段的学习,我们知道系统提升基本依靠空间换取时间,使用更多的资源和空间处理更多的用户请求。
随着业务的复杂度越来越高,以及高并发的来临,一些大厂开始将业务系统进行切分,分开部署。
此时的架构图如图 9 所示:
图 9:业务拆分
如果说前面的服务器集群模式是将同一个应用复制到不同的服务器上,那么业务拆分就是将一个应用拆成多个部署到不同的服务器中。
此外,还可以对核心应用进行水平扩展,将其部署到多台服务器上。应用虽然做了拆分,但应用之间仍旧有关联,存在应用之间的调用、通信和协调问题。
由此也会引入队列、服务注册发现、消息中心等中间件,它们可以协助系统管理分布到不同服务器、网络节点上的应用。
业务拆分以后会形成一个个应用服务,既有基于业务的服务,例如商品服务、订单服务,也有基础服务,例如消息推送和权限验证。
这些应用服务连同数据库服务器分布在不同的容器、服务器、网络节点中,对它们的通信、协调、管理和监控都是我们需要解决的问题。
分布式与微服务
近几年,微服务是比较火的架构方式,它将业务应用进行更加精细化的切割,使之成为更加小的业务模块。做到模块的高内聚低耦合,每个模块可以独立存在,由独立的团队维护。每个模块内部可以采取特有的技术,而不用关心其他模块的技术实现。模块通过容器的部署运行,模块之间通过接口和协议进行调用。任何一个模块都可以将自己公开给其他的模块调用。同时可以将热点模块进行水平扩展,增强系统的性能。当其中某一个模块出现问题时,又可以由其他相同的模块代替其工作,增强了可用性。
大致总结下来,微服务拥有以下特点,业务精细化拆分、自治性、技术异构性、高性能、高可用。它像极了分布式架构,下面来看看它们的区别,如图 10 所示:
图 10:分布式与微服务的区别
从概念上理解,它们都做了“拆”的动作,但在下面这几个方面存在区别:
拆分目的不同:分布式设计是为了解决单体应用资源有限的问题,在一个服务器上无法支撑更高的用户访问,因此将一个应用拆解成不同的部分,然后将其部署到不同服务器上,从而分担高并发的压力。微服务是对服务组件进行精细化,目的是更好地解耦,让服务之间通过组合完成高性能、高可用、可伸缩、可扩展。
拆分方式不同:分布式服务架构将系统按照业务和技术分类进行拆分,目的是让拆分的服务负载原来单一服务的业务。微服务则是在分布式的基础上进行更细的拆分,它将服务拆成更小的模块,更加专业化,分工更加精细,并且每个小模块都可以独立运行。
部署方式不同:**分布式将服务拆分以后,通常会部署到不同的服务器上。而微服务也可以将不同的服务模块放到不同的服务器上,同时它也可以在一个服务器上部署多个微服务,或者同一个微服务的多个备份,并且多使用容器的方式部署。
虽然分布式与微服务有以上区别,但从实践的角度来看,它们都是基于分布式架构的思想构建的。微服务是分布式的进化版本,也是分布式的子集。它同样会遇到服务拆分、服务通信、协同、管理调度等问题。
总结
本文按照技术跟随业务变化的思路,描述从单体架构到集群,再到分布式架构以及微服务的发展阶段,讲述了每个软件架构阶段变化的特点,前后架构更替的原因和关系,说明了软件架构发展会一直随着业务发展的方向变化。遵循高性能,高可用,伸缩性,扩展性,安全性的架构目的。