「大型网站架构设计」—— 大型网站核心架构要素

「大型网站架构设计」—— 大型网站核心架构要素

Posted by tomas家的小拨浪鼓 on October 15, 2019

「大型网站架构设计」—— 大型网站核心架构要素

本文主要是笔者对《大型网站技术架构》一书的总结归纳。主要通过两种方式展现,一是通过「思维导图」的形式输出;另一种,就是本文以图文的形式更加详细和展开的描述‘大型网站技术架构’的方方面面。


本文是「大型网站架构设计」系列文章的第二篇,相关文章见:
大型网站技术架构 ——「思维导图」
「大型网站架构设计」—— 前言
「大型网站架构设计」—— 大型网站核心架构要素
「大型网站架构设计」—— 网站性能测试
「大型网站架构设计」—— 网站运行监控


二,大型网站核心架构要素

一般来说,除了当前的系统功能需求外,软件架构还需要关注:

  • 性能
  • 可用性
  • 伸缩性
  • 扩展性
  • 安全性

2.1 性能

优化网站性能的手段也非常多,根据网站分层架构,可分为:
① Web 前端性能优化
② 应用服务器性能优化
③ 数据库服务器性能优化


2.1.1 Web 前端性能优化

主要优化手段有:

  • 优化浏览器访问
  • 使用反向代理
  • CDN
1. 浏览器访问优化

① 减少 http 请求
减少 HTTP 的主要手段是合并 CSS、合并 JavaScript、合并图片。将浏览器一次访问需要的 JavaScript、CSS 合并成一个文件,这样浏览器就只需要一次请求。

② 使用浏览器缓存
对一个网站而言,CSS、JavaScript、Logo、图标这些静态资源文件更新的频率都比较低,而这些文件又几乎是每次 HTTP 请求都需要的,如果将这些文件缓存在浏览器中,可以极好地改善性能。通过设置 HTTP 头中 Cache-Control 和 Expires 的属性,可设定浏览器缓存,缓存时间可以是数天,甚至是几个月。

使用浏览器缓存策略的网站在更新静态资源时,应采用逐量更新的方法,以免用户浏览器突然大量缓存失效,集中更新缓存,造成服务器负载骤增、网络堵塞的情况。

③ 启用压缩
在服务器端对文件进行压缩,在浏览器端对文件解压缩,可有效减少通信传输的数据量。文本文件的压缩率可达 80% 以上。但是压缩对服务器和浏览器产生了一定的压力,在通信带宽良好,而服务器资源不足的情况下要权衡考虑。

④ CSS 放在页面最上面、JavaScript 放在页面最下面
浏览器会在下载完全部 CSS 之后才对整个页面进行渲染,因此最好的做法是将 CSS 放在页面最上面,让浏览器尽快下载 CSS。JavaScript 则相反,浏览器在加载 JavaScript 后立即执行,有可能会阻塞整个页面,造成页面显示缓慢,因此 JavaScript 最好放在页面最下面,但如果页面解析时就需要用到 JavaScript,这时放在底部就不合适了。

⑤ 减少 Cookie 传输
Cookie 包含在每次请求和响应中,可以考虑避免请求静态资源时发送 Cookie,减少 Cookie 传输的次数。


2. CDN 加速

CDN(Content Distribute Network,内容分发网络),即内容分发网络,部署在距离终端用户最近的网络服务商,用户的网络请求总是先到达他的网络服务商那里,在这里缓存网站的一些静态资源(较少变化的数据),可以就近以最快速度返回给用户。

CDN的本质仍然是一个缓存,而且将数据缓存在离用户最近的地方,使用户以最快速度获取数据,即所谓网络访问第一跳。

利用CDN的网站架构

CDN 能够缓存的一般是静态资源。


3. 反向代理

反向代理属于网站前端架构的一部分,部署在网站的前端,当用户请求到达网站的数据中心时,最先访问到的就是反向代理服务器,这里缓存网站的静态资源,无需将请求继续转发给应用服务器就能返回给用户。

传统代理服务器(即,正向代理。如,翻墙)位于浏览器一侧,代理浏览器将 HTTP 请求发送到互联网上,而反向代理服务器位于网站机房一侧,代理网站 Web 服务器接受 HTTP 请求。

利用反向代理的网站架构

反向代理的作用就比较多了,这里简单列举一下:
a)保护和隐藏原始资源服务器
b)加密和SSL加速
c)负载均衡
d)缓存静态内容
e)压缩
f)减速上传
g)安全
h)外网发布

和传统代理服务器可以包含浏览器安全一样,反向代理服务器也具有保护网站安全的作用。

除了安全功能,代理服务器也可以通过配置缓存功能加速 Web 请求。静态内容通常会被缓存在反向代理服务器上,事实上,有些网站也会把动态内容页缓存在代理服务器上,当这些动态内容有变化时,通过内部通知机制通知反向代理缓存失效,反向代理会重新加载最新的动态内容再次缓存起来。

此外,反向代理也可以实现负载均衡的功能。

  • CDN 和 反向代理 的基本原理都是缓存,区别在于:
    CDN 部署在网络提供商的机房,使用户在请求网站服务时,可以从距离自己最近的网络提供商机房获取数据;
    而反向代理则部署在网站的中心机房,当用户请求到达中心机房后,首先访问的服务器是反向代理服务器,如果方向代理服务器中缓存这用户请求的资源,就将其直接返回给用户。

“反向代理”的目的和‘数据缓存’也非常的相似。但‘反向代理’是请求级别的缓存;而’数据缓存’是数据库数据单位级别的缓存。


2.1.2 应用服务器性能优化

应用服务器性能优化手段主要有:

  • 缓存;
  • 集群;
  • 异步;
  • 代码优化
1. 缓存

可以使用服务器本地缓存和分布式缓存,通过缓存在内存中的热点数据处理用户请求,加快请求处理过程,减轻数据库负载压力。

本地缓存:在应用服务器本地缓存着热点数据,应用程序可以在本机内存中直接访问数据,而无需访问数据库。

分布式缓存:大型网站的数据量非常庞大,即使只缓存一小部分,需要的内存空间也不是单机能承受的,所以除了本地缓存,还需要分布式缓存,将数据缓存在一个专门的分布式缓存集群中,应用程序通过网络通信访问缓存数据。

网站性能优化第一定律:优先考虑使用缓存优化性能。

  • 缓存的基本原理
    缓存的本质是一个内存 Hash 表。Hash 表数据读写的时间复杂度为 O(1)。
    缓存主要用来存放那些读写比很高、很少变化的数据。
    网站数据访问通常遵循二八定律,即 80% 的访问落在 20% 的数据上。

  • 合理使用缓存

    a)频繁修改的数据
    一般来说,数据的读写比在 2:1 以上,即写入一次缓存,在数据更新前至少读取两次,缓存才有意义。实践中,这个读写比通常非常高。

    b)没有热点的访问
    如果应用系统访问数据没有热点,不遵循二八定律,即大部分数据访问并没有集中在小部分数据上,那么缓存就没有意义,因为大部分数据还没有被再次访问就已经被挤出缓存了。

    c)数据一致性与脏读
    一般会对缓存的数据设置失效时间,一旦超过失效时间,就要从数据库中重新加载。因此应用要容忍一定时间的数据不一致。
    还有一种策略是数据更新时立即更新缓存,不过这也会带来更多系统开销和事务一致性的问题。

    d)缓存可用性
    当缓存服务崩溃时,数据库会因为完全不能承受如此大的压力而宕机,进而导致整个网站不可用。这种情况被称作缓存雪崩,发生这种故障,甚至不能简单地重启缓存服务器和数据库服务器来恢复网站访问。

    实践中,有的网站通过缓存热备等手段提供缓存可用性。但这种设计显然有违缓存的初衷,缓存根本就不应该被当做一个可靠的数据源来使用。

    通过分布式缓存服务器集群,将缓存数据分布到集群多台服务器上可在一定程度上改善缓存的可用性。当一台缓存服务器宕机的时候,只有部分缓存数据丢失,重新从数据库加载这部分数据不会对数据库产生很大影响。

    e)缓存预热
    缓存中存放的是热点数据,热点数据又是缓存系统利用 LRU(最近最久未用算法)对不断访问的数据筛选淘汰出来的,这个过程需要花费较长的时间。新启动的缓存系统如果没有任何数据,在重建缓存数据的过程中,系统的性能和数据库负载都不太好。那么最好在缓存系统启动时就把热点数据加载好,这个缓存预加载手段叫做缓存预热(warmup)。

    f)缓存穿透
    如果因为不恰当的业务,或者恶意攻击持续高并发地请求某个不存在的数据,由于缓存没有保存该数据,所有的请求都会落到数据库上,会对数据库造成很大压力,甚至崩溃。一个简单的对策是将不存在的数据也缓存起来(其 value 值为 null)

    👆一个优化方式是,当某个不存在的值在某个时间段内被访问超过 N 次后,再将其缓存起来。以防一些业务逻辑下,可能存在较多不存在的值的请求情况。

  • 分布式缓存架构
    分布式缓存指缓存部署在多个服务器组成的集群中,以集群方式提供缓存服务,其架构方式有两种:
    ① 以 JBoss Cache 为代表的需要更新同步的分布式缓存
    ② 以 Memcached 为代表的不互相通信的分布式缓存

① JBoss Cache
JBoss Cache 的分布式缓存在集群中所有服务器中保存相同的缓存数据,当某台服务器有缓存数据更新的时候,会通知集群中其他机器更新缓存数据或清除缓存数据。
JBoss Cache 通常将应用程序和缓存部署在同一台服务器上,应用程序可从本地快速获取缓存数据。

JBoss Cache 的分布式缓存集群的缺点:
但是这种方式带来的问题是缓存数据的数量受限于单一服务器的内存空间,而且当集群规模较大的时候,缓存更新信息需要同步到集群所有机器,其代价惊人。因而这种方案更多见于企业应用系统中,而很少在大型网站使用。

需要更新同步的JBoss Cache

② Memcached
Memcached 采用一种集中式的缓存集群管理,也被称作互不通信的分布式架构方式。缓存与应用分离部署,缓存系统部署在一组专门的服务器上,应用程序通过一致性 Hash 等路由算法选择缓存服务器远程访问缓存数据,缓存服务器之间不通信,缓存集群的规模可以很容易地实现扩容,具有良好的可伸缩性。

不互相通信的Memcached


2. 集群

集群化:即多台服务器部署相同应用构成一个集群,通过负载均衡设备共同对外提供服务。

目的:提高系统的可用性。

使用集群带来的好处:
① 可以提供更好的并发特性,避免单一服务器因负载压力过大而响应缓慢,使用户请求具有更好的响应延迟特性;
② 因为一个应用由多台服务器提供,当某台服务器发生故障时,负载均衡设备或者系统的失效转移机制会将请求转发到集群中其他服务器上,使服务器故障不影响用户使用。

利用负载均衡技术改善性能


3. 异步

大型网站架构中,系统解耦的手段除了前面提到的分层、分割、分布等,还有一个重要手段是异步,业务之间的消息传递不是同步调用,而是将一个业务操作分成多个阶段,每个阶段之间通过共享数据的方式异步执行进行协作。

  • 异步的实现:
    ① 单一服务器内:可通过多线程共享内存队列的方式实现异步;
    ② 分布式系统中:多个服务器集群通过分布式消息队列实现异步。

  • 使用异步消息队列具有如下特性:
    ① 提高系统可用性,改善网站的扩展性
    ② 加快网站响应速度
    ③ 消除并发访问高峰 ———— 即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。

该方法会导致响应“延迟”。可以使用优先级队列来保证对时效性高的业务在访问高峰期的及时响应。

使用消息队列消除并发访问高峰

需要注意的是,由于数据写入消息队列后立即返回给用户,数据在后续的业务校验、写数据库等操作可能失败,因此在使用消息队列进行业务异步处理后,需要适当修改业务流程进行配合。


4. 代码层面优化

主要优化层面:

  • 多线程
  • 资源复用
  • 数据结构
  • 垃圾回收

多线程
假设服务器上执行的都是相同类型任务,针对该类任务启动的线程数有个简化的估算公式可供参考:

启动线程数 = [任务执行时间 / (任务执行时间 - IO等待事件)] * CPU 内核数

最佳启动线程数和 CPU 内核数量成正比,和 IO 等待时间成正比。如果任务都是 CPU 计算型任务,那么线程数最多不超过 CPU 内核数,因为启动再多线程,CPU 也来不及调度;相反如果是任务需要等待磁盘操作,网络响应,那么多启动线程有助于提高任务并发度,提高系统吞吐能力,改善系统性能。

编程上,解决线程安全的主要手段有如下几点:

① 将对象设计为无状态对象:

有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。其实就是有数据成员的对象。

无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。具体来说就是只有方法没有数据成员的对象,或者有数据成员但是数据成员是可读的对象。

② 使用局部变量

③ 并发访问资源时使用锁


资源复用
系统运行时,要尽量减少哪些开销很大的系统资源的创建和销毁。

从编程角度,资源复用主要有两种模式:
① 单例(Singleton)
② 对象池(Object Pool)


数据结构
早期关于程序的一个定义是,程序就是数据结构+算法,数据结构对于编程的重要性不言而喻。在不同场景中合理使用恰当的数据结构,灵活组合各种数据结构改善数据读写和计算特性可极大优化程序的性能。

以 Hash 表为例,目前比较好的字符串 Hash 散列算法有 Time33 算法,即对字符串逐字符迭代乘以 33,求得 Hash 值,算法原型为:

hash(i) = hash(i-1) * 33 + str[i]

Time33 虽然可以较好的地解决冲突,但是有可能相似字符串的 HashCode 也比较接近。
这在某些场景是不能接受的,这种情况下,一个可行的方案是对字符串取信息指纹,再对信息指纹求 HashCode,由于字符串微小的变化就可以引起信息指纹的巨大不同,因此可以获得较好的随机散列。

通过MD5计算HashCode


垃圾回收 如果 Web 应用运行在 JVM 等具有垃圾回收功能的环境中,那么垃圾回收可能会对系统的性能特性产生巨大影响。理解垃圾回收机制有助于程序优化和参数调优,以及编写内存安全的代码。


2.1.3 数据库服务器性能优化

  1. 索引、缓存、SQL 优化等性能优化手段
  2. NoSQL 数据库通过优化数据模型、存储结构、伸缩性等手段
  3. 存储性能优化


2.2 可用性

目的:高可用设计的目标就是当服务器宕机的时候,服务或者应用依然可用。

网站高可用的主要手段是冗余,应用部署在多台服务器上同时提供访问,数据存储在多台服务器上互相备份,任何一台服务器宕机都不会影响应用的整体可用,也不会导致数据丢失。


2.2.1 网站可用性度量

对于大多数网站而言:
2 个 9 是基本可用。
3 个 9 是较高可用。
4 个 9 是具有自动恢复能力的高可用。
5 个 9 是极高可用性。


2.2.2 高可用的应用

实现:位于应用层的服务器通常为了应对高并发的访问请求,会通过负载均衡设备将一组服务器组成一个集群共同对外提供服务,当负载均衡设备通过心跳检测等手段监控到某台应用服务器不可用时,就将其从集群列表中剔除,并将请求分发到集群中其他可用的服务器上,使整个集群保持可用,从而实现应用高可用。

应用层主要处理网站应用的业务逻辑,因此有时也称作业务逻辑层,应用的一个显著特点是应用的无状态性。 所谓无状态的应用是指应用服务器不保存业务的上下文信息,而仅根据每次请求提交的数据进行相应的业务逻辑处理,多个服务实例(服务器)之间完全对等,请求提交到任意服务器,处理结果都是完全一样的。

  • 通过负载均衡进行无状态服务的失效转移

对于应用服务器集群,实现这种服务器可用状态实时监测、自动转移失败任务的机制是负载均衡。

但如果需要保证该服务高可用,也必须至少部署两台服务器,使用负载均衡技术构建一个小型的集群。


  • 应用服务器集群的 Session 管理

Web 应用中将这些多次请求修改使用的上下文对象称作会话(Session)。

但事实上,业务总是有状态的。由于负载均衡可能会将请求分发到集群中任意一台服务器上,因此需要保证每次请求都能获取到正确的 Session。

「单机情况下」
Session 可由部署在服务器上的 Web 容器(如 JBoss)管理。

「集群环境下」:Session 管理主要有以下几种手段:
① Session 复制
② Session 绑定
③ 利用 Cookie 记录 Session
④ Session 服务器(推荐)

1. Session 复制

Session 复制是早期企业应用系统使用较多的一种服务器集群 Session 管理机制。应用服务器开启 Web 容器的 Session 复制功能,在集群中的几台服务器之间同步 Session 对象,使得每台服务器上都保存所有用户的 Session 信息。

使用Session复制实现应用服务器共享Session

「优点」
这种方案简单,从本机读取 Session 信息也很快速,但只能使用在集群规模比较小的情况。

「缺点」
当集群规模比较大时,集群服务器间需要大量的通信进行 Session 复制,占用服务器和网络的大量资源,系统不堪负担。而且由于所有用户的 Session 信息在每台服务器上都有备份,在大量用户访问的情况下,甚至会出现服务器内存不够 Session 使用的情况。

而大型网站的核心应用集群就是数千台服务器,同时在线用户可达千万,因此并不适用这种方案。


2. Session 绑定

Session 绑定可以利用负载均衡的源地址 Hash 算法实现,负载均衡服务器总是将来源于同一 IP 的请求分发到同一台服务器上(也可以根据 Cookie 信息将同一个用户的请求总是分发到同一台服务器上,当然这时负载均衡服务器必须工作在 HTTP 协议层上)。

这样在整个会话期间,用户所有的请求都在同一台服务器上处理,即 Session 绑定在某台特定服务器上,保证 Session 总能在这台服务器上获取。这种方法又被称作「会话粘滞」。

利用负载均衡的会话黏滞机制将请求绑定到特定服务器

但是 Session 绑定的方案显然不符合我们对系统高可用的需求。
因为一旦某台服务器宕机,那么该机器上的 Session 也就不复存在了,用户请求切换到其他机器后因为没有 Session 而无法完成业务处理。因此虽然大部分负载均衡服务器都提供源地址负载均衡算法,但是少有网站利用这个算法进行 Session 管理。


一种管理 Session 的方式是将 Session 记录在客户端,每次请求服务器的时候,将 Session 放在请求中发送给服务器,服务器处理完请求后再将修改过的 Session 响应给客户端。

网站没有客户端,但是可以利用浏览器支持的 Cookie 记录 Session。

利用Cookie记录Session信息

「缺点」
受 Cookie 大小限制,能记录的信息有限;每次请求响应都需要传输 Cookie,影响性能;如果用户关闭 Cookie,访问就会不正常。

「优点」
由于 Cookie 的简单易用,可用性高,支持应用服务器的线性伸缩,而大部分应用需要记录的 Session 信息又比较小。因此事实上,许多网站都或多或少地使用 Cookie 记录 Session。


4. Session 服务器

Session 服务器:可用性高、伸缩性好、性能也不错,对信息大小又没有限制。

利用独立部署的 Session 服务器(集群)统一管理 Session,应用服务器每次读写 Session 时,都访问 Session 服务器。

利用Session服务器共享Session

对于有状态的 Session 服务器一般有 2 种实现方式:
① 一种比较简单的方法是利用分布式缓存、数据库等,这些产品的基础上进行包装,使其符合 Session 的存储和访问要求。
② 如果业务场景对 Session 管理有比较高的要求,比如利用 Session 服务集成单点登录(SSO)、用户服务等功能,则需要开发专门的 Session 服务管理平台。


2.2.3 高可用的服务

位于服务层的服务器情况和应用层的服务器类似,也是通过集群方式实现高可用,只是这些服务器被应用层通过分布式服务调用框架访问,分布式服务调用框架会在应用层客户端程序中实现软件负载均衡,并通过服务注册中心对提供服务的服务器进行心跳检测,发现有服务不可用,立即通知客户端程序修改服务访问列表,剔除不可用的服务器。

负载均衡技术是构建大型网站必不可少的架构策略之一。它的目的是,把用户的请求分发到多台后端的设备上,用以均衡服务器的负载。我们可以把负载均衡器划分为两大类:硬件负载均衡器和软件负载均衡器。

硬件负载均衡器,常见的有NetScaler、F5、Radware和Array等,这些设备为专业的厂商开发的负载均衡器,价格比较高昂,但也提供了高可用性和高稳定性,同时还提供专业的技术服务,这些设备往往都是一些大企业(非IT类)所热衷的。因为这些企业不缺乏资金,也没有专业的it团队来开发和运维类似的负载均衡套件。

软件负载均衡器,较流行的有LVS,haproxy,nginx。这三种软件负载均衡器都为开源软件,任何个人或企业都可以无偿使用,所以对于一些小企业或者比较专业的大型IT或者互联网企业来说,使用这些软件负载均衡器成为了一种必然趋势。

具体的实践中,还有以下几点高可用的服务策略:

  1. 分级管理
    运维上将服务器进行分级管理,核心应用和服务优先使用更好的硬件,在运维响应速度上也格外迅速。
    同时在服务部署上也进行必要的隔离,避免故障的连锁反应。低优先级的服务通过启动不同的线程或者部署在不同的虚拟机上进行隔离,而高优先级的服务则需要部署在不同的物理机上,核心服务和数据甚至需要部署在不同地地域的数据中心。

  2. 超时设置
    在应用程序中设置服务调用的超时时间,一旦超时,通信框架就抛出异常,应用程序根据服务调用策略,可选择继续重试或将请求转移到提供相同服务的其他服务器上。

  3. 异步调用(根据具体情况而定,并不是所有的服务调用都可以异步调用)
    应用对服务的调用通过消息队列等异步方式完成,避免一个服务失败导致整个应用请求失败的情况。

  4. 服务降级
    在网站访问高峰期,服务可能因为大量的并发调用而性能下降,严重时可能会导致服务宕机。为了保证核心应用和功能的正常运行,需要对服务进行降级。
    降级有两种手段:
    ① 拒绝服务;
    ② 关闭服务

    • 拒绝服务
      ① 决绝低优先级应用的调用,减少服务调用并发数,确保核心应用正常使用;
      ② 或者随机拒绝部分请求调用,节约资源,让另一部分请求得以成功。(貌似 Twitter 比较喜欢用拒绝请求的策略)

    • 关闭功能
      ① 关闭部分不重要的服务
      淘宝在每年的“双十一”促销中就使用这种方法,在系统最繁忙的时段关闭“评价”、“确认收货”等非核心服务,以保证核心交易服务的顺利完成。
      ② 关闭服务内部部分不重要的功能
      以节约系统开销,为重要的服务和功能让出资源。

  5. 幂等性设计 服务层保证服务重复调用和调用一次产生的结果相同,即服务具有幂等性。


2.2.4 高可用的数据

位于数据层的服务器情况比较特殊,数据服务器上存储着数据,为了保证服务器宕机时数据不丢失,数据访问服务不中断,需要在数据写入时进行数据同步复制,将数据写入多台服务器上,实现数据冗余备份。当数据服务器宕机时,应用程序将访问切换到有备份数据的服务器上。

不同于高可用的应用和服务,由于数据存储服务器上保存的数据不同,当某台服务器宕机的时候,数据访问请求不能任意切换到集群中其他的机器上。

保证数据存储高可用的手段主要是 ① 数据备份 和 ② 失效转移机制

  • 数据备份
    数据备份是保证数据有多个副本,任意副本的失效都不会导致数据的永久丢失,从而实现数据完全的持久化。

  • 失效转移
    失效转移机制则保证当一个数据副本不可访问时,可以快速切换访问数据的其他副本,保证系统可用。

  • 关于缓存服务的高可用
    关于缓存服务的高可用,在实践中争议很大。
    ① 第一种观点认为缓存已经成为网站数据服务的重要组成部分,事实上承担了业务中绝大多数的数据读取访问服务,缓存服务失效可能会导致数据库负载过高而宕机,进而影响整个网站的可用性,因此缓存服务需要实现和数据存储服务同样的高可用。
    ② 另一种观点认为,缓存服务不是数据存储服务,缓存服务器宕机引起缓存数据丢失导致服务器负载压力过高应该通过其他手段解决,而不是提供缓存服务本身的高可用。
    对于缓存服务集群中的单机宕机,如果缓存服务器集群规模较大,那么单机宕机引起的缓存数据丢失比例和数据库负载压力变化都较小,对整个系统影响也较小。


2.2.4.1 CAP 原理

为了保证数据的高可用,网站通常会牺牲另一个也很重要的指标:数据一致性。

高可用的数据有如下几个层面的含义:
① 数据持久性
② 数据可访问性
③ 数据一致性

CAP 原理认为,一个提供数据服务的存储系统无法同时满是数据一致性(Consistency)、数据可用性(Availability)、分区耐受性/伸缩性(Partition Tolerance,系统具有跨网络分区的伸缩性)这三个条件。

在大型网站中,通常会选择强化分布式存储系统的可用性(A)和伸缩性(P),而在某种程度上放弃一致性(C)。
一般来说,数据不一致通常出现在系统高并发写操作或者集群状态不稳(故障恢复、集群扩容……)

  • 具体来说,数据一致性又可分为如下几点
    ① 数据强一致
    ② 数据用户一致
    即数据在物理存储中的各个副本的数据可能是不一致的,但是终端用户访问时,通过纠错和校验机制,可以确定一个一致的且正确的数据返回给用户。
    ③ 数据最终一致


2.2.4.2 数据备份
  1. 数据冷备
    • 优点
      冷备的优点是简单和廉价,成本和技术难度都较低。

    • 缺点
      ① 不能保证数据最终一致
      从上一个备份点开始后更新的数据会永久丢失,不能从备份中恢复。
      ② 不能保证数据可用性
      从冷备存储中恢复数据需要较长的时间,而在这段时间无法访问数据,系统也不可用。

    数据冷备作为一种传统的数据保护手段,依然在网站日常运维中使用,同时在网站实时在线业务中,还需要进行数据热备,以提供更好的数据可用性。

  2. 数据热备
    数据热备可以分为两种:
    ① 异步热备方式
    异步方式是指多份数据副本的写入操作异步完成,应用程序收到数据服务系统的写操作成功响应时,只写成功了一份,存储系统将会异步地写完其他副本(这个过程有可能会失败)
    ② 同步热备方式
    同步方式是指多份数据副本的写入操作同步完成,即应用程序收到数据服务系统的写成功响应时,多份数据都已经写操作成功。但是当应用程序收到数据写操作失败的响应时,可能有部分副本或者全部副本都已经写成功了(因为网络或者系统故障,无法返回操作成功的响应)


2.2.4.3 失效转移

失效转移:若数据服务器集群中任何一台服务器宕机,那么应用程序针对这台服务器的所有读写操作都需要重新路由到其他服务器,保证数据访问不会失败,这个过程叫作失效转移。

失效转移操作由三部分组成:
① 失效确认;
② 访问转移;
③ 数据恢复。

  1. 失效确认
    系统确认一台服务器是否宕机的手段有两种:
    ① 心跳检测
    ② 应用系统访问失败报告
    对于应用程序的访问失败报告,控制中心还需要再一次发送心跳检测进行确认,以免错误判断服务器宕机。
    存储服务器失效确认
  2. 访问转移
    确认某台数据存储服务器宕机后,就需要将数据读写访问重新路由到其他服务器上。

    对于完全对等存储的服务器(几台存储服务器存储的数据完全一样,我们称几台服务器为对等服务器,比如主从结构的存储服务器,其存储的数据完全一样),当其中一台宕机后,应用程序根据配置直接切换到对等服务器上。如果存储是不对等的, 那么就需要重新计算路由,选择存储服务器。

  3. 数据恢复
    因为某台服务器宕机,所以数据存储的副本数目就会减少,必须将副本的数目恢复到系统设定的值,否则,再有服务器宕机时,就可能出现无法访问转移(所有副本的服务器都宕机了),数据永久丢失的情况。


2.2.5 高可用网站的软件质量保证

除了运行环境,网站的高可用还需要软件开发过程的质量保证。通过预发布验证、自动化测试、自动化发布、灰度发布等手段,减少将故障引入线上环境的可能,避免故障范围扩大。

  • 网站发布
    发布过程中,每次关闭的服务器都是集群中的一小部分,并在发布完成后立即可以访问,因此整个发布过程不影响用户使用。

  • 预发布验证
    因此在网站发布时,并不是把测试通过的代码包直接发布到线上服务器,而是先发布到预发布机器上,开发工程师和测试工程师在预发布服务器上进行预发布验证,执行一些典型的业务流程,确认系统没有问题后才正式发布。

    预发布服务器是一种特殊用途的服务器,它和线上的正式服务器唯一的不同就是没有配置在负载均衡服务器上,外部用户无法访问。

    此外,在网站应用中强调的一个处理错误的理念是快速失败(fast failed),即如果系统在启动时发现问题就立刻抛出异常,停止启动让工程师介入排查错误,而不是启动后执行错误的操作。

  • 灰度发布
    大型网站会使用灰度发布模式,将集群服务器分成若干部分,每天只发布一部分服务器,观察运行稳定没有故障,第二天继续发布一部分服务器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器即可。
    网站灰度发布模型
    灰度发布也常用于用户测试,即在部分服务器上发布新版本,其余服务器保持老版本(或者发布另一个版本),然后监控用户操作行为,收集用户体验报告,比较用户对两个版本的满意度,以确定最终的发布版本。这种手段也被称作AB测试。


2.3 伸缩性

所谓网站的伸缩性是指不需要改动网站的软硬件设计,仅仅通过改变部署的服务器数量就可以扩大或缩小网站的服务处理能力

  • 衡量架构伸缩性的主要指标就是:
    ① 是否可以用多台服务器构建集群;
    ② 是否容易向集群中添加新的服务器;
    ③ 加入新的服务器后是否可以提供和原来的服务器无差别的服务;
    ④ 集群中可容纳的总的服务器数量是否有限制

  • 集群伸缩性又可分为:
    ① 应用服务器集群伸缩性;
    ② 数据服务器集群伸缩性
    数据服务器集群也可分为:a)缓存数据服务器集群;b)存储数据服务器集群。这两种集群的伸缩性设计也不大相同。

两种集群由于对数据状态管理的不同,技术实现也有非常大的区别。


2.3.1 应用服务器集群的伸缩性设计

只要服务器上不保存数据,所有服务器都是对等的,通过使用合适的负载均衡设备就可以向集群中不断加入服务器。

实现负载均衡的基础技术不外以下几种:

1. HTTP 重定向负载均衡

利用 HTTP 重定向协议实现负载均衡。

HTTP重定向负载均衡原理

HTTP 重定向服务器是一台普通的应用服务器,其唯一的功能就是根据用户的 HTTP 请求计算一台真实的 Web 服务器地址,并将该 Web 服务器地址写入 HTTP 重定向响应中(响应状态码 302)返回给用户浏览器。

「优点」
这种负载均衡方案的优点是比较简单。

「缺点」
a)浏览器需要两次请求服务器才能完成一次访问,性能较差;
b)重定向服务器自身的处理能力有可能成为瓶颈,整个集群的伸缩性规模有限;
c)使用 HTTP 302 响应码重定向,有可能使搜索引擎判断为 SEO 作弊,降低搜索排名。
因此实践中使用这种方案进行负载均衡的案例并不多见。


2. DNS 域名解析负载均衡

这是利用 DNS 处理域名解析请求的同时进行负载均衡处理的一种方案。

DNS域名解析负载均衡原理

在DNS服务器中配置多个A记录,如:www.mysite.com IN A 114.100.80.1、www.mysite.com IN A 114.100.80.2、www.mysite.com IN A 114.100.80.3。 每次域名解析请求都会根据负载均衡算法计算一个不同的 IP 地址返回,这样 A 记录中配置的多个服务器就构成了一个集群,并可以实现负载均衡。

「优点」
a)将负载均衡的工作转交给 DNS,省掉了网站管理维护负载均衡服务器的麻烦;
b)同时许多 DNS 还支持基于地理位置的域名解析,即会将域名解析成距离用户地理最近的一个服务器地址,这样可加快用户访问速度,改善性能。

「缺点」
a)目前的 DNS 是多级解析,每一级 DNS 都可能缓存 A 记录,当下线某台服务器后,即使修改了 DNS 的 A 记录,要使其生效也需要较长时间,这段时间,DNS 依然会将域名解析到已经下线的服务器,导致用户访问失败;
b)而且 DNS 负载均衡的控制权在域名服务商那里,网站无法对其做更多改善和更强大的管理。

事实上,大型网站总是部分使用 DNS 域名解析,利用域名解析作为第一级负载均衡手段,即域名解析得到的一组服务器并不是实际提供 Web 服务的物理服务器,而是同样提供负载均衡服务的内部服务器,这组内部负载均衡服务器再进行负载均衡,将请求分发到真实的 Web 服务器上。


3. 反向代理负载均衡

利用反向搭理服务器进行负载均衡

方向代理负载均衡原理

大多数反向代理服务器同时提供负载均衡的功能,管理一组 Web 服务器,将请求根据负载均衡算法转发到不同 Web 服务器上。Web 服务器处理完成的响应也需要通过反向代理服务器返回给用户。由于 Web 服务器不直接对外提供访问,因此 Web 服务器不需要使用外部 IP 地址,而反向代理服务器则需要配置双网卡和内部外部两套 IP 地址。

由于反向代理服务器转发请求在 HTTP 协议层面,因此也叫应用层负载均衡。其优点是和反向代理服务器功能集成在一起,部署简单。缺点是反向代理服务器是所有请求和响应的中转站,其性能可能会成为瓶颈。


4. IP 负载均衡

在网络层通过修改请求目标地址进行负载均衡。

IP负载均衡原理

用户请求数据包到达负载均衡服务器后,负载均衡服务器在操作系统内核进程获取请求的网络数据包,根据负载均衡算法计算得到一台真实 Web 服务器,然后将请求网络包目的 IP 修改为真是 Web 服务器 IP,这过程不需要通过用户进行处理。真实 Web 应用服务器处理完成后,响应数据包回到负载均衡服务器,负载均衡服务器再将数据包源地址修改为自身 IP 地址发送给用户浏览器。

这里的关键在于真实物理Web服务器响应数据包如何返回给负载均衡服务器。一种方案是负载均衡服务器在修改目的IP地址的同时修改源地址,将数据包源地址设为自身IP,即源地址转换(SNAT),这样Web服务器的响应会再回到负载均衡服务器;另一种方案是将负载均衡服务器同时作为真实物理服务器集群的网关服务器,这样所有响应数据都会到达负载均衡服务器。

IP 负载均衡在内核进程完成数据分发,较反向代理负载均衡(在应用程序中分发数据)有更好的处理性能。但是由于所有请求响应都需要经过负载均衡服务器,集群的最大响应数据吞吐量不得不受制于负载均衡服务器网卡带宽。


5. 数据链路层负载均衡

顾名思义,数据链路层负载均衡是指在通信协议的数据链路层修改 mac 地址进行负载均衡

数据链路层负载均衡原理

这种数据传输方式又称作三角传输模式,负载均衡数据分发过程中不修改 IP 地址,只修改目的 mac 地址,通过配置真是物理服务器集群所有机器虚拟 IP 和负载均衡服务器 IP 地址一致,从而达到不修改数据包的源地址和目的地址就可以进行数据分发的目的,由于实际处理请求的真是物理服务器 IP 和数据请求目的 IP 一致,不需要通过负载均衡服务器进行地址转换,可将响应数据包直接返回给用换浏览器(即,响应数据不需要通过负载均衡服务器),避免负载均衡服务器网卡的带宽成为瓶颈。这种负载均衡方式又称作直接路由方式(DR)。

使用三角传输模式的链路层负载均衡是目前大型网站使用最广的一种负载均衡手段。在 Linux 平台上最好的链路层负载均衡开源产品是 LVS(Linux Virtual Server)。


  • 负载均衡算法

负载均衡服务器的实现可以分为两个部分:

  1. 根据负载均衡算法和 Web 服务器列表计算得到集群中一台 Web 服务器的地址。
  2. 将请求数据发送到该地址对应的 Web 服务器上。

具体的负载均衡算法通常有以下几种:
① 轮询(Round Robin,RR)
适用于所有服务器硬件都相同的场景

② 加权轮询(Weighted Round Robin,WRR)

③ 随机(Random)
在许多场合下,这种方案都很简单实用,因为好的随机数本身就很均衡。即时应用服务器硬件配置不同,也可以使用加权随机算法。

④ 最少连接(Least Connections)
应该说,这是最符合负载均衡定义的算法。同样,最少连接算法也可以实现加权最少连接。

⑤ 源地址散列(Source Hashing)
根据请求来源的 IP 地址进行 Hash 计算,得到应用服务器,这样来自同一个 IP 地址的请求总在同一个服务器上处理,该请求的上下文信息可以存储在这台服务器上,在一个会话周期内重复使用,从而实现会话黏滞。


2.3.2 分布式缓存集群的伸缩性设计

不同于应用服务器集群的伸缩性设计,分布式缓存集群的伸缩性不能使用简单的负载均衡手段来实现。

分布式缓存服务器集群中不同服务器中缓存的数据各不相同。
缓存访问请求不可以在缓存服务器集群中的任意一台处理,必须先找到缓存有需要数据的服务器,然后才能访问。这个特点会严重制约分布式缓存集群的伸缩性设计,因为新上线的缓存服务器没有缓存任何数据,而已下线的缓存服务器还缓存着网站的许多热点数据。

加入新的服务器可能会导致缓存路由失效,进而导致集群中大部分缓存数据都无法访问。虽然缓存的数据可以通过数据库重新加载,但是如果应用已经严重依赖缓存,可能会导致整个网站崩溃。需要改进缓存路由算法保证缓存数据的可访问性。
必须让新上线的缓存服务器对整个分布式缓存集群影响最小,也就是说新加入缓存服务器后应使整个缓存服务器集群中已经缓存的数据尽可能还被访问到,这是分布式缓存集群伸缩性设计的最主要目标。


2.3.2.1 分布式缓存集群的伸缩性挑战

简单的路由算法可以使用余数 Hash。

由于 HashCode 具有随机性,因此使用余数 Hash 路由算法可以保证缓存数据在整个 Memcached 服务器集群中比较均衡地分布。

对余数 Hash 路由算法稍加改进,就可以实现和负载均衡算法中加权负载均衡一样的加权路由。 事实上,如果不需要考虑缓存服务器集群伸缩性,余数 Hash 几乎可以满足绝大多数的缓存路由需求。

  • 解决新加入缓存服务器导致缓存命中率暴跌的方案
    ① 一种解决办法是网站访问量最少的时候扩容缓存服务器集群,这时候对数据库的负载冲击最小。然后通过模拟请求的方法逐渐预热缓存,使缓存服务器中的数据重新分布。
    但是这种方案对业务场景有要求,还需要技术团队通宵加班。

    ② 通过改进路由算法,使得新加入的服务器不影响大部分缓存数据的正确命中。目前比较流行的算法是「一致性 Hash 算法」。


2.3.2.2 分布式缓存的一致性 Hash 算法

一致性 Hash 算法通过一个叫作一致性 Hash 环的数据结构实现 KEY 到缓存服务器的 Hash 映射。

一致性Hash算法原理

具体算法过程为:先构造一个长度为 2^32 的整数环(这个环被称作一致性 Hash 环),根据节点名称的 Hash 值(其分布范围为[0, 2^32 -1])将缓存服务器节点放置在这个 Hash 环上。然后根据需要缓存的数据的 KEY 值计算得到其 Hash 值(其分布范围也同样为[0, 2^32 -1]),然后在 Hash 环上顺时针查找距离这个 KEY 的 Hash 值最近的缓存服务器节点,完成 KEY 到服务器的 Hash 映射查找。

当缓存服务器集群需要扩容的时候,只需要将新加入的节点名称(NODE3)的 Hash 值放入一致性 Hash 环中,由于 KEY 是顺时针查找距离其最近的节点,因此新加入的节点只影响整个环中的一小段。

增加节点后的一致性Hash环结构

具体应用中,这个长度为 2^32 的一致性Hash环通常使用二叉查找树实现,Hash查找过程实际上是在二叉查找树中查找不小于查找数的最小数值。当然这个二叉树的最右边叶子节点和最左边的叶子节点相连接,构成环。

  • 但是,上述的算法过程还存在一个问题
    新加入的节点 NODE3 只影响了原来的节点 NODE1,也就是说一部分原来需要访问 NODE1 的缓存数据现在需要访问 NODE3(概率上是 50%)。但是原来的节点 NODE0 和 NODE2 不受影响,这就意味着 NODE0 和 NODE2 缓存数据量和负载压力是 NODE1 与 NODE3 的两倍。如果 4 台服务器的性能是一样的,那么这种结果显然不是我们需要的。

  • 解决方案
    解决上述一致性 Hash 算法带来的负载不均衡问题,也可以通过使用虚拟层的手段;将每台物理缓存服务器虚拟为一组虚拟缓存服务器,将虚拟服务器的 Hash 值放置在 Hash 环上,KEY 在环上先找到虚拟服务器节点,再得到物理服务器的信息。
    这样新加入物理服务器节点时,是将一组虚拟节点加入环中。最终的结果是:新加入一台缓存服务器,将会较为均匀地影响原来集群中已经存在的所有服务器,也就是说分摊原有缓存服务器集群中所有服务器的一小部分负载,其总的影响范围和上面讨论的相同。

显然每个物理节点对应的虚拟节点越多,各个物理节点之间的负载越均衡,新加入物理服务器对原有的物理服务器的影响越保持一致(这就是一致性 Hash 这个名称的由来)。那么在实践中,一台物理服务器虚拟为多少个虚拟服务器节点合适呢?太多会影响性能,太少又会导致负载不均衡,一般来说,经验值是 150,当然根据集群规模和负载均衡的精度需求,这个值应该根据具体情况对待。


2.3.3 数据存储服务器集群的伸缩性设计

和缓存服务器集群的伸缩性设计不同,数据存储服务器集群的伸缩性对数据的持久性和可用性提出了更高的要求。

具体来说,数据存储服务器的伸缩性设计有可以分为:
① 关系数据库集群的伸缩性设计
② NoSQL 数据库的伸缩性设计


2.3.3.1 关系数据库集群的伸缩性设计

关系数据库虽然支持数据复制,主从热备等机制,但是很难做到大规模集群的可伸缩性,因此关系数据库的集群伸缩性方案必须在数据库之外实现,通过路由分区等手段将部署有多个数据库的服务器组成一个集群。

主要从 3 个方面实现关系型数据库集群的伸缩性:
① 主从复制
② 分库
③ 分片

  • 主从复制
    市场上主要的关系数据都支持数据复制功能,使用这个功能可以对数据库进行简单伸缩。

  • 分库
    除了数据库主从读写分离,业务分割模式也可以用在数据库,不同业务数据表部署在不同的数据库集群上,即俗称的「数据分库」。
    这种方式的制约条件是跨库的表不能进行 Join 操作。

  • 分片
    在大型网站的实际应用中,即使进行了分库和主从复制,对一些单表数据仍然很大的表,比如 Facebook 的用户数据库,淘宝的商品数据库,还需要进行分片,将一张表拆开分别存储在多个数据库中。


2.3.3.2 NoSQL 数据库的伸缩性设计

NoSQL,主要指非关系的、分布式的数据库设计模式。

对于大部分NoSQL数据库产品,由于其先天就是为海量数据而生,因此其对伸缩性的支持通常都非常好。

一般而言,NoSQL 数据库产品都放弃了关系数据库的两大重要基础:
① 以关系代数为基础的结构化查询语言(SQL);
② 事务一致性保证(ACID)。

而强化其他一些大型网站更关注的特性:
① 高可用性
② 可伸缩性。


2.4 扩展性

  • 目的:
    不同于其他架构要素主要关注非功能性需求,网站的扩展性架构直接关注网站的功能需求。网站快速发展,功能不断扩展,如何设计网站的架构使其能够快速响应需求变化,是网站可扩展架构主要的目的。

  • 「扩展性」vs「伸缩性」
    • 扩展性(Extensibility)
      指对现有系统影响最小的情况下,系统功能可持续扩展或提升的能力。 表现在:
      ① 系统基础设施稳定不需要经常变更;
      ② 应用之间较少依赖和耦合
      ③ 对需求变更可以敏捷响应

    • 伸缩性(Scalability)
      指系统能够通过增加(减少)自身资源规模的方式增强(减少)自己计算处理事务的能力。如果这种增减是成比例的,就被称作线性伸缩性。在网站架构中,通常指利用集群的方式增加服务器数量、提高系统的整体事务吞吐能力。

  • 衡量网站架构扩展性好坏的主要标准:
    ① 在网站增加新业务产品时,是否可以实现对现有产品透明无影响,不需要任何改动或者很少改动既有业务功能就可以上线新产品。
    ② 不同产品之间是否很少耦合,一个产品改动对其他产品无影响,其他产品和功能不需要受牵连进行改动。

  • 网站可扩展架构的主要手段
    ① 事件驱动架构
    事件驱动架构在网站通常利用消息队列实现。
    ② 分布式服务
    分布式服务则是将业务和可复用服务分离开来,通过分布式服务框架调用。
    可复用服务升级变更的时候,也可以通过提供多版本服务对应用实现透明升级,不需要强制应用同步变更。


2.4.1 事件驱动架构

事件驱动架构(Event Driven Architecture):通过在低耦合的模块之间传输事件消息,以保持模块的松散耦合,并借助事件消息的通信完成模块间合作。 在大型网站架构中,具体实现手段有很多,最常用的是分布式消息队列。

利用消息队列实现的事件驱动架构

由于消息发送者不需要等待消息接收者处理数据就可以返回,系统具有更好的响应延迟;同时,在网站访问高峰,消息可以暂时存储在消息队列中等待消息接收者根据自身负载处理能力控制消息处理速度,减轻数据库等后端存储的负载压力。

2.4.1.2 分布式消息队列
  • 在伸缩性方面
    由于消息队列服务器上的数据可以看作是被即时处理的,因此类似于无状态的服务器,伸缩性设计比较简单。将新服务器加入分布式消息队列集群中,通知生产者服务器更改消息队列服务器列表即可。

  • 在可用性方面
    为了避免消费者进程处理缓慢,分布式消息队列服务器内存空间不足造成的问题,如果内存队列已满,会将消息写入磁盘,消息推送模块在将内存队列消息处理完以后,将磁盘内容加载到内存队列继续处理。

为了避免消息队列服务器宕机造成消息丢失,会将消息成功发送到消息队列的消息存储在消息生产者服务器,等消息真正被消息消费者处理器处理后才删除消息。


2.4.2 分布式服务

使用分布式服务是降低系统耦合性的另一个重要手段。
如果说分布式消息队列通过消息对象分解系统耦合性,不同子系统处理同一个消息;那么分布式服务器则通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务调用。

对系统进行拆分,将模块独立部署,降低系统耦合性。

  • 不同功能进行物理分离实现伸缩性
    通过物理上分离不同的网站功能,具体又可分成如下两种情况:
    ① 纵向分离(分层后分离)
    将业务处理流程上的不同部分分离部署,实现系统伸缩性。
    通过纵向分离部署实现系统伸缩性

    ② 横向分离(业务分割后分离)
    将不同的业务模块分离部署,实现系统伸缩性。
    通过横向分离部署实现系统伸缩性
    横向分离的粒度可以非常小。

  • 大型网站分布式服务的需求和特点
    ① 负载均衡
    ② 失效转移
    对于大型网站的分布式服务而言,即使是很少访问的简单服务,也需要集群部署,分布式服务框架支持服务提供者的失效转移机制,当某个服务实例不可用,就将访问切换到其他服务实例上,以实现服务整体高可用
    ③ 高效的远程通信
    ④ 整合异构系统
    异构系统:(即,不同的语言开发的服务)
    ⑤ 对应用最少浸入
    ⑥ 版本管理
    ⑦ 实时监控


2.4.2.1 分布式服务框架设计

以阿里巴巴分布式开源框架 Dubbo 为例,分析其架构设计。

分布式服务器框架Dubbo的架构原理

服务消费者程序通过服务接口使用服务,而服务接口通过代理加载具体服务,具体服务可以是本地的代码模块,也可以是远程的服务,因此对应用较少侵入:应用程序只需要调用服务接口,服务框架根据配置自动调用本地或远程实现。

服务框架客户端模块通过服务注册中心加载服务提供者列表(服务提供者启动后自动向服务注册中心注册自己可提供的服务接口列表),查找需要的服务接口,并根据配置的负载均衡策略将服务调用请求发送到某台服务提供者服务器。如果服务调用失败,客户端模块会自动从服务提供者列表选择一个可提供同样服务的另一台服务器重新请求服务,实现服务的自动失效转移,保证服务高可用。


2.5 安全性

网站的安全架构就是保护网站不受恶意访问和攻击,保护网站的重要数据不被窃取。

衡量网站安全架构的标准就是针对现存和潜在的各种攻击与窃密手段,是否有可靠的应对策略。

2.5.1 网站应用攻击与防御

网站应用攻击最主要的两种手段:
① XSS 攻击;
② SQL 注入攻击

2.5.1.1 XSS 攻击

XSS 攻击即跨站点脚本攻击(Cross Site Script),指黑客通过篡改网页,注入恶意 HTML 脚本,在用户浏览网页时,控制用户浏览器进行恶意操作的一种攻击方式。

常见的 XSS 攻击类型有两种:
① 反射型,攻击者诱使用户点击一个嵌入恶意脚本的链接,达到攻击的目的。
反射型XSS攻击

② 另一种 XSS 攻击是持久型 XSS 攻击,黑客提交含有恶意脚本的请求,保存在被攻击的 Web 站点的数据库中,用户浏览网页时,恶意脚本被包含在正常网页中,达到攻击的目的。
持久型XSS攻击

XSS 防攻击也是非常复杂的。主要手段有如下两种:
① 消毒
即对某些 html 危险字符转义,如“>”转义为“>”、“<”转义为”<”等。
事实上,消毒几乎是所有网站最必备的 XSS 防攻击手段。

② HttpOnly
最早由微软提出,即浏览器禁止页面 JavaScript 访问带有 HttpOnly 属性的 Cookie。HttpOnly 并不是直接对抗 XSS 攻击的,而是防止 XSS 攻击者窃取 Cookie。对于存放敏信息的 Cookie,如用户认证信息等,可通过对该 Cookie 添加 HttpOnly 属性,避免被攻击脚本窃取。


2.5.1.2 注入攻击

注入攻击主要有两种形式:
① SQL 注入攻击
② OS 注入攻击

  • SQL 注入攻击
    攻击者在 HTTP 请求中注入恶意 SQL 命令(drop table users;),服务器用请求参数构造数据库 SQL 命令时,恶意 SQL 被一起构造,并在数据库中执行。

攻击者获取数据库表结构信息的手段有如下几种:
a)开源
b)错误回显
如果网站开启错误回显,即服务器内部 500 错误会显示到浏览器上。
c)盲注
网站关闭错误回显,攻击者根据页面变化情况判断 SQL 语句的执行情况,据此猜测数据表结构,此种方式攻击难度较大。

  • 防御 SQL 注入攻击的几种方式
    a)消毒
    通过正则匹配,过滤请求数据中可能输入的 SQL,如“drop table”、“\b(?:update\b.*?\bset |delete\b\W*?\bfrom)\b”等。
    b)参数绑定(推荐)
    使用预编译手段,绑定参数是最好的防 SQL 注入方法。

  • OS 注入攻击
    除了 SQL 注入,攻击者还根据具体应用,注入 OS 命令、编程语言代码等,利用程序漏洞,达到攻击目的。


2.5.1.3 CSRF 攻击

CSRF(Cross Site Request Forgery,跨站点请求伪造),攻击者通过跨站请求,以合法用户的身份进行非法操作。其核心是利用了浏览器 Cookie 或服务器 Session 策略,盗取用户身份。

CSRF攻击

CSRF 的防御手段主要是识别请求者身份。主要有下面几种方法:
① 表单 Token
CSRF是一个伪造用户请求的操作,所以需要构造用户请求的所有参数才可以。表单Token通过在请求参数中增加随机数的办法来阻止攻击者获得所有请求参数:在页面表单中增加一个随机数作为Token,每次响应页面的Token都不相同,从正常页面提交的请求会包含该Token值,而伪造的请求无法获得该值,服务器检查请求参数中Token的值是否存在并且正确以确定请求提交者是否合法。

② 验证码
但是输入验证码是一个糟糕的用户体验,所以请在必要时使用

③ Referer check
HTTP 请求头的 Refere 域中记录着请求来源。可通过检查请求来源,验证其是否合法。


2.5.1.4 其他攻击和漏洞
  • Error Code
    也称作错误回显,许多 Web 服务器默认是打开异常信息输出的。
    通过故意制造非法输入,使系统运行时出错,获得异常信息,从而寻找系统漏洞进行攻击。
    防御手段也很简单,通过配置 Web 服务器参数,跳转 500 页面(HTTP 响应码 500 表示服务器内部错误)到专门的错误页面即可,Web 应用常用的 MVC 框架也有这个功能。

  • HTML 注解
    程序最终发布前需要进行代码 review 或自动扫描,避免 HTML 注释漏洞。

  • 文件上传
    最有效的防御手段是设置上传文件白名单,只允许上传可靠的文件类型。此外还可以修改文件名、使用专门的存储等手段,保护服务器免受上传文件攻击。

  • 路径遍历
    攻击者在请求的URL中使用相对路径,遍历系统未开放的目录和文件。防御方法主要是将JS、CSS等资源文件部署在独立服务器、使用独立域名,其他文件不使用静态URL访问,动态参数不包含文件路径信息。


2.5.1.5 Web 应用防火墙

如果有一款产品能够统一拦截请求,过滤恶意参数,自动消毒、添加Token,并且能够根据最新攻击和漏洞情报,不断升级对策,处理掉大多数令人头痛的网站攻击,就是一件很美妙的事了。

非常幸运,真的有这样的产品——ModSecurity。

ModSecurity采用处理逻辑与攻击规则集合分离的架构模式。处理逻辑(执行引擎)负责请求和响应的拦截过滤,规则加载执行等功能。而攻击规则集合则负责描述对具体攻击的规则定义、模式识别、防御策略等功能(可以通过文本方式进行描述)。处理逻辑比较稳定,规则集合需要不断针对漏洞进行升级,这是一种可扩展的架构设计。


2.5.1.6 网站安全漏洞扫描

网站安全漏洞扫描工具是根据内置规则,构造具有攻击性的 URL 请求,模拟黑客攻击行为,用以发现网站安全漏洞的工具。


2.5.2 信息加密计算以及密钥安全管理

信息加密技术可分为三类:
① 单向散列加密;
② 对称加密;
③ 非对称加密;

2.5.2.1 单向散列加密

单向散列加密是指通过对不同输入长度的信息进行散列计算,得到固定长度的输出,这个散列计算过程是单向,即不能对固定长度的输出进行计算从而获得输入信息。

虽然不能通过算法将单向散列密文反算得到明文,但是由于人们设置密码具有一定的模式,因此通过彩虹表(人们常用密码和对应的密文关系表)等手段可以进行猜测式破解。

为了加强单向散列计算的安全性,还会给散列算法加点盐(salt),salt 相当于加密的密钥,增加破解的难度。

常用的单向散列算法有 MD5,SHA 等。单向散列算法还有一个特点就是输入的任何微小变化都会导致输出的完全不同,这个特性有时也会被用来生成信息摘要,计算具有高度离散程度的随机数等用途。


2.5.2.2 对称加密

对称加密是指加密和解密使用的密钥是同一个密钥(或者可以互相推算)

对称加密通常用在信息需要安全交换或存储的场合,如 Cookie 加密、通信加密等。

  • 优点
    a)算法简单
    b)加密效率高
    c)系统开销小
    d)适合对大量数据加密

  • 缺点 加密使用同一个密钥,远程通信的情况下如何安全的交换密钥是个难题,如果密钥丢失,那么所有的加密信息也就没有秘密可言了。

常用的对称加密算法有 DES 算法、RC 算法等。


2.5.2.3 非对称加密

非对称加密和解密使用的密钥不是同一密钥,其中一个对外界公开,被称作公钥,另一个只有所有者知道,被称作私钥。
用公钥加密的信息必须用私钥才能解开,反之,用私钥加密的信息只有用公钥才能解开。

理论上说,不可能通过公钥计算获得私钥。

非对称加密技术通常用在信息安全传输,数字签名等场合。

在实际应用中,常常会混合使用对称加密和非对称加密。先使用非对称加密技术对对称密钥进行安全传输,然后使用对称加密技术进行信息加解密与交换。

非对称加密的常用算法有 RSA 算法等。HTTPS 传输中浏览器使用的数字证书实质上是经过权威机构认证的非对称加密的公钥。


2.5.2.4 密钥安全管理

实践中,改善密钥安全性的手段有两种。

① 一种方案是把密钥和算法放在一个独立的服务器上,甚至做成一个专用的硬件设施,对外提供加密和解密服务,应用系统通过调用这个服务,实现数据的加解密。 但是这种方案成本较高,而且有可能会成为应用的瓶颈,每次加密、解密都需要进行一次远程服务调用,系统性能开销也较大。

② 另一种方案是将加解密算法放在应用系统中,密钥则放在独立服务器中,为了提高密钥的安全性,实际存储时,密钥被切分成数片,加密后分别保存在不同存储介质中,兼顾密钥安全性的同时又改善了性能

密钥安全管理

一个密钥分片后存储在多个存储服务器中,每个服务器都有专人负责管理。密钥申请者、密钥管理者、安全审核人员通过密钥管理控制台管理更新密钥,每个人各司其事,没有人能查看完整的密钥信息。


2.5.3 信息过滤与反垃圾

常用的信息过滤与反垃圾手段有以下几种:

  • 文本匹配
  • 分类算法
  • 黑名单


2.5.3.1 文本匹配

文本匹配主要解决敏感词过滤的问题。通常网站维护一份敏感词列表,如果用户发表的信息含有列表中的敏感词,则进行消毒处理(将敏感词转义为***)或拒绝发表。

2.5.3.2 分类算法

对如此海量的信息进行人工审核是不实现的,对广告贴、垃圾邮件等内容的识别比较好的自动化方法是采用分类算法。

2.5.3.3 黑名单

对于垃圾邮件,除了用分类算法进行内容分类识别,还可以使用黑名单技术,将被报告的垃圾邮箱地址放入黑名单,然后针对邮件的发件人在黑名单列表中查找,如果查找成功,则过滤该邮件。

黑名单也可用于信息去重,如将文章标题或者文章关键段落记录到黑名单中,以减少搜索引擎收录重复信息等用途。

再对过滤需求要求不完全精确的场景下,可以使用布隆过滤器代替 Hash 表。通过一个二进制列表和一组随机数映射函数实现。

布隆过滤器

仍以需要处理10亿邮件地址黑名单列表为例,在内存中建立一个2GB大小的存储空间,即16GB个二进制bit,并全部初始化为0。要将一个邮箱地址加入黑名单时,使用8个随机映射函数(F1,F2,…,F8)得到0~16GB范围内的8个随机数,从而将该邮箱地址映射到16GB二进制存储空间的8个位置上,然后将这些位置置为1。当要检查一个邮箱地址是否在黑名单中时,使用同样的映射函数,得到16GB空间8个位置上的bit,如果这些值都为1,那么该邮箱地址在黑名单中。

但如果需要精确的判断,则不合适使用布隆过滤器。


2.5.4 电子商务风险控制

2.5.4.1 风险

风险也各有特点,大致可分为以下几种:
① 账户风险
② 买家风险
③ 卖家风险
④ 交易风险


2.5.4.2 风控

大型电商网站都配备有专门的风控团队进行风险控制,风控的手段也包括自动和人工两种。机器自动识别为高风险的交易和信息会发送给风控审核人员进行人工审核,机器自动风控的技术和方法也不断通过人工发现的新风控类型进行逐步完善。

机器自动风控的技术手段主要有:
① 规则引擎;
② 统计模型

  1. 规则引擎
    大型网站在运营过程中,结合业界的最新发现,会总结出数以千计的此类高风险交易规则。一种方案是在业务逻辑中通过编程方式使用if…else…代码实现这些规则,可想而知,这些代码会非常庞大,而且由于运营过程中不断发现新的交易风险类型,需要不断调整规则,代码也需要不断修改……

    网站一般使用规则引擎技术处理此类问题。规则引擎是一种将业务规则和规则处理逻辑相分离的技术。 业务规则文件由运营人员通过管理页面编辑,当需要修改规则时,无需更改代码发布程序,即可实时使用新规则。
    基于规则引擎的风险控制系统

  2. 统计模型
    规则引擎虽然技术简单,但是随着规则的逐渐增加,会出现规则冲突,难以维护等情况,而且规则越多,性能也越差。目前大型网站更倾向于使用统计模型进行风控。风控领域使用的统计模型是分类算法或者更复杂的机器学习算法进行智能统计。

    经过充分训练后的统计模型,准确率不低于规则引擎。分类算法的实时计算性能更好一些,由于统计模型使用模糊识别,并不精确匹配欺诈类型规则,因此对于新出现的交易欺诈还具有一定预测性。
    基于统计模型的风险控制系统