大型系统常用的技术方案和技术手段
1. 高性能架构方案
1.1 前端性能优化
1.1.1 浏览器访问优化,浏览器访问优化是一种让用户感觉加载更快的方案,包括浏览器的请求、加载、渲染整个端到端的流程,主要优化手段有减少HTTP请求、使用浏览器缓存、启用压缩、CSS优化、减少Cookie传输等。
1.1.2 CDN加速,CDN的本质是一个缓存,将数据存放到离用户最近的地方,使用户以最快的速度获取数据。CDN一般缓存一些静态资源,如图片、文件、CSS、Script脚本、静态网页等,这些文件变化很小但访问频度很高,将其缓存在CDN可极大改善网页的打开速度。
1.1.3 反向代理,反向代理既可以保障系统服务器的安全,也可以通过配置缓存功能加速web请求,当用户第一次访问静态内容的时候,静态内容就会被缓存在反向代理服务器上,这样当其他用户访问该静态内容的时候,就可以直接从反向代理服务器返回,加速Web请求响应速度,减轻Web服务器负责压力。此外,反向代理也可以实现负责均衡,提升系统总体处理能力,进而改善系统高并发情况下的性能。
1.2 应用服务性能优化
1.2.1 分布式缓存
在某些复杂的业务场景下,如需要经过复杂运算后得出的数据、读多写少的数据等单纯依靠存储系统的性能提升不够的,这个时间就可以通过缓存来进行解决。缓存就是为了弥补存储系统在这些复杂业务场景下的不足,其基本原理是将可能重复使用的数据放到内存中,一次生成、多次使用,避免每次使用都去访问存储系统。缓存虽然能够大大减轻存储系统的压力,但同时也给架构引入了更多复杂性。架构设计时如果没有针对缓存的复杂性进行处理,某些场景下甚至会导致整个系统崩溃。下面就对缓存的架构设计要点简单分析。
缓存穿透
缓存穿透是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。通常情况下有两种情况:1. 存储数据不存在;2. 缓存数据生成耗费大量时间或者资源;
缓存雪崩
缓存雪崩是指当缓存失效(过期)后引起系统性能急剧下降的情况。当缓存过期被清除后,业务系统需要重新生成缓存,因此需要再次访问存储系统,再次进行运算,这个处理步骤耗时几十毫秒甚至上百毫秒。而对于一个高并发的业务系统来说,几百毫秒内可能会接到几百上千个请求。由于旧的缓存已经被清除,新的缓存还未生成,并且处理这些请求的线程都不知道另外有一个线程正在生成缓存,因此所有的请求都会去重新生成缓存,都会去访问存储系统,从而对存储系统造成巨大的性能压力。这些压力又会拖慢整个系统,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。
缓存雪崩的常见解决方法有两种:更新锁机制和后台更新机制。
缓存热点
缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。以微博为例,对于粉丝数超过 100 万的明星,每条微博都可以生成 100 份缓存,缓存的数据是一样的,通过在缓存的 key 里面加上编号进行区分,每次读缓存时都随机读取其中某份缓存。
1.2.2 异步操作
高性能架构设计主要集中在两方面:尽量提升单服务器的性能,将单服务器的性能发挥到极致。如果单服务器无法支撑性能,设计服务器集群方案。除了以上两点,最终系统能否实现高性能,还和具体的实现及编码相关。下面就介绍几种通过异步操作提升系统性能的方式。
Reactor
PPC 模式最主要的问题就是每个连接都要创建进程(为了描述简洁,这里只以 PPC 和进程为例,实际上换成 TPC 和线程,原理是一样的),连接结束后进程就销毁了,这样做其实是很大的浪费。为了解决这个问题,一个自然而然的想法就是资源复用,即不再单独为每个连接创建进程,而是创建一个进程池,将连接分配给进程,一个进程可以处理多个连接的业务。引入资源池的处理方式后,会引出一个新的问题:进程如何才能高效地处理多个连接的业务?当一个连接一个进程时,进程可以采用“read -> 业务处理 -> write”的处理流程,如果当前连接没有数据可以读,则进程就阻塞在 read 操作上。这种阻塞的方式在一个连接一个进程的场景下没有问题,但如果一个进程处理多个连接,进程阻塞在某个连接的 read 操作上,此时即使其他连接有数据可读,进程也无法去处理,很显然这样是无法做到高性能的。解决这个问题的最简单的方式是将 read 操作改为非阻塞,然后进程不断地轮询多个连接。这种方式能够解决阻塞的问题,但解决的方式并不优雅。首先,轮询是要消耗 CPU 的;其次,如果一个进程处理几千上万的连接,则轮询的效率是很低的。
Proactor
Reactor 是非阻塞同步网络模型,因为真正的 read 和 send 操作都需要用户进程同步操作。这里的“同步”指用户进程在执行 read 和 send 这类 I/O 操作的时候是同步的,如果把 I/O 操作改为异步就能够进一步提升性能,这就是异步网络模型 Proactor。Proactor 中文翻译为“前摄器”比较难理解,与其类似的单词是 proactive,含义为“主动的”,因此我们照猫画虎翻译为“主动器”反而更好理解。Reactor 可以理解为“来了事件我通知你,你来处理”,而 Proactor 可以理解为“来了事件我来处理,处理完了我通知你”。这里的“我”就是操作系统内核,“事件”就是有新连接、有数据可读、有数据可写的这些 I/O 事件,“你”就是我们的程序代码。
消息队列
消息队列是最古老的中间件之一,消息队列最常被使用的三种场景:异步处理、流量控制和服务解耦。当然,消息队列的适用范围不仅仅局限于这些场景,还有包括:作为发布 / 订阅系统实现一个微服务级系统间的观察者模式;连接流计算任务和数据;用于将消息广播给大量接收者等。简单的说,在单体应用里面需要用队列解决的问题,在分布式系统中大多都可以用消息队列来解决。
1.2.3 负载均衡
硬件总会有一个性能天花板,当单服务器的性能无法满足业务需求时,就需要设计高性能集群来提升系统整体的处理性能。高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法。对于任务分配器,就是“负载均衡器”。常见的负载均衡系统包括 3 种:DNS 负载均衡、硬件负载均衡和软件负载均衡。
DNS 负载均衡
DNS 是最简单也是最常见的负载均衡方式,一般用来实现地理级别的均衡。DNS 方式实现简单、成本低,但也存在粒度太粗、负载均衡算法少等缺点。
硬件负载均衡
硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。目前业界典型的硬件负载均衡设备有两款:F5 和 A10。
软件负载均衡
软件负载均衡通过负载均衡软件来实现负载均衡功能,常见的有 Nginx 和 LVS,其中 Nginx 是软件的 7 层负载均衡,LVS 是 Linux 内核的 4 层负载均衡。4 层和 7 层的区别就在于协议和灵活性,Nginx 支持 HTTP、E-mail 协议;而 LVS 是 4 层负载均衡,和协议无关,几乎所有应用都可以做,例如,聊天、数据库等。
1.3 数据库性能优化
1.3.1 读写分离
不管是为了满足业务发展的需要,还是为了提升自己的竞争力,关系数据库厂商(Oracle、DB2、MySQL 等)在优化和提升单个数据库服务器的性能方面也做了非常多的技术优化和改进。但业务发展速度和数据增长速度,远远超出数据库厂商的优化速度,尤其是互联网业务兴起之后,海量用户加上海量数据的特点,单个数据库服务器已经难以满足业务需要,必须考虑数据库集群的方式来提升性能。
高性能数据库集群的第一种方式是“读写分离”,其本质是将访问压力分散到集群中的多个节点,但是没有分散存储压力;第二种方式是“分库分表”,既可以分散访问压力,又可以分散存储压力。
读写分离的基本原理是将数据库读写操作分散到不同的节点上。基本实现是:
数据库服务器搭建主从集群,一主一从、一主多从都可以。
数据库主机负责读写操作,从机只负责读操作。
数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
业务服务器将写操作发给数据库主机,将读操作发给数据库从机。
读写分离的实现逻辑并不复杂,但有两个点将引入设计复杂度:主从复制延迟和分配机制。
复制延迟
以 MySQL 为例,主从复制延迟可能达到 1 秒,如果有大量数据同步,延迟 1 分钟也是有可能的。主从复制延迟会带来一个问题:如果业务服务器将数据写入到数据库主服务器后立刻(1 秒内)进行读取,此时读操作访问的是从机,主机还没有将数据复制过来,到从机读取数据是读不到最新数据的,业务上就可能出现问题。
分配机制
将读写操作区分开来,然后访问不同的数据库服务器,一般有两种方式:程序代码封装和中间件封装。程序代码封装指在代码中抽象一个数据访问层(所以有的文章也称这种方式为“中间层封装”),实现读写操作分离和数据库服务器连接的管理。中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。中间件对业务服务器提供 SQL 兼容的协议,业务服务器无须自己进行读写分离。
1.3.2 分库分表
读写分离分散了数据库读写操作的压力,但没有分散存储压力,当数据量达到千万甚至上亿条的时候,单台数据库服务器的存储能力会成为系统的瓶颈,为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上。常见的分散存储的方法“分库分表”,其中包括“分库”和“分表”两大类。
业务分库
业务分库指的是按照业务模块将数据分散到不同的数据库服务器。例如,一个简单的电商网站,包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上,而不是将所有数据都放在一台数据库服务器上。
分表
将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
单表数据拆分有两种方式:垂直分表和水平分表。
1.3.3 NoSQL数据库
关系数据库经过几十年的发展后已经非常成熟,强大的 SQL 功能和 ACID 的属性,使得关系数据库广泛应用于各式各样的系统中,但随着数据量的持续增长,关系数据库存在如下缺点。
关系数据库存储的是行记录,无法存储数据结构
关系数据库的 schema 扩展很不方便
关系数据库在大数据场景下 I/O 较高
关系数据库的全文搜索功能比较弱
针对上述问题,分别诞生了不同的 NoSQL 解决方案,常见的 NoSQL 方案分为 4 类。
K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表。
文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。
列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表。
全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。
世上没有免费的午餐,NoSQL 方案带来的优势,本质上是牺牲 ACID 中的某个或者某几个特性,因此我们不能盲目地迷信 NoSQL 是银弹,而应该将 NoSQL 作为 SQL 的一个有力补充,NoSQL != No SQL,而是 NoSQL = Not Only SQL。
2. 高可用架构方案
2.1 双机架构
2.2 集群和分区
2.3 异地多活
3. 伸缩性架构方案(可扩展)
3.1 分层架构
3.2 微服务
评论