大型网站技术

大型网站开发

网站架构
缓存和数据一致性
分布式事务
负载均衡和高可用
微服务
消息队列
秒杀系统

大型网站特点

海量数据 高并用 高可用 需求多

容量的估算

常见容量估算:数据量 并发量 带宽 CPU|MEM|DISK

容量评估步骤
1.评估总访问量
2.评估平均访问量 QPS
3.评估高峰 QPS
4.评估系统 单机极限
5.计算容量

常见性能测试方案

ab
JMeter
LoadRunner

系统负载

System Load :系统CPU繁忙程度的度量 有多少进程在等待被CPU调用 (进程等待队列的长度)
Load Average : 一段时间内系统的平均负载 1,5,15分钟
top uptime w

负载的定义

Load < 1单核
Load = 1单核
Load > 1单核
Load = 双核

应用服务器和数据库分离

如何选择(webapp和数据库对机器的要求)配置
应用服务器需要处理大量的逻辑 需要强大的CPU
文件服务器需要存储大量用户上传的文件 需要更大的硬盘
数据库服务器需要更快磁盘检索和数据缓存 需要更大的内存和硬盘

应用服务器集群

假设数据库服务器无压力情况下 把应用服务器从一台变为两台甚至多台
把用户请求分散到不同服务器 提高负载能力

用户的请求由谁来转发到到具体的应用服务器
负载均衡的转发的算法有哪些?
用户如果每次访问到的服务器不一样,那么如何维护session的一致性

负载均衡

Load Balance 是分布式架构考虑因素
将请求/数据均匀分摊到多个操作单元上执行 关键在于均匀

负载均衡架构

客户端层 反向代理层nginx 站点层 服务层 数据层
每一个下游有多个上游调用 每一个上游均匀访问下游 就是负载均衡

负载均衡工作方式

http重定向
当http代理(比如浏览器)向web服务器请求某个URL后,web服务器可以通过http响应头信息中的Location标记来返回一个新的URL。这意味着HTTP代理需要继续请求这个新的URL,完成自动跳转。
缺点:吞吐率限制
优点:不需要任何额外支持
适用场景:我们需要权衡转移请求的开销和处理实际请求的开销,前者相对于后者越小,那么重定向的意义就越大,例如下载。你可以去很多镜像下载网站试下,会发现基本下载都使用了Location做了重定向。

DNS负载均衡
DNS 负责提供域名解析服务,当访问某个站点时,实际上首先需要通过该站点域名的DNS服务器来获取域名指向的IP地址,在这一过程中,DNS服务器完成了域名到IP地址的映射,同样,这样映射也可以是一对多的,这时候,DNS服务器便充当了负载均衡调度器。
使用命令:dig google.cn 查看DNS的配置
DNS服务器可以在所有可用的A记录中寻找离用记最近的一台服务器。
缺点: DNS记录缓存更新不及时、策略的局限性、不能做健康检查
优点:可以寻找最近的服务器,加快请求速度。
适用场景:一般我们在多机房部署的时候,可以使用。

反向代理负载均衡
在用户的请求到达反向代理服务器时(已经到达网站机房),由反向代理服务器根据算法转发到具体的服务器。常用的apache,nginx都可以充当反向代理服务器。反向代理的调度器扮演的是用户和实际服务器中间人的角色。
工作在HTTP层(七层)
缺点:代理服务器成为性能的瓶颈,特别是一次上传大文件。
优点:配置简单、策略丰富、维持用户会话、可根据访问路径做转发。
适用场景:请求量不高的,简单负载均衡。后端开销较大的应用。

IP负载均衡
工作在传输层(四层)
通过操作系统内黑修改发送来的IP数据包,将数据包的目标地址修改为内部实际服务器地址,从而实现请求的转发,做到负载均衡。lvs的nat模式。
缺点:所有数据进出还是要过负载机器,网络带宽成为瓶颈。
优点:内核完成转发,性能高。
适用场景:对性能要求高,但对带宽要求不高的的应用。视频和下载等大带宽的应用,并不适合使用。

数据链路层的负载均衡
工作在数据链路层(二层)
在请求到达负载均衡器后,通过配置所有集群机器的虚拟ip和负载均衡器相同,再通过修改请求的mac地址,从而做到请求的转发。与IP负载均衡不一样的是,当请求访问完服务器之后,直接返回客户。而无需再经过负载均衡器。 LVS DR(Direct Routing)模式。
缺点:配置复杂
优点:由集群机器直接返回,提高了出口带宽。
适用场景:大型网站使用最广的一种负载均衡方法。

负载均衡常见策略

轮循(Round Robin)
能力比较弱的服务器导致能力较弱的服务器最先超载。

加权轮循(Weighted Round Robin)
这种算法解决了简单轮循调度算法的缺点:传入的请求按顺序被分配到集群中服务器,但是会考虑提前为每台服务器分配的权重。

最少连接数(Least Connection)
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。

加权最少连接(Weighted Least Connection)
如果服务器的资源容量各不相同,那么“加权最少连接”方法更合适:在考虑连接数的同时也权衡管理员根据服务器情况定制的权重。

源IP哈希(Source IP Hash)
这种方式通过生成请求源IP的哈希值,并通过这个哈希值来找到正确的真实服务器。这意味着对于同一主机来说他对应的服务器总是相同。

随机
通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。实际效果接近轮询的结果。

常见负载均衡方案

硬件
常见的硬件有比较昂贵的NetScaler、F5、Radware和Array等商用的负载均衡器,优点就是有专业的维护团队维护、性能高。缺点就是昂贵,所以对于规模较小的网络服务来说暂时还没有需要使用。

软件
Nginx(1w)、HAProxy(5-10w)、LVS(十多万) F5 BIG-IP(百万)

负载均衡如何维持session 会话

1.把同一个用户在某一个会话中的请求 都分配到固定的某一台服务器中 常见的负载均衡算法ip_hash
2.session数据集中存储 session数据集中存储就是利用数据库或者缓存来存储session数据,实现了session和应用服务器的解耦
3.使用cookie来代替session的使用。

数据库读写分离

一主多从,读写分离,冗余多个读库

但随着访问量的的提高,数据库的负载也在慢慢增大。大部分互联网的业务都是“读多写少”的场景,数据库层面,读性能往往成为瓶颈。如下图:业界通常采用“一主多从,读写分离,冗余多个读库”的数据库架构来提升数据库的读性能。

mysql复制原理

1.master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events)
2.slave将master的binary log events拷贝到它的中继日志(relay log)
3.slave重做中继日志中的事件,将改变反映它自己的数据。

复制无法扩展写操作

上一个例子中,原因是因为无法像扩展读操作一样,方便的扩展写操作。所以MySQL的复制功能只是用来扩展读操作的,无法扩展写操作。

增加写负载的唯一方法就是分库分表

DB主从架构一致性优化方法

半同步复制
系统先对DB-master进行了一个写操作
写主库等主从同步完成,写主库的请求才返回
读从库,读到最新的数据(如果读请求先完成,写请求后完成,读取到的是“当时”最新的

写操作的脚本强制读主库,可借助数据库代理实现
将统一脚本中的后续读操作都从主库读取。可以配置数据库代理自动实现。比如db_proxy等

缓存记录写key法
将某个库上的某个key要发生写操作,记录在cache里,并设置“经验主从同步时间”的cache超时时间,例如500ms
修改数据库
读库时先到cache里查看,对应库的对应key有没有相关数据如果cache hit,有相关数据,说明这个key上刚发生过写操作,此时需要将请求路由到主库读最新的数据
如果cache miss,说明这个key上近期没有发生过写操作,此时将请求路由到从库,继续读写分离

用搜索引擎和缓存来缓解读库的压力

数据库做读库的话,常常对模糊查找力不从心,即使做了读写分离,这个问题还未能解决。
以我们所举的交易网站为例,经常根据商品的标题来查找对应的商品。对于这种需求,一般我们都是通过like功能来实现的,但是这种方式的代价非常大。此时我们可以使用搜索引擎的倒排索引来完成。
缓存是一种提高系统读性能的常见技术,对于读多写少的应用场景,我们经常使用缓存来进行优化。

常用搜索引擎

Lucene更像是一个SDK。有完整的API族以及对应的实现。你可以利用这些在自己的应用里实现高级查询(基于倒排索引技术的)。是一套信息检索工具包,但并不包含搜索引擎系统,因此在使用Lucene时你仍需要关注如数据获取、解析、分词等方面的东西。
Solr是基于Lucene做的。它更接近于我们认识到的搜索引擎系统,它是一个搜索引擎服务,通过各种API可以让你的应用使用搜索服务,而不需要将搜索逻辑耦合在应用中。
Elasticsearch也是基于Lucene做的。但它比Solr在实时搜索上性能更高,使用上也更傻瓜简单一些。目前使用者也越来越多,推荐使用。
Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL,PostgreSQL做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索

缓存与数据库一致性

更新缓存 VS 淘汰缓存

更新缓存的代价很小,此时我们应该更倾向于更新缓存,以保证更高的缓存命中率
如果余额是通过很复杂的数据计算得出来的,倾向于删除缓存。
淘汰缓存操作简单,并且带来的副作用只是增加了一次cache miss,建议作为通用的处理方式。

先操作数据库 vs 先操作缓存

当写操作发生时,假设淘汰缓存作为对缓存通用的处理方式,又面临两种抉择:
先写数据库,再删除缓存。
先删除缓存,再写数据库。
对于一个不能保证事务性的操作,一定涉及“哪个任务先做,哪个任务后做”的问题。
解决这个问题的方向是:如果出现不一致,谁先做对业务的影响较小,就谁先执行。

先写数据库,再淘汰缓存:
第一步写数据库操作成功,第二步淘汰缓存失败,则会出现DB中是新数据,Cache中是旧数据,数据不一致。

先淘汰缓存,再写数据库
第一步淘汰缓存成功,第二步写数据库失败,则只会引发一次Cache miss。

缓存的一致性哈希算法

拆分

垂直拆分:按功能或业务将原来一个表中的内容拆分成多个表,或者一个库拆分成多个库。

水平拆分:将同类型的数据分别存放与相同结构的多个表中。
一个好的分表键通常是数据库中非常重要的核心实体的主键。比如用户ID.
user_id % 5 = 0
user_id % 5 = 1

跨库join的问题

在拆分之前,系统中很多列表和详情页所需的数据是可以通过sql join来完成的。而拆分后,数据库可能是分布式在不同实例和不同的主机上,join将变得非常麻烦。

1.全局表
系统中所有模块都可能会依赖到的一些表在各个库中都保存。

2.字段冗余
“订单表”中保存“卖家Id”的同时,将卖家的“Name”字段也冗余,这样查询订单详情的时候就不需要再去查询“卖家用户表”

3.数据同步
定时A库中的tbl_a表和B库中tbl_b关联,可以定时将指定的表做主从同步。

如果join很复杂,首先需要考虑分库的设计是否合理,其次可以考虑使用hadoop或者spark来解决。

垂直拆库,跨库事务的问题

CAP:一致性、可用性和分区容错性
分布式事务的实现:二阶段提交
两阶段提交协议(Two-phase Commit,2PC)经常被用来实现分布式事务。

一般分为事务协调器TC和资源管理SI两种角色,这里的资源管理就是具体的数据库。

1.应用程序发起一个开始请求到TC;
2.向所有的Si发起消息。
TC给B的prepare消息是通知余额宝数据库相应账目增加1w。
TC给A的prepare消息是通知支付宝数据库相应账目扣款1万
3.Si收到消息后,执行具体本机事务,但不会进行commit,锁定资源等待提交,成功返回,失败返回
4.TC收集所有执行器返回的消息,
如果所有执行器都返回yes,那么给所有执行器发生送commit消息,执行器收到commit后执行本地事务的commit操作
如果有任一个执行器返回no,那么给所有执行器发送abort消息,执行器收到abort消息后执行事务abort操作

如何保障系统的高可用

1.单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。
2.高可用保证的原则是 “冗余”。只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。
3.高可用架构设计的核心准则是:冗余。
4.有了冗余之后,还不够,每次出现故障需要人工介入恢复势必会增加系统的不可服务实践。所以,又往往是通过“自动故障转移”来实现系统的高可用。

高可用和负载均衡的软件方案

LVS和haproxy都是实现的负载均衡的作用。
keepalived和heartbeat都是提高高可用性的,避免单点故障。
Keepalived和heartbeat简单区别:
keepalived配置简单, heartbeat功能强大配置丰富。
LVS和keepalived都是通过检测vrrp数据包来工作,因此keepalived更适合与lvs搭配。
Heartbeat功能丰富更适合业务使用。
在keepalived实现不了的业务中选择Heartbeat。
日常工作中常用的搭配方式是:
LVS+ keepalived和haproxy+heartbeat

消息队列

消息队列常用的应用场景有哪些?
主要就是:应用解耦合、异步操作、流量削锋。

异步操作

将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。

用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒

应用解耦

用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口

解耦以后,假如,在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了

流量削减

流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。

秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
可以控制活动的人数;
可以缓解短时间内高流量压垮应用

常见消息队列软件

RabbitMQ
使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正因如此,它非常重量级,更适合于企业级的开发。

Redis
Redis是一个基于Key-Value对的NoSQL数据库,开发维护很活跃。虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。

ZeroMQ
号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。其中,Twitter的Storm中默认使用ZeroMQ作为数据流的传输。

ActiveMQ
类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。

Kafka/Jafka
是一个高性能跨语言分布式Publish/Subscribe消息队列系统。
快速持久化,可以在O(1)的系统开销下进行消息持久化;
高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;
完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,
自动实现复杂均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。

缓存

缓存是大型网站中的重要组件,主要解决高并发,大数据场景下,热点数据访问的性能问题。适用于读多写少的场景。
从我们的使用角度来分,主要有:
CDN缓存
反向代理缓存
分布式缓存

CDN缓存

CDN主要解决将数据缓存到离用户最近的位置,一般缓存静态资源文件(页面,脚本,图片,视频,文件等)。
其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。

反向代理缓存

反向代理位于应用服务器机房,处理所有对WEB服务器的请求。
反向代理一般缓存静态资源,动态资源转发到应用服务器处理。常用的缓存应用服务器有Varnish,Ngnix,Squid。

代理缓存比较
varnish和squid是专业的cache服务,
nginx需要第三方模块支持;
Varnish采用内存型缓存,避免了频繁在内存、磁盘中交换文件,性能比Squid高;
Varnish由于是内存cache,所以对小文件如css,js,小图片啥的支持很棒,后端的持久化缓存可以采用的是Squid或ATS;
Squid功能全而大,适合于各种静态的文件缓存,一般会在前端挂一个HAProxy或nginx做负载均衡跑多个实例;
Nginx采用第三方模块ncache做的缓冲,性能基本达到varnish,一般作为反向代理使用,可以实现简单的缓存。

分布式缓存

分布式缓存,主要指缓存用户经常访问数据的缓存,数据源为数据库。一般起到热点数据访问和减轻数据库压力的作用

分布式缓存

Redis
Redis 是一个开源(BSD许可)的,基于内存的,多数据结构存储系统。可以用作数据库、缓存和消息中间件。 支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。

集群方案:
Twemproxy
Codis
官方近期推出的Redis Cluster

Memcache与Redis的比较
1数据结构:Memcache只支持key value存储方式,Redis支持更多的数据类型,比如Key value,hash,list,set,zset;
2多线程:Memcache支持多线程,redis支持单线程;CPU利用方面Memcache优于redis;
3持久化:Memcache不支持持久化,Redis支持持久化;
4内存利用率:memcache高,redis低(采用压缩的情况下比memcache高);
5过期策略:memcache过期后,不删除缓存,会导致下次取数据数据的问题,Redis有专门线程,清除缓存数据;

秒杀系统

特点:库存只有一份,所有人会在集中的时间读和写这些数据。但是最终成功的人却很少

思路:将请求尽量拦截在系统上游。

1浏览器层请求拦截
JS层面,限制用户在x秒之内只能提交一次请求。
2站点层(webServer)请求拦截与页面缓存
同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面。
3服务层(SOA)请求拦截与数据缓存
对于写请求,做请求队列,每次只透过有限的写请求去数据层,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”
对于读请求,cache来抗,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题。
4数据层处理真正的下单请求

posted @ 2018-01-11 09:38  WWSASUKE  阅读(181)  评论(0编辑  收藏  举报