大型网站技术架构

大型网站架构演化

大型网站软件系统的特点

  • 高并发、大流量:淘宝2012年双十一活动一天交易额超过191亿,活动开始第一分钟独立访问用户达1kw。
  • 高可用:系统7×24小时不间断服务,宕机事件会成为新闻焦点,2010年百度域名被劫持导致不能访问。
  • 海量数据:需要存储、管理海量数据,需要使用大量服务器。Facebook每周上传照片数接近10亿。
  • 用户分布广泛,网络情况复杂:许多大型网站是为全球用户提供服务的,中美光缆的数次故障,让一些对国外用户依赖较大的网站不得不考虑在海外建立数据中心。
  • 安全环境恶劣:互联网的开放性,使得互联网更容易受到攻击。
  • 需求快速变更,发布频繁:Office的产品版本是以年为单位发布的,而大型网站的产品每周都有新版本发布上线,至于中小型网站的发布更频繁。
  • 渐进式发展:好的互联网产品是慢慢运营出来的,而不是一开始就开发好的,这与网站架构的发展演化过程对应,阿里巴巴是在马云家的客厅里诞生的。

大型网站架构演化发展历程

  1. 初始阶段的网站架构

    小型网站最开始没有流量,只需要一台服务器就够了。应用程序、数据库、文件等所有的资源都在一台服务器上。

  2. 应用服务和数据服务分离

    网站业务的发展,越来越多的用户访问导致性能越来越差,越来越多的数据导致空间不足,就需要将应用和数据分离,整个网站使用三台服务器:应用服务器、文件服务器、数据库服务器。应用服务器需要处理大量业务逻辑,需要更好的CPU;数据库服务器需要快速磁盘检索和数据缓存,因此需要更快的硬盘和更大的内存;文件服务器需要存储大量用户上传的文件,因此需要更大的硬盘。

  3. 使用缓存改善网站性能

    网站访问遵循二八定律:80%的业务集中在20%的数据上。使用缓存后,减少查询数据库的压力和复杂业务逻辑处理的耗时,但是单一应用服务器能够处理的请求连接有限,在网站访问高峰期,应用服务器成为整个网站的瓶颈。

  4. 使用应用服务器集群改善网站的并发处理能力

    通过负载均衡调度服务器,可将来自用户浏览器的访问请求分发到应用服务器集群中的任何一台服务器上。用户变多,只需要在集群中加入更多的应用服务器即可。

  5. 数据库读写分离

    使用缓存后,大部分读操作可以不通过访问数据库就能完成,但是仍有一部分读操作(缓存未命中、缓存过期)和全部的写操作需要访问数据库,在网站的用户达到一定规模后,数据库因为负载压力过高而成为网站的瓶颈。网站可以利用数据库主从热备功能,让应用程序在写数据库的时候访问主库,主库通过主从复制将数据更新同步到从数据库;应用读数据时访问从库。通常在应用服务器端使用专门的数据访问模块,使数据库读写分离对应用透明。

  6. 使用反向代理和CDN加速网站响应

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

  7. 使用分布式文件系统和分布式数据库系统

    分布式数据库是网站数据库拆分的最后手段,只有在单表数据规模非常庞大的时候才使用。不到不得已时,网站更常用的数据库拆分手段是业务分库,将不同业务的数据库部署在不同的物理服务器上。

  8. 使用NoSQL和搜索引擎

    随着网站的业务越来越复杂,对数据存储和检索的需求也越来越复杂,网站需要使用一些非关系数据库技术如NoSQL和非数据库查询技术如ES。

  9. 业务拆分

    整个网站业务分成不同的产品线,如大型购物交易网站将首页、商铺、订单、买家、卖家等拆分为不同的产品线,分归不同的业务团队负责。不同业务之间可以通过消息队列进行数据分发。

  10. 分布式服务

    随着业务拆分越来越少,存储系统越来越大,应用系统的整体复杂度呈指数级增加,部署维护越来越困难,因此可以将每个应用系统都需要执行许多相同的业务操作提取出来,独立部署。如百度系的独立账号系统。

大型网站架构演化的价值观

  1. 大型网站架构技术的核心价值观是随网站所需灵活应对:小型网站最需要做的是为用户提供更好的服务来创造价值,得到用户认可,活下去,野蛮生长,因此这些网站几十年如一日地使用LAMP技术(Linux+Apache+MySQL+PHP)开发自己的网站,因为LAMP既便宜又简单,对付一个中小型网站绰绰有余。
  2. 驱动大型网站技术发展的主要力量是网站的业务发展:业务发展对网站架构技术提出了新挑战,才使得网站架构技术得以发展成熟。

网站架构设计的误区

  • 一味追随大公司的解决方案:小型网站最重要的是发现自己的业务是否有价值,而不是照搬大公司那套复杂的架构。
  • 为了技术而技术:脱离业务的技术毫无意义。
  • 企图用技术解决所有问题:2012年12306故障后,各路专业和非专业人士帮12306出谋划策以解决其大规模并发访问的问题。12306真正的问题不是其技术架构,而是其业务架构,它不该在几亿人一票难求的情况下搞秒杀这种操作,换一种方式买票会更好。后来证明12306确实是朝着这个方向发展的,售票方式上引入排队机制、整点售票调整为分时段售票。其实如果能控制住并发访问的量,很多棘手的技术问题也就不是什么问题了。

大型网站架构模式

网站架构模式

  1. 分层。分层是企业应用系统中最常见的一种架构模式,将系统在横向维度上切分成几个部分,每个部分负责一部分相对单一的职责,然后通过上层对下层的依赖和调用组成一个完整的系统。
  2. 分割。分割是在纵向方面对软件进行切分,即将不同的功能和服务分割开来,包装成高内聚低耦合的模块单元,一方面有助于软件的开发和维护,另一方面便于不同模块的分布式部署,提高网站的并发处理能力和功能扩展能力。
  3. 分布式。分层和分割的一个主要目的是为了切分后的模块便于分布式部署。
    • 分布式应用和服务:将分层和分割后的应用和服务模块分布式部署,除了可以改善网站性能和并发性、加快开发和发布速度、减少数据库连接资源消耗外,还可以使不同应用复用共同的服务,便于业务功能的扩展。实际来说,展现类和用户服务类分成两个应用开发和部署。
    • 分布式静态资源:静态资源独立部署,并使用独立的域名。静态资源分布式部署可以减轻应用服务器的负载压力、加快浏览器并发加载的速度、由负责用户体验到团队进行开发维护有利于网站分工合作。
    • 分布式数据和存储:以P为单位的海量数据需要分布式存储,且为网站应用而生的NoSQL产品几乎都是分布式的。
    • 分布式计算:网站除了要处理在线业务,还需要处理一些其他的后台业务,包括搜索引擎的索引构建、数据仓库的数据分析与统计,常用分布式计算框架完成。
    • 还包括支持网站线上服务器配置实时更新的分布式配置;分布式环境下并发和协同的分布式锁;支持云存储的分布式文件系统。
  4. 集群。多台服务器部署相同的应用,通过负载均衡共同对外提供服务。
  5. 缓存。CDN、反向代理、本地缓存、分布式缓存。
  6. 异步。
  7. 冗余。服务器冗余运行、数据冗余备份(冷热备份)。
  8. 自动化。通过减少人为干预,使发布过程自动化可以有效减少故障,包括自动化代码管理、自动化测试、自动化安全检测、自动化部署。还包括自动化监控、自动化报警、自动化失效转移、自动化失效恢复、自动化降级、自动化分配资源。
  9. 安全。通过密码和验证码进行身份认证,对通信进行加密,防止网站被攻击,过滤垃圾和敏感信息,对交易转账等重要操作进行风控。

新浪微博的架构

新浪微博系统分为三层:

  1. 基础服务层。提供数据库、缓存、存储、搜索等服务。
  2. 平台服务和应用服务层。核心服务是微博、关系和用户,它们被分割为独立的服务模块,通过依赖调用和共享基础数据构成新浪微博的业务基础。
  3. API和新浪微博的业务层、各种客户端和第三方应用。

新浪早期使用同步推模式,用户发布微博后立即将该微博插入到数据库所有粉丝的订阅列表中,当用户比较大时会引起数据库的大量写操作,超出数据库负载,性能急剧降低。后来改用异步推拉结合的模式,用户发博后系统将微博写入消息队列后返回,用户响应迅速,消息队列消费者任务将微博推送给所有当前在线粉丝的订阅列表中,非在线用户登录后再根据关注列表拉取微博订阅列表。

由于微博频繁刷新,新浪使用多级缓存策略,热门微博和明星用户的微博缓存在所有服务器上,在线用户的微博和近期微博缓存在分布式缓存集群中,对于微博操作中最常见的“刷微博”操作,几乎全部是缓存访问操作,以获得良好的系统性能。

为了提高系统的整体可用性和性能,新浪微博启用多个数据中心。这些数据中心既是地区用户访问中心,用户可以就近访问以加快访问速度,也是数据冗余复制的灾备中心,所有的用户和微博数据通过远程消息系统在不同的数据中心之间同步,提高系统可用性。

大型网站核心架构要素

需Review!

性能

性能是网站的一个重要指标,用户没法忍受一个响应缓慢的网站。

衡量网站性能有一系列指标,重要的有响应时间、TPS、系统性能计数器,通过测试这些指标以确定系统设计是否达到目标。这些指标也是网站监控的重要参数,通过监控这些指标可以分析系统瓶颈,预测网站容量,并对异常指标进行报警,保障系统可用性。

可用性

一些大型网站可以做到4个9以上的可用性,也就是可用性超过99.99%。

伸缩性

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

扩展性

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

网站可扩展的手段包括事件驱动架构和分布式服务:

  • 事件驱动架构通常利用消息队列实现。将消息生产和消费分离开来,可以透明地增加新的消息生产者或新的消息消费者。
  • 分布式服务则是将业务和可复用服务分离开来,通过分布式服务框架调用。新增产品可通过复用服务实现自身业务逻辑。可复用服务升级时,可通过提供多版本服务对应用实现透明升级,不需要强制应用同步变更。

安全性

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

网站的高性能架构

在技术层面,性能优化需要全面考虑:性能提升一倍,服务器数量增加一倍;或者访问时间缩短,同时数据一致性也下降,这样的优化是否可接受?这类问题的答案不是技术团队能回答的。技术是为业务服务的,技术选型和架构决策依赖业务规划乃至企业战略规划,离开业务发展的支撑和驱动,技术走不远。

网站性能测试

不同视角下的网站性能

用户视角:网站性能是用户在浏览器上直观感受到的网站响应速度快还是慢。其感受到的时间包括网络通信时间、服务器处理时间、数据解析渲染时间。实践中,使用一些前端架构优化手段,通过优化页面HTML样式、利用浏览器的并发和异步特性、调整浏览器缓存策略、使用CDN服务、反向代理等手段,使浏览器尽快地显示用户感兴趣的内容、尽可能近地获取页面内容。

开发人员视角:开发人员关注应用程序本身及其子系统的性能,包括响应延迟、系统吞吐量、并发处理能力、系统稳定性等技术指标。主要的优化手段有使用缓存加速数据读取,使用集群提供吞吐量,使用异步消息加快请求响应及实现削峰,使用代码优化手段改善程序性能。

运维人员视角:运维人员关注基础设施性能和资源利用率,如网络运营商的带宽能力、服务器硬件的配置、数据中心网络架构、服务器和网络带宽的资源利用率。优化手段有建设优化骨干网、使用高性价比定制服务器】利用虚拟化技术优化资源利用等。

性能测试指标

  • 响应时间:指应用执行一个操作需要的时间。
  • 并发数:指系统能同时处理请求的数目,这个数字也反映了系统的负载特性。对于网站而言,并发数即同时提交请求的用户数。如果需要进行秒杀活动,则需要评估可能的用户并发数,并对系统的并发处理能力进行测试。
  • 吞吐量:指单位时间内系统处理的请求数量,体现系统的整体能力。对于网站,可以用TPS(每秒事务数)、QPS(每秒查询数)、HPS(每秒HTTP请求数)。
  • 性能计数器:描述服务器或操作系统性能的一些指标。包括System Load、对象与线程数、内存使用、CPU使用、磁盘和网络IO等指标。System Load指当前正在被CPU执行和等待被CPU执行的进程数目综合,反映系统空闲程度的指标。多核CPU情况下,完美情况是所有CPU都在使用,没有进程在等待处理,所以Load的理想值是CPU的个数。Linux的top命令表示最近1分钟、5分钟、15分钟的运行平均进程数。

在系统并发数由小增大的过程中,系统的吞吐量先是逐渐增加,达到一个极限后,随着并发数的增加反而下降,达到系统崩溃点后,系统资源耗尽,吞吐量为零;这个过程中,响应时间先是保持小幅上升,到达吞吐量极限后,快速上升,到达系统崩溃点后,系统失去响应。

性能测试方法

  • 性能测试:以系统设计初期规划的性能指标为预期目标,不断对系统施压,验证系统在资源可接受范围内,是否能达到性能瓶颈。
  • 负载测试:对系统不断增加并发请求以增加系统压力,直到系统的某项或多项性能指标达到安全临界值,如某种资源已经饱和,这时继续对系统施加压力,系统的处理能力会下降。
  • 压力测试:超过某项或多项性能指标的安全临界值,对系统继续施压,直到系统崩溃或不能处理任何请求,以此获得系统最大压力承受能力。
  • 稳定性测试:被测试系统在特定硬件、软件、网络环境条件下,给系统加载一定业务压力,使系统运行较长一段时间,以此检测系统是否稳定。在不同生产环境、不同时间点的请求压力是不均匀的,呈波浪特性,因此为了更好地模拟生产环境,稳定性测试也应不均匀地对系统施加压力。

性能优化策略

  • 性能分析:排查一个网站的性能瓶颈和排查一个程序的性能瓶颈手法基本相同,即检查请求处理的各个环节的日志,分析哪个环节响应时间不合理,然后检查监控数据,分析影响性能的主要因素是内存、磁盘、网络还是CPU,是代码问题还是架构设计不合理,或者系统资源确实不足。
  • 性能优化:定位性能问题的具体原因后,就需要进行性能优化,根据网站分层结构,可分为Web前端性能优化、应用服务器性能优化、存储服务器性能优化。

Web前端性能优化

浏览器访问优化

  • 减少HTTP请求:通过合并CSS、合并JS、合并图片。将浏览器一次访问需要的JS、CSS合并成一个文件,这样浏览器只需要请求一次。图片也可以合并,多张图片合并成一张,如果每张图片有不同的超链接,可通过CSS偏移响应鼠标点击操作,构造不同的URL。
  • 使用浏览器缓存:CSS、JS、Logo、图片等静态资源文件的更新频率比较低,而这些文件每次HTTP请求都是需要的,如果将它们缓存在浏览器中,可以提高性能。通过设置HTTP头中的Cache-Control和Expires属性,可设定浏览器缓存时间。某些时候,静态资源文件变化需要及时应用到客户端浏览器,可通过改变文件名实现,比如给JS一个版本号,更新HTML的JS引用版本号即可。
  • 启用压缩:在服务器端对文件进行压缩,在浏览器端对文件解压,可减少通信数据量。文本文件的压缩效率可达80%以上,因此HTML、CSS、JS文件启用GZip压缩可以达到较好的效果。
  • CSS放在页面最上面,JS放在页面最下面:浏览器会在下载完所有CSS文件后才会对整个页面进行渲染;L浏览器在加载JS后立即执行,有可能会阻塞整个页面,造成页面显示缓慢。
  • 减少Cookie传输:Cookie包含在每次请求和响应中,太大的Cookie会影响数据传输。对于某些静态资源的访问如CSS、JS等,发送Cookie没有意义,可以考虑静态资源使用独立域名访问,避免请求静态资源时发送Cookie。

CDN加速

CDN部署在网络运营商的机房,这些运营商是终端用户的网络服务提供商,因此用户请求路由的第一跳就到达了CDN服务器,当CDN中存在浏览器请求的资源时,从CDN直接返回给浏览器,减少数据中心负载的压力。

CDN缓存的一般是静态资源,如图片、文件、CSS、JS、静态网页。

反向代理

反向代理服务器的作用:

  • 保护网站安全。来自互联网的访问请求必须经过代理服务器,相当于在Web服务器和可能的网络攻击之间建立了一个屏障。
  • 通过配置缓存功能加速Web请求。当用户第一次访问静态内容时,静态内容被缓存在反向代理服务器上。有些网站会把动态内容也缓存在代理服务器上,比如维基百科及某些博客论坛网站,把热门词条、帖子、博客缓存在反向代理服务器上加速用户访问速度,当这些动态内容有变化时,通过内部通知机制通知反向代理缓存失效,反向代理会重新加载最新的动态内容。

应用服务器性能优化

分布式缓存

缓存主要用来放那些读写比很高、很少变化的数据,如商品的类目信息、热门词的搜索列表信息、热门商品信息等。网站数据访问通常遵循二八定律,因此缓存20%的数据就可以改善系统性能。

使用缓存的注意点:

  • 频繁修改数据。一般来说,数据的读写比在2:1以上,即写入一次缓存,在数据更新前至少读取两次,缓存才有意义。实践中,这个读写比通常非常高,比如新浪微博的热门微博,缓存以后可能被读取数百万次。
  • 没有热点的访问。如果应用系统访问数据没有热点,不遵守二八定律,即大部分数据访问没有集中在小部分数据上,那么缓存就没有意义,因为大部分数据还没有被再次访问就被挤出缓存了。
  • 数据不一致或脏读。一般缓存有失效时间,应用要容忍一定时间的数据不一致,在互联网中,这种延迟一般是能接受的,但是具体应用仍需谨慎对待。
  • 缓存可用性。缓存雪崩可能导致数据库宕机,有些网站通过缓存热备提高缓存可用性,但这有违缓存的初衷,缓存不该被当作一个可靠的数据源,因此建议使用分布式缓存。
  • 缓存预热。新启动的缓存系统如果没有任何数据,在重建缓存的过程中,系统的性能和数据库负载都不太好,因此最好在缓存系统启动的时候就把热点数据加载好。
  • 缓存穿透。如果因为不恰当的业务,或者恶意攻击持续高并发地请求某个不存在的数据,会穿透缓存对数据造成很大压力,一般通过布隆过滤器或缓存不存在的数据来解决。

异步操作

消息队列不仅可以改善网站扩展性,还可以改善网站性能。

需要注意的是,数据写入消息队列后立即返回给用户,数据在后续的业务校验、写数据库等操作可能失败,因此在使用消息队列进行业务异步处理后,需要适当修改业务流程进行配合,如订单提交后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消费者真正处理完该订单,甚至商品出库后,再通过电子邮件或SMS消息通知用户订单成功,以免交易纠纷。

使用集群

负载均衡服务器分发请求,使每台业务服务器处理的请求数目控制在最佳运行区间,以获得最佳的访问请求延迟。

代码优化

多线程:从资源利用的角度看,使用多线程的原因有两个,分别是IO阻塞和多核CPU。线程进行IO操作时,会被阻塞释放CPU,由于IO操作(磁盘或网络)通常需要较长时间,这时CPU可以调度其他的线程进行处理。使用多线程需要解决线程安全问题,手段有:

  • 将对象设计为无状态对象。无状态对象指对象本身不存储状态信息(对象无成员变量,或者成员变量也是无状态对象),这样多线程并发访问不会出现状态不一致问题,Java Web的Servlet对象就是无状态对象。Web开发中常用的贫血模型对象都是无状态对象,不过从面向对象设计的角度看,无状态对象是一种不良设计。
  • 使用局部对象。即在方法内部创建对象,且不传递给其他线程。
  • 并发访问资源使用锁。使用锁解决并发修改问题,但可能导致性能问题和死锁。

资源复用:单例模式和对象池模式实现资源复用。由于Web开发中主要使用贫血模式,从Service到Dao都是无状态对象,无需重复创建,使用单例模式就很合适;对象池模式通过复用对象实例,减少对象创建和资源消耗。

数据结构:Hash散列算法Time33(hash(i) = hash(i - 1) * 33 + str[i])和MD5等。

垃圾回收:JVM调优,根据系统业务特点和对象生命周期,设置年轻代和老年代大小,尽量减少Full GC。

存储服务器性能优化

SSD替换机械硬盘。

B+树和LSM树

  • B+树是专门针对磁盘存储而优化的N叉排序树,目前数据库多采用两级索引B+树,树的层次最多三层,因此可能需要5次磁盘访问才能更新一条记录(三次磁盘访问获得数据索引及行ID,一次数据文件读操作,一次数据文件写操作)。

  • NoSQL多采用LSM树。

    LSM树可以看作一个N阶合并树,数据写操作都在内存中进行,并且会创建一个新记录(修改会记录新的数据值,删除会记录一个删除标记),这些数据在内存中仍然是一棵排序树,当数据量超过设定的内存阈值后,会将这棵树和磁盘上最新的排序树合并。当磁盘上最新的排序树也超过设定阈值后,和磁盘上下一级的排序树合并。合并过程中,会用最新更新的数据覆盖旧的数据(或者记录为不同的版本)。

    当需要进行读操作时,总是从内存的排序树开始搜索,如果没有找到,就从磁盘上的排序树顺序查找。

    在LSM树上进行一次数据更新不需要访问磁盘,速度快于B+树。当数据以写操作为主,而读操作则集中在最近写入的数据上时,使用LSM树可以极大减少磁盘访问次数,加快访问速度。

    LSM树可参考大数据日知录

RAID和HDFS

  • RAID(廉价磁盘冗余阵列)技术主要是为了改善磁盘的访问延迟,增强磁盘的可用性和容错能力。可通过硬件如专用的RAID卡或主板直接支持来实现,也可通过软件实现。
    • RAID0。数据从内存缓冲区写入磁盘时,根据磁盘数量将数据分为N份,这些数据同时并发写入N块磁盘,使得数据整体写入速度是一块磁盘的N倍。读取时也一样,因此RAID0具有极快的数据读写速度,但其不做数据备份,N块磁盘只要有一块损坏,数据完整性就被破坏了。
    • RAID1。数据写入磁盘时,一份数据同时写入两块磁盘,损坏一块磁盘不会导致数据丢失,插入一块新磁盘可通过复制数据的方式自动修复,具有极高的可靠性。
    • RAID10。结合RAID0和RAID1,将磁盘平均分为两份,数据同时在两份磁盘写入,相当于RAID1,但在每一份磁盘里面的N/2块磁盘上,利用RAID0并发读写。RAID10磁盘利用率较低,有一半的磁盘用来写备份数据。
    • RAID3。一般情况下,一台服务器不会出现同时损坏两块硬盘的情况,在只损坏一块磁盘的情况下,如果能利用其他磁盘的数据恢复损坏磁盘的数据,就可在保证可靠性和性能的同时,磁盘利用率也得到大幅提升。数据写入时,将数据分成N-1份并发写入N-1块磁盘,在第N块磁盘记录校验数据,任何一块磁盘损坏都可利用其它N-1块磁盘进行数据修复。但是,在数据修改较多的场景中,修改任何磁盘数据都会导致第N块磁盘重写校验数据,频繁写入的后果是第N块磁盘更容易损坏,因此实际中很少用。
    • RAID5。RAID5类似RAID3,但是校验数据不是写入第N块磁盘,而是螺旋式地写入所有磁盘中,以避免RAID3频繁写坏一块磁盘的情况。
    • RAID6。如果数据需要很高的可靠性,在出现同时损坏两块磁盘的情况下,仍然需要修复数据,可以用RAID6。RAID6和RAID5类似,但数据只写入N-2块磁盘,并螺旋式地在两块磁盘中写入校验信息(使用不同算法生成)。
  • HDFS。RAID在传统关系数据库及文件系统中应用广泛,但在NoSQL以及分布式文件系统中,却是HDFS应用广泛。
    • HDFS以块为单位管理文件内容,一个文件被分割为若干个Block,当应用程序写文件时,每写完一个Block,HDFS就将其自动复制到其他机器上,相当于实现了RAID1。
    • 当对文件进行处理计算时,通过MR框架启动多个Map Task读取文件的多个Block,启动多个Reduce Task写入多个Block,相当于实现了RAID0。
  • HDFS-RAID架构。参考大数据日知录

网站的高可用架构

网站的可用性度量

网站可用性度量:网站不可用被称作网站故障,通常用多少个9来衡量网站的可用性。网站不可用时间=故障修复时间点-故障发现时间点网站年度可用性指标=(1-网站不可用时间/年度总时间)×100%。对于大多数网站而言,2个9是基本可用,网站年度不可用时间小于88小时;3个9是较高可用,网站年度不可用时间小于9小时;4个9是具有自动恢复能力的高可用,网站年度不可用时间小于53分钟;5个9是极高可用,网站年度不可用时间小于5分钟。

高可用的网站架构

主要手段是数据和服务的冗余备份及失效转移,一旦某些服务器宕机,就将服务切换到其他可用的服务器上,如果磁盘损坏,则从备份的磁盘读取数据。

典型的分层模型是三层:应用层、服务层、数据层。应用层负责具体业务逻辑处理;服务层提供可复用的服务;数据层负责数据存储与访问。中小型网站部署时长将应用层和服务层不是在一起,数据层另外部署。大型网站将不同的业务部署在不同的服务器集群上,如某网站的文库、贴吧、知道、百科等属于不同的产品,部署在各自独立的服务器集群上,这些产品依赖的一些共同的复用业务,如注册登录、Session管理服务、账户管理服务等,这些业务服务也各自部署在独立的服务器集群上,至于数据层的数据库服务、文件服务、缓存服务、搜索服务等部署在各自独立的服务器集群上。(登录贴吧后,访问百科也是登录态)

高可用应用

通过负载均衡进行无状态服务的失效转移:应用层不保存状态,因此一台服务器宕机,请求可以提交给其他任意一台可用机器处理。

应用服务器的高可用架构主要基于服务无状态这一特性,但实际上业务总是有状态的,记录这些状态信息的上下文对象称为Session。集群环境下需要保证每次请求能获得正确的Session,手段有以下几种:

  • Session复制。应用服务器开启Web容器的Session复制功能,在服务器之间同步Session对象。当集群规模较大或者用户较多的情况下,会占用服务器和网络的大量资源,对于大型网站来说这种方案并不适用。
  • Session绑定(会话黏滞)。可通过负载均衡的源地址Hash实现,负载均衡服务器总是将来源于同一IP的请求分发到同一台服务器上,负载均衡服务器必须工作在HTTP协议层上。但是该方案不符合系统高可用需求,因为一旦一台服务器宕机,该机器的Session消失,用户切换到其他机器因没有Session而无法完成业务。
  • 利用Cookie记录Session。每次请求服务器时,将Cookie记录的Session发送给服务器,服务器处理完请求后再将修改过的Session响应给客户端。缺点是,受Cookie大小限制,能记录的信息有限;每次请求响应都要传输Cookie,影响性能;如果用户关闭Cookie,访问就会不正常。但是,Cookie简单易用,可用性高,支持服务器的线性伸缩,而大部分应用需要记录的Session信息比较小,因此许多网站或多或少使用Cookie记录Session。
  • Session服务器。利用独立部署的Session服务器集群统一管理Session,应用服务器每次读写Session时都访问Session服务器。这种方案实际上是将应用服务器的状态分离,分为无状态的应用服务器和有状态的Session服务器,针对不同的特性分别设计器架构。对于有状态的Session服务器,可以利用分布式缓存、数据库等,在这些产品的基础上包装。如果业务场景对Session管理有较高要求,如利用Session服务集成单点登录(SSO)、用户服务功能,则需要开发专门的Session管理平台。

高可用服务

高可用服务策略:

  • 分级管理。核心应用和服务优先使用更好的硬件,如订单、支付服务比评价服务有更高的优先级。同时在服务部署上也进行必要的隔离,避免故障连锁反应。低优先级的服务通过启动不同的线程或部署在不同的虚拟机上进行隔离,而高优先级的服务则需要部署在不同的物理机上,核心服务和数据甚至需要部署在不同地域的数据中心。
  • 超时设置。由于服务端宕机、线程死锁等原因,可能导致应用程序对服务端的调用失去响应,进而导致用户请求长时间得不到响应,同时还占用应用程序的资源,不利于及时将访问请求转移到正常的服务器上。通过设置超时时间,一旦超时通信框架就抛出异常,应用程序根据服务调度策略,可选择继续重试或将请求转移到提供相同服务的其他服务器上。
  • 异步调用。异步调用可以避免一个服务失败导致整个应用请求失败的情况,但不是所有服务调用都可以用异步,如获取用户信息、需确认服务调用成功才能继续下一步操作的应用。
  • 服务降级。网站访问高峰期,服务可能因为大量的并发调用而性能下降,严重时可能会导致服务宕机。为保证核心应用和功能正常运行,需要对服务进行降级。降级的手段:1)拒绝服务,即拒绝低优先级应用的调用、减少服务调用并发数,或随机拒绝部分请求,让另一部分请求得以成功;2)关闭功能,关闭部分不重要服务,或服务内部关闭部分不重要功能,以节约系统开销。如淘宝在双十一促销时,在系统最繁忙的时段关闭评价、确认收货等非核心服务,以保证核心交易服务顺利完成。
  • 幂等性设计。服务重复调用是无法避免的,因此必须在服务层保证服务重复调用和调用一次产生的结果相同。

高可用数据

保证数据存储高可用的手段主要是数据备份和失效转移机制。数据备份保证数据有多个副本,任意副本失效都不会导致数据的永久丢失,从而实现数据完全的持久化。而失效转移机制则保证当一个数据副本不能访问时,可以快速切换其他副本,保证系统可用。

为了保证数据的高可用,网站通常会牺牲另一个很重要的指标:数据一致性。这就涉及CAP原理。数据一致性可分为:

  • 数据强一致。在没有出现网络分区的时候可以保证,各个副本的数据在物理存储中保持一致。
  • 数据用户一致。数据在物理存储中的各个副本数据可能不一致,但同一用户访问时,通过纠错和校验机制,返回一个一致的数据给用户。
  • 数据最终一致。物理存储的数据可能是不一致的,同一用户访问到的数据可能也是不一致的,但系统经过一段时间的自我恢复和修正,数据最终会达到一致。

数据备份:

  • 数据冷备。优点是简单,成本和技术难度低,缺点是不能保证数据最终一致性。如binlog、RDB。
  • 数据热备:
    • 异步热备。多份数据副本的写入操作异步完成,应用程序收到数据系统的写操作成功响应时,只写成功了一份,存储系统会异步地写其他副本。该种方式下,存储服务器分为Master和Slave。
    • 同步热备。多份数据副本的写入操作同步完成,一般通过让客户端并发向多个存储服务器同时写入数据,等待所有存储服务器都返回成功响应后,再通知应用程序写操作成功。

失效转移:

  1. 失效确认。当应用程序报告存储服务器访问失败时,由控制中心再一次发送心跳检测进行确认,以免错误判断服务器宕机,因为一旦进行数据访问的失效转移,就意味着数据存储多份副本不一致,需要进行后续一系列复杂的操作来恢复数据一致性。
  2. 访问转移。当存储是对等的服务器(即存储服务器存储的数据完全一样),应用程序根据配置直接切换到对等的服务器上;如果存储不对等,则需要重新计算路由,选择存储服务器。
  3. 数据恢复。参考海量分布式存储系统Doris的高可用架构设计分析

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

网站发布:发布新功能时,都需要在服务器上关闭原有的应用,然后重新部署启动新的应用,整个过程还要求不影响用户的使用。因此,设计一个网站的高可用架构时,需要考虑的服务器宕机概率不是物理上的一到两次,而是事实上的每周一到两次。通常利用发布脚本完成发布,渐进式发布。

自动化测试:如果每次发布新功能都是在原有系统功能上的小幅增加,为了保证系统没有引入未预料的Bug,网站测试需要对整个网站功能进行全面的回归测试,如果使用人工测试,成本、时间和测试覆盖率都难以接受。

预发布验证:即使经过严格测试,软件部署到线上服务器后还是经常出现各种问题,甚至无法启动服务器。主要原因是测试环境和线上环境并不相同,特别是应用需要依赖的其他服务,如数据库、缓存、公共业务服务、第三方服务等。因此,在网站发布时,先把测试通过的代码发布到预发布机器上,在预发布机器上进行完预发布验证后,确认没问题后再正式发布。预发布服务器(也可以叫沙盒)是一种特殊用途的服务器,它和线上的正式服务器唯一的不同是没有配置在负载均衡服务器上,外部用户无法访问。需要注意,预发布服务器连接的是真实的生产环境,所有预发布验证操作都是真实有效的数据,这些操作也许会引起不可预期的问题,如创建一个店铺,上架一个商品,就有可能有真实用户过来购买,如果不能发货,会导致用户投诉。

代码控制:主流的是分支开发,主干发布。

自动化发布:人的干预越少,自动化程度越高,引入故障的可能性越小。

灰度发布:大型网站的主要业务服务器集群规模非常庞大,一旦发现故障, 即使想要发布回滚也需要很长时间才能完成,为了应对这种情况,大型网站会使用灰度发布模式,将集群服务器分成若干份,每天只发布一部分服务器,观察运行稳定没有故障,第二天继续发布一部分服务器,持续几天才把整个集群发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器。灰度分布也常用于用户测试,在部分服务器上发表新版本,其余服务器保持老版本或发布另一个版本,然后收集用户行为,比较用户对两个版本的满意度,以确定最终发布的版本,这种手段也被称作AB测试。

网站运行监控

监控数据采集

用户行为日志收集:用户行为日志指用户在浏览器上所做的所有操作及其所在的操作环境,包括用户操作系统、浏览器版本、IP、访问的网页、页面停留时间等,这些数据对统计网站PV/UV指标、分析用户行为、优化网站设计、个性化营销和推荐都非常重要。收集用户行为日志的手段有两种:1)服务端日志收集,记录大部分用户的行为日志;2)客户端浏览器日志收集,用JS收集用户的真实操作行为。

服务器性能监控:收集服务器性能指标,如系统Load、内存占用、磁盘IO、网络IO等,超出设定阈值时应及时报警。

运行数据报告:网站还需要监控一些与具体业务场景相关的技术和业务指标,如缓存命中率、平响、任务堆积数等。

监控管理

系统报警:当服务器监控指标超过某个阈值,意味着相同可能要出现故障,这时就需要对相关人员报警,及时采取措施,在故障还未真正发生时就将其扼杀。报警方式有邮件、及时通信工具、短信、语音等。

失效转移:监控系统在发现故障的情况下,主动通知应用进行失效转移。

自动优雅降级:优雅降级是指网站为了应对突然爆发的访问高峰,主动关闭部分功能,释放部分系统资源,保证核心功能正常使用。

网站的伸缩性架构

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

网站架构的伸缩性设计

网站的伸缩性设计分为两类:

  • 根据功能进行物理分离实现伸缩。
  • 单一功能通过集群实现伸缩。

不同功能进行物理分离实现伸缩

纵向分离(分层后分离):将业务流程上的不同部分分离部署,实现系统伸缩性。类似MVC。

横向分离(业务分割后分离):将不同业务模块分离部署,实现系统伸缩性。粒度可以非常小,甚至可以一个关键网页部署一个独立服务,比如对于电商网站的产品详情页、店铺页面、搜索页面等。

单一功能通过集群规模实现伸缩

当即使将功能按最小的粒度进行分离也不能满足业务的需求时,必须使用集群来对外提供服务。集群伸缩性可分为应用服务器集群伸缩性、缓存数据服务器集群伸缩性、存储数据服务器集群伸缩性。

应用服务器集群伸缩性

应用服务器应设计为无状态的,即其不存储请求上下文信息,如果将部署有相同的服务器组成集群,每次用户请求可以发送到集群中任意一台服务器上处理,任何服务器的处理结果都是相同的。

如果HTTP请求分发装置可以感知或可以配置集群的服务器数量,可以及时发现集群中新上线或下线的服务器,并能向新上线的服务器分发请求,停止向下线的服务器分发请求,那么就实现了应用服务器集群的伸缩性。这个HTTP请求分发装置被称作负载均衡服务器,其实现方式有以下几种。

HTTP重定向负载均衡

HTTP重定向服务器为用户的HTTP请求计算一台应用服务器地址,写入HTTP重定向响应中,浏览器再自动重新请求实际物理服务器的IP。

优点是简单。缺点是浏览器需要请求两次才能完成一次访问,性能较差;重定向服务器可能成为瓶颈;302重定向可能被搜索引擎判定为作弊。实际中使用这种方案的案例不多见。

DNS域名解析负载均衡

在DNS服务器配置多个A服务器的记录,每次域名解析请求都会根据负载均衡算法选择一个IP,这样A记录中配置的多个服务器就构成一个集群,并可以实现负载均衡。

优点:将负载均衡工作交给DNS,省掉网站管理维护负载均衡服务器的麻烦;DNS支持基于地理位置的域名解析,即将域名解析成距离用户地理最近的一个服务器地址,加快用户的访问速度。

缺点:DNS是多级解析,每一级DNS都可能缓存A记录,当下线某台服务器,即使修改了DNS的A记录,要使其生效需要较长时间,导致用户访问失败;DNS负载均衡的控制权在域名服务商那里,网站无法对其做更多改善和管理。

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

反向代理负载均衡

浏览器请求的地址是反向代理服务器的IP地址,反向代理服务器收到请求后,根据负载均衡算法选择一台应用服务器地址,并将请求转发给服务器,应用服务器处理完后将响应返回给反向代理服务器,反向代理服务器再将该响应返回给用户。

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

IP负载均衡

用户请求包到达负载均衡服务器后,负载均衡服务器在操作系统内核进程获取网络数据包,根据负载均衡算法选择一台应用服务器,然后修改数据包的目的IP地址,应用服务器处理完后,响应数据包回到负载均衡服务器,负载均衡服务器再将数据包源地址修改为自身IP。关键在于应用服务器如何将响应数据包返回给负载均衡服务器?一种方案是负载均衡服务器在修改目的IP地址的同时修改源地址,将其设为自身IP,即源地址转换(SNAT)。另一种方案思考哦昂负载均衡服务器作为业务服务器集群的网关服务器,这样所有响应数据都会达到负载均衡服务器。

IP负载均衡在内核进程完成数据分发,较反向代理负载均衡有更好的性能。但集群的最大响应数据吞吐量受制于负载均衡服务器的网卡带宽。

数据链路层负载均衡

数据链路层负载均衡是在通信协议的数据链路层修改mac地址进行负载均衡。负载均衡数据分发过程中不修改IP地址,只修改目的mac地址,通过配置业务服务器集群所有集群的虚拟IP和负载均衡服务器IP地址一致,从而达到不修改数据包的源地址和目的地址就可以进行数据分发的目的,业务服务器可以将响应数据包直接返回给浏览器。这种方式称为三角传输模式,又称为直接路由方式。

使用三角传输模式的链路层负载均衡是目前大型网站使用最广泛的一种手段。

负载均衡算法

  • 轮询
  • 加权轮询
  • 随机
  • 最少连接
  • 源IP散列

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

和应用服务器集群不同,缓存服务器集群中不同服务器缓存的数据各不相同,缓存访问请求不可以在任意一台服务器上处理,必须先找到缓存着需要数据的服务器,然后才能访问。分布式缓存集群伸缩性的主要目标是新加入缓存服务器后整个集群中已经缓存的数据尽可能还被访问到。

一致性Hash+虚拟节点。每个物理节点对应的虚拟节点越多,各个物理节点之间的负载越均衡,新加入的缓存服务器对原来集群的影响越保持一致(一致性Hash名称的由来)。实践中,一台缓存服务器虚拟为150个节点比较合适,当然根据集群规模和负载均衡的精度需求,这个值应该具体情况具体分析。

计算机的任何问题都可以通过增加一个虚拟层来解决。计算机网络7层协议;计算机操作系统可以看作计算机硬件的虚拟层;Java虚拟机可以看作是操作系统的虚拟层。

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

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

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

数据库主从读写分离;不同业务数据表部署在不同的数据库集群上(分库),限制是跨库的表不能进行Join操作;数据分片,将一张达标拆开分别存储在多个数据库中。

支持数据分片的一个例子是Cobar,其是一个分布式关系数据库访问代理,介于应用服务器和数据库服务器之间,解析SQL和根据路由规则分解SQL,分发到MySQL集群不同的数据库实例上执行,多个数据库的执行结果返回到SQL执行模块,通过结果合并模块将多个结果集合合并成一个结果集。

Cobar集群伸缩:

  • Cobar服务器集群伸缩:可以看作无状态的应用服务器,其集群伸缩可以使用负载均衡手段实现。
  • MySQL服务器集群伸缩:需要做数据迁移,面临的问题有迁移过程对数据库有一定访问压力、迁移过程中数据的一致性、可访问性、服务器宕机的可用性。实践中,Cobar利用了MySQL的数据同步功能进行数据迁移。数据迁移不是以数据为单位,而是以Schema为单位。在Cobar集群初始化时,在每个MySQL实例上创建多个Schema(根据业务远景规划未来集群规模,如集群最大规模为1000台数据库服务器,那么总的初始Schema数不小于1000)。集群扩容时,从每个服务器中迁移部分Schema到新机器中,由于迁移以Schema为单位,迁移过程可以使用MySQL的同步机制。同步完成后,修改Cobar服务器路由配置,将迁移Schema的IP修改为新机器的IP,然后删除原机器中的相关Schema,完成MySQL集群扩容。

Cobar服务器处理时间很少,时间花费主要还是在MySQL,因此应用程序通过Cobar访问分布式数据库,性能基本和直接访问关系数据库相当。但Cobar路由后只能在单一数据库实例上处理请求,因此无法执行跨库的Join操作,也不能执行跨库的事务。

NoSQL数据库的伸缩性设计

关系数据库面临的问题是糟糕的海量数据处理能力及僵硬的设计约束,因此NoSQL被提出以弥补关系数据库的不足,NoSQL更关注高可用性和可伸缩性。

HBase为可伸缩海量数据存储而设计,实现面向在线业务的实时数据访问延迟,其伸缩性主要依赖可分裂的HRegion及HDFS。数据以HRegion为单位管理,即应用程序想访问一个数据,必须先找到HRegion,然后将数据读写操作提交给HRegion,由HRegion完成存储层的操作。每个HRegion存储一段key值区间[key1, key2)的数据,HRegionServer是物理服务器,可以启动多个HRegion实例。当一个HRegion中写入的数据太多,达到配置的阈值时,HRegion会分裂成两个,并在整个集群中迁移,以使HRegionServer负载均衡。

所有HRegion的信息存储在HMaster服务器上,HMaster通过ZooKeeper保证高可用。

  • 数据读取过程:通过ZooKeeper得到HMaster的地址,通过key得到key所在的HRegionServer地址,然后请求HRegionServer上的HRegion,获得需要的数据。
  • 数据写入过程:得到HRegion后继续操作,HRegion将数据存储在若干个HFile(使用HDFS存储)格式的文件中,当一个HRegion数据量过多则进行分裂迁移。
  • 如果集群中有新加入的HRegionServer,由于其负载较低,也会把HRegion迁移过去并记录到HMaster,实现HBase的线性伸缩。

网站的可扩展架构

构建可扩展的网站架构

软件架构师最大的价值不在于掌握多少先进的技术,而在于具有将一个大系统拆分成多个低耦合的子模块的能力,这些子模块包含横向的业务模块,也包含纵向的基础技术模块。

设计网站可扩展架构的核心思想是模块化,在此基础上,降低模块间的耦合性,提高模块的复用性。

利用分布式消息队列降低系统耦合性

消息队列解耦生产者和消费者。

利用分布式服务打造可复用的业务平台

RPC。

如果说分布式消息队列通过消息对象分解系统耦合性,不同子系统处理同一类消息,那么分布式服务则通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务调用。

当系统过于庞大时,需要对其拆分,将模块独立部署,降低系统耦合性,拆分可分为纵向拆分和横向拆分。

  • 纵向拆分:将一个大应用拆分为多个小应用,如果新增业务较为独立,那么就直接将其设计部署为一个独立的Web应用系统。
  • 横向拆分:将复用的业务拆分,独立部署为分布式服务,新增业务只需要调用这些分布式服务,不需要依赖具体的模块代码,即可快速搭建一个应用系统,而模块内业务逻辑变化时,只有接口保持一致就不会影响业务程序和其他模块。

大型网站分布式服务端需求和特点:

  • 服务注册和发现、服务调用
  • 负载均衡
  • 失效转移
  • 高效的远程通信。大型网站核心服务每天的调用次数达数亿次,没有高效的远程通信手段,服务调用会成为整个系统性能的瓶颈。
  • 整合异构系统。由于历史原因和组织分割,网站服务可能会使用不同的语言开发并部署于不同的平台。
  • 对应用最少侵入。
  • 版本管理。为了应对快速变化的需求,服务升级不可避免,如服务访问接口要升级。分布式服务框架需要支持多版本发布,服务提供者先升级接口发布新版本的服务,并同时提供旧版本的服务供请求者调用,当请求者调用接口升级后才可以关闭旧版本的服务。
  • 实时监控

可扩展的数据结构

传统的关系数据库为了保证关系运算(通过SQL语句)的正确性,在设计数据库表结构的时候,就需要指定表的schema(字段名称、类型等),并要遵守特定的设计规范。这带来的问题是僵硬的数据结构难以面对需求变更带来的挑战。

有没有能够无需修改表结构就可以新增字段的可扩展数据结构设计呢?许多NoSQL数据库使用的ColumnFamily(列族)设计就是一个解决方案。创建表的时候,只需要指定ColumnFamily的名字,无需指定字段(Column),可以在数据写入的时候再指定,通过这种方式,数据表可以包含数百万字段,使得应用程序的数据结构可以随意扩展。在查询时,可通过指定任意字段名称和值进行查询。

利用开放平台建设网站生态圈

大型网站为了更好地服务自己的用户,开发更多的增值服务,和把网站内部的服务封装成一些调用接口开放出去,供外部的第三方开发者使用,这个提供开放接口的平台被称为开放平台,其架构设计为:

  • API接口。
  • 协议转换。将各种API输入转换成内部服务可以识别的形式,并将内部服务的结果封装成API的格式。
  • 安全。除了一般应用需要的身份识别、权限控制等安全手段,开放平台还需要分级的访问带宽限制,保证平台资源被第三方应用公平合理使用,也保护网站内部服务不会被外部服务拖垮。
  • 审计。记录第三方应用的访问情况,并进行监控、计费。
  • 路由。将开放平台的各种访问路由映射到具体的内部服务。
  • 流程。将一组离散的服务组织成一个上下文相关的新服务,隐藏服务细节,提供统一接口供开发者使用。

网站的安全架构

网站应用攻击与防御

XSS攻击

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

XSS攻击类型:

  • 反射型。攻击者诱使用户点击一个嵌入恶意脚本的链接,达到攻击的目的。
  • 持久型。含有恶意脚本的请求保存在被攻击的Web站点数据库中,用户浏览网页时,恶意脚本被包含在正常页面中,达到攻击的目的。这种攻击经常使用在论坛、博客等Web应用中。

防XSS攻击的手段:

  • 消毒。对HTML特殊的字符进行转义。
  • HttpOnly。浏览器禁止页面JavaScript访问带有HttpOnly属性的Cookie,其用于防止XSS攻击者窃取Cookie。对于存放敏感信息的Cookie,如用户认证信息,可通过对该Cookie添加HttpOnly属性,避免被攻击脚本窃取。

注入攻击

注入攻击包括SQL注入攻击和OS注入攻击。

SQL注入攻击手段:

  • 开源。如果网站用开源软件搭建,如用Discuz搭建,那么网站的数据库结构是公开的,攻击者可以直接获得。
  • 错误回显。如果网站开启错误回显,即服务器内部的500错误显示到浏览器上。攻击者通过故意构造非法参数,使服务端异常信息输出到浏览器端,为攻击猜测数据库表结构提供了便利。
  • 盲注。网站关闭错误回显,攻击者通过页面变化情况判断SQL语句的执行情况,据此猜测数据库表结构。

防SQL注入的手段:

  • 消毒。通过正则匹配,过滤请求数据中可能注入的SQL。
  • 参数绑定。使用预编译手段,绑定参数是最好的放SQL注入手段。

CSRF攻击

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

防CSRF攻击的手段:

  • 表单token。CSRF是伪造用户请求的操作,所以需要构造用户请求的所有参数才可以。表单token通过在请求参数中增加随机数的方法来阻止攻击者获得所有请求参数:在页面表单中增加一个随机数作为token,每次响应页面的token都不同,从正常页面提交的请求会包含该token,而伪造的请求无法获得该值,服务器检查请求参数中的token是否存在且正确。
  • 验证码。验证码简单有效,但输入验证码是一个糟糕的用户体验,所以只在必要时使用,如支付交易等关键页面。
  • Referer Check。HTTP请求头的Referer域中记录着请求的来源,可通过检查请求来源,验证其是否合法,很多网站使用这个功能实现图片防盗链。

其他攻击和漏洞

Error Code。也称作错误回显。许多Web服务器默认打开异常信息输出,即服务器端未处理的异常堆栈信息会直接输出到客户端浏览器。防御手段是配置Web服务器参数,跳转到500页面。

文件上传。一般网站都会有文件上传功能,设置头像、分享视频、上传附件等。如果上传的是可执行程序,并通过该程序获得服务器端命令执行的能力,那么攻击者几乎可以在服务器上为所欲为。最有效的防御手段是设置上传文件白名单,只允许上传可靠的文件类型。此外还可以修改文件名、使用专门的存储等手段。

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

信息过滤与反垃圾

文本匹配

文本匹配主要解决敏感词过滤的问题,如果用户发表的信息含有列表的敏感词,则进行消毒处理或拒绝发表。

判断用户信息是否有敏感词的方法:

  • 如果敏感词较少,用户提交信息文本长度也较短,可以直接使用正则表达式匹配。
  • 如果敏感词较多,用户发布的信息也很长,网站并发量较高时,可以使用Trie树的变种如双数组Trie算法。双数组Trie算法利用两个稀疏数组存储树结构,base数组存储Trie树的节点,check数组进行状态检查。双数组Trie需要根据业务场景和经验确定数组大小,避免数组过大或冲突过多。
  • 另一种简单的实现是构造多级Hash表进行文本匹配。假设敏感词表包含敏感词:阿拉伯、阿拉汉、北京、北风、北大荒,那么可以构造两棵树,每棵树的节点都是一个字。

有时候,为了绕过敏感词检查,某些输入信息会做一些手脚,如“阿 拉 伯”,这时候还需要对信息做降噪预处理,然后再进行匹配。

分类算法

对广告贴、垃圾邮件等内容的识别比较好的自动化方法是分类算法,如贝叶斯算法。

黑名单

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

黑名单可用Hash表实现,在黑名单列表不大的时候可满足需求。

在对过滤需求不完全精确的场景下,可用布隆过滤器代替Hash表。

电子商务风险控制

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

机器自动风控的手段:

  • 规则引擎。规则引擎是一种将业务规则和规则逻辑相分离的技术,业务规则由运营人员通过管理界面编辑。
  • 统计模型。规则引擎虽然技术简单,但随着规则的增加,会出现规则冲突,难以维护等情况,而且规则越多性能越差。目前大型网站更倾向于使用统计模型进行风控,主要是由分类算法或更复杂的机器学习算法进行智能统计。经过充分训练后的统计模型,准确率不低于规则引擎。分类算法的实时计算性能更好一些,由于统计模型使用模糊识别,并不精确匹配规则,因此对新出现的交易欺诈还具有一定的预测性。

案例

维基百科的高性能架构设计分析

Wikipedia建立在LAMP(Linux+Apache+MySQL+PHP)之上,其他基础技术组件也全部采用免费的开源软件。其业务比较简单,用户操作大部分是只读的,这使得其性能优化的约束变得简单。

前端性能优化。包括DNS服务、CDN服务、反向代理服务、静态资源服务。对于Wikipedia来说,80%的请求可以通过前端服务返回。其核心是反向代理服务器Squid集群,请求通过LVS(基于Linux的开源负载均衡服务器)负载均衡分发到每台Squid服务器,热点词条被缓存在这里,大量请求可以直接返回;Squid服务器不能命中的请求再通过LVS发送到Apache应用服务器集群;如果有词条更新,应用服务器使用Invalidation Notification服务通知Squid缓存失效,重新访问应用服务器更新词条;在Squid之前,是CDN服务,CDN缓存的几条准则:1)内容不包含动态信息,以免内容缓存很快失效或包含过时信息;2)每个内容页面有唯一的REST风格的URL,以便CDN快速查找并避免重复缓存;3)在HTML响应头写入缓存控制信息,通过应用控制内容是否缓存及缓存有效期等。

服务端性能优化。服务端运行的模块比较笨重,需要消耗较多的资源,Wikipedia将最好的服务器部署在这里,从硬件上改善性能。其他优化有:1)使用APC,一个PHP字节码缓存模块,可以加速代码执行减少资源消耗;2)使用Imagemagick进行图片处理和转化;3)使用Tex进行文本格式化,特别是将科学公式内容转换为图片格式;4)替换PHP的字符串查找函数strtr,使用更优化的算法重构。

后端性能优化。包括缓存、存储、数据库等被应用服务器依赖的服务都可以归类为后端服务。后端服务大多建立在网络通信和磁盘操作基础上,是性能的瓶颈,也是性能优化的重灾区。后端优化最主要的手段是缓存,缓存策略有:1)热点特别集中的数据直接缓存到应用服务器的本地内存中,因为要占用应用服务器的内存且每台服务器都需要重复缓存这些数据,因此这些数据量必须很小,且读取频率很高;2)缓存数据的内容尽量是应用服务器可以直接使用的格式,如HTML,以减少应用服务器从缓存中获取数据后解析构造数据的代价;3)使用缓存服务器存储session对象。MySQL优化策略有:1)使用较大的服务器内存,增加内存比增加其他资源更能改善MySQL性能;2)使用RAID0磁盘阵列以加速磁盘访问,RAID0(有n块磁盘,原来只能同时写一块磁盘,写满了再下一块,做了RAID0之后,一份数据分为n份,n块可以同时写,速度很快)虽然加速磁盘访问,但降低了数据库的持久可靠性(一块盘坏了,整个数据库的数据都不完整了),Wikipedia认为性能更重要,而数据可靠性可以通过其他手段解决(如MySQL主从复制,数据异步复制);3)数据库事务设置在较低水平,加快宕机恢复速度;4)如果Master宕机,立即将应用切换到Salve数据库,同时关闭数据写服务,即关闭词条编辑功能。

海量分布式存储系统Doris的高可用架构设计分析

高可用架构

Doris是一个海量分布式KV存储系统,目标是支持中等规模高可用、可伸缩的KV存储集群,和主流NoSQL系统HBase,Doris具有相似的性能和线性伸缩能力,并具有更好的可用性及友好的用户管理界面。MPP运行框架。

系统分为三个部分:

  • 应用程序服务器:它们是存储服务的客户,对存储服务发起数据操作请求。
  • 数据存储服务器:它们是存储服务的核心,负责存储数据、响应应用服务器的数据请求操作。
  • 管理中心服务器:主备组成的小规模服务器集群,负责集群管理,对数据存储集群进行健康心跳检测;集群扩容、故障恢复管理;对应用服务器提供数据服务器集群地址配置信息服务。

为便于管理和访问数据的多个副本,将存储服务器划分为多个序列,数据的多个副本存储在不同的序列中(序列可以理解为存储集群中的子集群)。

应用服务器写入数据时,根据集群配置和应用可用性级别,使用路由算法在每个序列中计算得到一台服务器,然后并发写入;应用服务器读取数据时,只需要随机选择一个序列,根据相同路由算法计算得到服务器编号和地址,即可读取。通常,系统至少写入的副本份数是两份。

正常情况下,存储服务器集群中的服务器互不感知,不进行任何通信;应用服务器只在启动时从管理中心服务器获取存储服务器集群信息,除非集群信息发生变化(故障、扩容),否则应用服务器不会和管理中心服务器通信。服务器之间通信越少,则依赖越少,发生故障时互相影响就越少,集群的可用性越高。

不同故障情况下的高可用解决方案

故障分类

  • 瞬时故障。主要原因是网络通信瞬时中断、服务器内存垃圾回收或后台线程繁忙停止数据访问操作响应。其特点是故障时间短、在秒级甚至毫秒级系统可自行恢复正常响应。
  • 临时故障。主要原因是交换机宕机、网卡松动等导致的网络通信中断;系统升级、停机维护等运维活动引起的服务关闭;内存损坏、CPU过热等硬件原因导致的服务器宕机。其特点是需要人工干预才能恢复正常,通常持续时间需要几十分钟到几小时。故障时间段可分为:临时故障期间、临时故障恢复期间。
  • 永久故障。主要原因是硬盘损坏、数据丢失。丢失的数据可能找不回来了,所以恢复系统到正常状态需要更长的时间。故障时间可分为:永久故障期间、永久故障恢复期间。

瞬时故障的高可用解决方案

瞬时故障是一种严重性较低的故障,一般系统经过较短暂的时间即可自行恢复。遇到瞬时故障只需要多次重试,就可以重连服务器,正常访问。

经过多次重试仍然失败,那么可能是临时故障,这时需要执行临时故障处理策略。也有可能是应用服务器自己的故障,比如系统文件句柄用光导致连接不能建立等,这时需要请求管理中心服务器进行故障仲裁,以判定故障种类。

临时故障的高可用解决方案

临时故障需要人工干预才能恢复正常,在故障服务器未能恢复正常前,系统也必须保证高可用。由于数据有多份副本,因此读数据时只需要路由选择正常服务的机器即可;写数据时,正常服务的机器依然正常写入,发生故障的机器需要将数据写入到临时存储服务器,等待故障服务器恢复正常后再将临时服务器中的数据迁移到该机器,整个集群就恢复正常了。

临时服务器是集群中专门部署的服务器(也可以是集群),正常情况下,该服务器不会有数据写入,只有在临时故障期间,才会写入数据。任何时候该服务器都不会提供读服务。

故障服务器恢复后,将临时故障期间写入临时服务器的数据全部迁移到存储服务器,存储服务器恢复到正常状态,系统可按正常情况访问。

永久故障的高可用解决方案

故障服务器上的数据永久丢失,从临时服务器迁移数据就没有意义了,必须从其他序列中正常的服务器中复制全部数据才能恢复正常状态。

永久故障发生期间,由于系统无法判断该故障是临时故障还是永久故障,因此系统访问结构和临时故障一样。当系统出现临时故障超时(超过设定时间临时故障服务器仍没启动)或人工确认为永久故障时,系统启用备用服务器替代原来永久失效的服务器,进入永久故障恢复。

网购秒杀系统架构设计案例分析

秒杀活动的技术挑战

  1. 对现有网站业务造成冲击。秒杀活动时间短,并发访问量大,如果和原有应用部署在一起,必然对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪。
  2. 高并发下的应用、数据库负载。用户在秒杀开始前,会不断刷新浏览器,导致大量请求打到后端。
  3. 突然增加的网络和服务器带宽。秒杀活动带来的流量超过网站平时使用的带宽。
  4. 直接下单。如果下单页面是一个普通的URL,得到这个URL不用等到秒杀开始就可以下单了。

秒杀系统的应对策略

  1. 秒杀服务独立部署。为了避免秒杀活动的高并发访问拖垮整个网站,可将秒杀系统独立部署;如果需要,还可以使用独立域名,使其与网站完全隔离,即使秒杀系统崩溃了,也不会对网站造成任何影响。
  2. 秒杀商品页面静态化。重新设计秒杀商品页面,不使用原来的商品详情页,页面内容静态化;将商品参数、商品描述、成交记录、用户评价全部写入一个静态页面,用户请求不需要经过应用服务器业务逻辑处理,也不需要访问数据库。
  3. 租借秒杀活动网络带宽。因秒杀新增的网络带宽,必须和运营商重新购买或租借。为了减轻网站服务器的压力,需要将秒杀商品页面缓存在CDN,同样需要和CDN服务商临时租借新增的出口带宽。
  4. 动态生成随机下单页面。方法是在下单页面URL加入由服务器端生成的随机数作为参数,在秒杀开始时才能得到。

秒杀系统架构设计

  1. 页面缓存,秒杀倒计时时间动态从服务器取。
  2. 控制能进入下单页面入口的用户数,其他用户直接进入秒杀结束页面,也就是说抛弃大量无效请求。

大型网站典型故障案例分析

写日志引发故障

故障现象:某应用服务器发布不久就出现多台服务器相继报警,硬盘可用空间低于警戒值,并且很快有服务器宕机。登录到线上服务器,发现log文件迅速增加,不断消耗磁盘空间。

原因分析:这是一个普通的应用服务器集群,不需要存储数据,因此服务器配置的是一块100GB的硬盘,安装完操作系统、Web服务器、Java虚拟机、应用程序后,空闲空间只有几十GB了,正常情况足够了,但是应用开发人员将log输出的level全局配置为Debug,导致一次简单的Web请求产生大量log数据,在高并发的用户请求下,很快就消耗完磁盘空间。

经验教训

  • 应用程序自己的日志和第三方组件日志输出要分别配置。
  • 检查log配置文件,日志输出级别至少为Warn,并检查log输出代码调用,调用级别要符合其日志级别。
  • 有些开源第三方组件会不恰当地输出太多Error日志,需要关闭这些第三方库的日志输出。

高并发访问数据库引起的故障

故障现象:某应用发布后,数据库Load居高不下,远超正常水平,持续报警。

原因分析:检查数据库,发现报警是因为某条SQL引起的,这条SQL是一条简单的有索引的数据查询,不应该引起报警。继续检查,发现该SQL执行频率非常高,远超正常水平。追查这条SQL,发现被网站首页应用调用,首页是访问最频繁的网页,这条SQL被首页调用,也就频繁执行了。

经验教训

  • 首页不应访问数据库,首页需要的数据可以从缓存服务器或者搜索引擎服务器获取。
  • 首页最好是静态的。

高并发情况下锁引起的故障

故障现象:某应用服务器不定时地因为响应超时报警,但很快又超时解除,恢复正常,如此反复,让运维人员很苦恼。

原因分析:程序中某个单例对象中多处使用了synchronized(this),由于this对象只有一个,所有的并发请求都要排队获得这把唯一的锁。一般情况下,都是一些简单操作,获得锁、迅速完成操作、释放锁,不会引起线程排队。但是某个需要远程调用的操作也被加了synchronized(this),这个操作只是偶尔会被执行,但是每次执行都需要较长的时间才能完成,这段时间锁被占用,所有用户线程都要等待,响应超时,这个操作完成后释放锁,其他线程迅速执行,超时解除。

经验教训

  • 使用锁操作要谨慎。

缓存引起的故障

故障现象:没有新应用发布,但是数据库服务器Load突然飙升,并很快失去响应。DBA将数据库访问切换到备机,Load也很快飙升,失去响应。最终引发网站全部瘫痪。

原因分析:缓存服务器在网站服务器集群中的地位一直比较低,服务器配置和管理级别比其他服务器要低一些。人们认为缓存是改善性能的手段,丢失一些缓存也没什么问题,有时候关闭一两台缓存服务器也确实对应用没有明显影响,所以长期疏于管理。结果这次一个缺乏经验的工程师关闭了缓存服务器中全部的十几台服务器,导致网站全部瘫痪的重大事故。

经验教训

  • 当缓存已经不仅仅是改善性能,而是成为网站架构不可或缺的一部分时,对缓存的管理就需要提高到和其他服务器一样的级别。

应用启动不同步引起的故障

故障现象:某应用发布后,服务器立即崩溃。

原因分析:应用程序Web环境使用Apache+JBoss模式,用户请求通过Apache转发JBoss。发布时,Apache和JBoss同时启动,JBoss启动需要加载很多应用初始化,花费时间比较长,结果JBoss还没有完全启动,Apache就已经启动完毕接收用户请求,大量请求阻塞在JBoss进程中,最终导致JBoss崩溃。

经验教训

  • 所有应用都启动完成后才能对外服务,可以先启动耗时较长的应用如JBoss,在脚本中不断请求以判断是否启动成功,启动成功后再启动Apache。

大文件读写独占磁盘引起的故障

故障现象:某应用主要功能是管理用户图片,接到部分用户投诉,表示上传图片非常慢,原来只需要一两秒,现在需要几十秒,有时等半天结果浏览器显示服务器超时。

原因分析:图片需要使用存储,最有可能出错的地方是存储服务器。检查发现大部分文件只有几百KB,而有几个文件非常大,有数百兆,读写这些大文件一次需要几十秒,这段时间,磁盘基本被这个文件操作独占,导致其他用户的文件操作缓慢。

经验教训

  • 存储的使用需要根据不同文件类型和用途进行管理,图片都是小文件,应该使用专用的服务器,不能和大文件共用存储。批处理用的大文件可以使用其他类型的分布式文件系统。

滥用生产环境引起的故障

故障现象:监控发现某个时间段内,某些应用突然变慢,内部网络延迟非常高。

原因分析:检查发现,该时段内网卡流量也下降了,找不到原因。过了一段时间才知道,有工程师在线上生产环境进行性能压力测试,占用了大量交换机带宽。

经验教训

  • 访问线上生产环境要规范,不小心就会导致大事故。
  • 修改线上数据的SQL,如果没有DBA,可以请其他工程师进行SQL Review。

不规范流程引起的故障

故障现象:某应用发布后,数据库Load迅速飙升,超过报警值,回滚发布后报警消除。

原因分析:发现该应用发布后出现大量数据库读操作,而这些数据本来该从分布式缓存读取。检查缓存,发现数据已经被缓存了,检查代码,发现访问缓存的那行代码被注释掉了。原来工程师在开发的时候,为了测试方便,特意注释掉读取缓存的代码,开发完后忘记去掉注释,直接提交到代码库被发布到线上环境(为了测试方便确实经常这么做,这从另一方面说明提交代码需要他人Code Review的意义)。

经验教训

  • 代码提交前使用diff命令进行代码比较,确认没有提交不该提交的代码。
  • 在合入分支或主干时,先自己Review,再请其他人进行CR。

不好的编程习惯引起的故障

故障现象:某应用更新某功能后,有少量用户投诉无法正常访问该功能,一点击就显示出错信息。

原因分析:分析这些用户,都是第一次使用该功能。检查代码,发现程序根据历史记录构造一个对象,如果该对象为null,就会导致空指针异常。

经验教训

  • 程序在处理一个输入的对象时,如果不能明确该对象是否为空,必须做空指针判断。
  • 程序在调用其他方法时,输入的对象尽量保证不是null,必要时构造空对象(使用空对象模式)。
posted @ 2023-06-03 21:43  sjmuvx  阅读(55)  评论(0编辑  收藏  举报