接下来介绍的是大型系统的设计架构。笔者会站在系统演进的角度,逐步介绍大型系统在发展史上遇到的一些经典问题,并给出各种问题的解决方案。读者可以通过本节的介绍理解架构演进的缘由。
对于大型项目来说,为了保证较高的性能,至少需要3台服务器:应用服务器、数据库服务器和文件服务器。并且不同类型的服务器对硬件的需求也各不相同。例如,应用服务器主要是处理业务逻辑,因此需要强大的CPU支持;数据库服务器的性能依赖于磁盘检索的速度和数据缓存的容量,因此需要速度较快的硬盘(如固态硬盘)和大容量的内存;文件服务器主要是储存文件,因此需要大容量的硬盘。
现在,虽然将服务器分成了应用服务器、数据库服务器和文件服务器,但是每台子服务器仅有一台,并且单一应用服务器能够处理的请求连接是有限的。例如,单个Tomcat最佳的并发量是250左右,如果并发的请求大于250就要考虑搭建Tomcat集群(可以使用Docker快速搭建)。使用集群除了能解决负载均衡外,还有另一个优势:失败迁移。当某一台应用服务器宕机时,其他应用服务器可以继续处理客户请求。
集群有了,是否就适合存放全部数据并能处理全部请求了呢?还可以进一步优化为“动静分离”。
静:如果是观看电视剧、电影、微视频、高清图片等大文件的静态请求,最好使用CDN将这些静态资源部署在各地的边缘服务器上,使用户可以就近获取所需内容,以降低网络拥塞,提高用户访问响应的速度。如果是HTML、CSS、JS或小图片等小文件的静态资源,就可以直接缓存在反向代理服务器上,当用户请求时直接给予响应。还可以用Webpack 等工具将CSS、JavaScript、HTML进行压缩、组合,并将多个图片合成一张雪碧图,最终将静态资源统一打包,从而减少占用前端资源,并减少对静态资源的请求次数。
动:如果是搜索、增、删、改等动态请求,就需要访问我们自己编写并搭建的应用服务器。
之后,如果并发数继续增加、项目继续扩大,就可以使用分布式对项目进行拆分。
分布式系统可以理解为将一个系统拆分为多个模块并部署到不同的计算机上,然后通过网络将这些模块进行整合,从而形成的一个完整系统。在实际应用时,分布式系统包含了分布式应用、分布式文件系统和分布式数据库等类型,具体介绍如下。
分布式应用:将项目根据业务功能拆分成不同的模块,各个模块之间再通过Dubbo、SpringCloud等微服务架构或SOA技术进行整合。像这种根据业务功能,将项目拆分成多个模块的方式,也称为垂直拆分。此外,也可以对项目进行水平拆分,例如,可以将项目根据三层架构拆分成View层、Service层、Dao层等,然后将各层部署在不同的机器上。
分布式文件系统:将所有文件分散存储到多台计算机上。
分布式数据库系统:将所有数据分散存储到多台计算机上(后文中会介绍“使用Oracle搭建分布式数据库”)。
分布式系统搭建完毕后,接下来可能遇到的问题是如何提高数据的访问速度。一种解决方案就是在应用服务器上使用缓存。缓存分为本地缓存和远程缓存。本地缓存的访问速度最快,但是本地机器的内存容量、CPU能力等有限。远程缓存就是指用于缓存分布式和集群上的数据,可以无限制地扩充内存容量、CPU能力,但是远程缓存需要进行远程IO操作,因此缓存的速度比本地缓存慢。
如果缓存没有命中,就需要直接请求数据库,当请求量较大时,就需要对数据库进行读写分离,并且用主从同步对数据库进行热备份。如果是海量数据,除了可以用关系型数据库搭建分布式数据库外,还可以使用Redis、Hbase等NoSQL数据库。相应地,在处理海量数据时,也推荐使用ElasticSearch或Lucene等搜索引擎,并用Hadoop、Spark、Storm等大数据技术进行处理。
值得一提的是,在微服务架构中,数据库的使用比较灵活,每个微服务都可以有自己独立的数据库,或者多个微服务之间也可以交叉使用或共享同一份数据库。
最后还要注意,各种语言都有自己擅长的一面,例如,Java更适合开发大型网站,Spark更适合处理实时运算,Python更适合人工智能等方面。因此在很多大型公司中,不同部门开发的服务可能是基于不同的编程语言,那么此时就可以使用Thrift、gRPC等RPC技术将这些服务进行整合。
但要注意,使用RPC会给整个系统增加一层跨语言的中转结构,因此必然会带来一定程序的性能损耗。一般来讲,进行RPC跨语言调用是一种不得已而为之的做法。