大数据与高并发
一、秒杀架构设计
业务介绍
什么是秒杀?通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动
比如说京东秒杀,就是一种定时定量秒杀,在规定的时间内,无论商品是否秒杀完毕,该场次的秒杀活动都会结 束。这种秒杀,对时间不是特别严格,只要下手快点,秒中的概率还是比较大的。
淘宝以前就做过一元抢购,一般都是限量 1 件商品,同时价格低到「令人发齿」,这种秒杀一般都在开始时间 1 到 3 秒内就已经抢光了,参与这个秒杀一般都是看运气的,不必太强求。


现有业务的冲击
秒杀是营销活动中的一种,如果和其他营销活动应用部署在同一服务器上,肯定会对现有其他活动造成冲击,极端情况下可能导致整个电商系统服务宕机。
直接下订单
下单页面是一个正常的 URL 地址,需要控制在秒杀开始前,不能下订单,只能浏览对应活动商品的信息。简单来 说,需要 Disable 订单按钮。
页面流量突增
秒杀活动开始前后,会有很多用户请求对应商品页面,会造成后台服务器的流量突增,同时对应的网络带宽增加, 需要控制商品页面的流量不会对后台服务器、DB、Redis 等组件的造成过大的压力
架构设计思想

限流
由于活动库存量一般都是很少,对应的只有少部分用户才能秒杀成功。所以我们需要限制大部分用户流量,只准少量用户流量进入后端服务器。
削峰
秒杀开始的那一瞬间,会有大量用户冲击进来,所以在开始时候会有一个瞬间流量峰值。如何把瞬间的流量峰值变得更平缓,是能否成功设计好秒杀系统的关键因素。实现流量削峰填谷,一般的采用缓存和 MQ 中间件来解决。
异步
秒杀其实可以当做高并发系统来处理,在这个时候,可以考虑从业务上做兼容,将同步的业务,设计成异步处理的任务,提高网站的整体可用性。
缓存
秒杀系统的瓶颈主要体现在下订单、扣减库存流程中。在这些流程中主要用到 OLTP 的数据库,类似 MySQL、 SQLServer、Oracle。由于数据库底层采用 B+ 树的储存结构,对应我们随机写入与读取的效率,相对较低。如果我们把部分业务逻辑迁移到内存的缓存或者 Redis 中,会极大的提高并发效率。
整体架构

客户端优化
客户端优化主要有两个问题:
秒杀页面
秒杀活动开始前,其实就有很多用户访问该页面了。如果这个页面的一些资源,比如 CSS、JS、图片、商品详情 等,都访问后端服务器,甚至 DB 的话,服务肯定会出现不可用的情况。所以一般我们会把这个页面整体进行静态化,并将页面静态化之后的页面分发到 CDN 边缘节点上,起到压力分散的作用。
防止提前下单
防止提前下单主要是在静态化页面中加入一个 JS 文件引用,该 JS 文件包含活动是否开始的标记以及开始时的动态 下单页面的 URL 参数。同时,这个 JS 文件是不会被 CDN 系统缓存的,会一直请求后端服务的,所以这个 JS 文件 一定要很小。当活动快开始的时候(比如提前),通过后台接口修改这个 JS 文件使之生效。
API 接入层优化
客户端优化,对于不是搞计算机方面的用户还是可以防止住的。但是稍有一定网络基础的用户就起不到作用了,因此服务端也需要加些对应控制,不能信任客户端的任何操作。一般控制分为 2 大类:
限制用户维度访问频率
针对同一个用户( Userid 维度),做页面级别缓存,单元时间内的请求,统一走缓存,返回同一个页面。
限制商品维度访问频率
大量请求同时间段查询同一个商品时,可以做页面级别缓存,不管下回是谁来访问,只要是这个页面就直接返回。
SOA 服务层优化
上面两层只能限制异常用户访问,如果秒杀活动运营的比较好,很多用户都参加了,就会造成系统压力过大甚至宕机,因此需要后端流量控制。
对于后端系统的控制可以通过消息队列、异步处理、提高并发等方式解决。对于超过系统水位线的请求,直接采取「Fail-Fast」原则,拒绝掉。
秒杀整体流程图

秒杀系统核心在于层层过滤,逐渐递减瞬时访问压力,减少最终对数据库的冲击。通过上面流程图就会发现压力最大的地方在哪里? MQ 排队服务,只要 MQ 排队服务顶住,后面下订单与扣减库存的压力都是自己能控制的,根据数据库的压力,可 以定制化创建订单消费者的数量,避免出现消费者数据量过多,导致数据库压力过大或者直接宕机。 库存服务专门为秒杀的商品提供库存管理,实现提前锁定库存,避免超卖的现象。同时,通过超时处理任务发现已 抢到商品,但未付款的订单,并在规定付款时间后,处理这些订单,将恢复订单商品对应的库存量。
总结
核心思想:层层过滤
尽量将请求拦截在上游,降低下游的压力
充分利用缓存与消息队列,提高请求处理速度以及削峰填谷的作用
二、数据库架构发展历程

Memcached(缓存)+MySQL+垂直拆分
后来,随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web程序不再仅 仅专注在功能上,同时也在追求性能。程序员们开始大量的使用缓存技术来缓解数据库的压力,优化数据库的结构 和索引。开始比较流行的是通过文件缓存来缓解数据库压力,但是当访问量继续增大的时候,多台web机器通过文 件缓存不能共享,大量的小文件缓存也带了了比较高的IO压力。在这个时候,Memcached就自然的成为一个非常时尚的技术产品。

Mysql主从复制读写分离


三、MySQL的扩展性瓶颈
MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”, 泛指非关系型的数据库。随着互联网web2.0网站的兴 起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力 不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。
五、传统RDBMS VS NOSQL
RDBMS vs NoSQL
RDBMS SQL
高度组织化结构化数据
结构化查询语言(SQL)
数据和关系都存储在单独的表中。
数据操纵语言,数据定义语言
严格的一致性
基础事务
NoSQL
代表着不仅仅是SQL
没有声明性查询语言
没有预定义的模式
-键 - 值对存储,列存储,文档存储,图形数据库
最终一致性,而非ACID属性
非结构化和不可预知的数据
CAP定理
高性能,高可用性和可伸缩性

数据的水平拆分和垂直拆分
当我们使用读写分离、缓存后,数据库的压力还是很大的时候,这就需要使用到数据库拆分了。
数据库拆分简单来说,就是指通过某种特定的条件,按照某个维度,将我们存放在同一个数据库中的数据分散存放
到多个数据库(主机)上面以达到分散单库(主机)负载的效果。
切分模式: 垂直(纵向)拆分、水平拆分。
垂直拆分(按业务类型进行拆分)
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据 库上面,这样也就将数据或者说压力分担到不同的库上面
优点:
1. 拆分后业务清晰,拆分规则明确。
2. 系统之间整合或扩展容易。
3. 数据维护简单。
缺点:
1. 部分业务表无法join,只能通过接口方式解决,提高了系统复杂度。
2. 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
3. 事务处理复杂
水平拆分
垂直拆分后遇到单机瓶颈,可以使用水平拆分。相对于垂直拆分的区别是:垂直拆分是把不同的表拆到不同的数据
库中,而水平拆分是把同一个表拆到不同的数据库中。
相对于垂直拆分,水平拆分不是将表的数据做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中
包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中 的某些行切分到
一个数据库,而另外的某些行又切分到其他的数据库中,主要有分表,分库两种模式

优点:
1. 不存在单库大数据,高并发的性能瓶颈。
2. 对应用透明,应用端改造较少。
3. 按照合理拆分规则拆分,join操作基本避免跨库。
4. 提高了系统的稳定性跟负载能力。
缺点:
1. 拆分规则难以抽象。
2. 分片事务一致性难以解决。
3. 数据多次扩展难度跟维护量极大。
4. 跨库join性能较差。
拆分的处理难点
两张方式共同缺点
1. 引入分布式事务的问题。
2. 跨节点Join 的问题。
3. 跨节点合并排序分页问题。
针对数据源管理,目前主要有两种思路:
A. 客户端模式,在每个应用程序模块中配置管理自己需要的一个(或者多个)数据源,直接访问各个 数据库,在模块内完成数据的整合。
优点:相对简单,无性能损耗。
缺点:不够通用,数据库连接的处理复杂,对业务不够透明,处理复杂。
B. 通过中间代理层来统一管理所有的数据源,后端数据库集群对前端应用程序透明;
优点:通用,对应用透明,改造少。
缺点:实现难度大,有二次转发性能损失。
拆分原则
1. 尽量不拆分,架构是进化而来,不是一蹴而就。(SOA)
2. 最大可能的找到最合适的切分维度。
3. 由于数据库中间件对数据Join 实现的优劣难以把握,而且实现高性能难度极大,业务读取 尽量少使用多表Join - 尽量通过数据冗余,分组避免数据垮库多表join。
4. 尽量避免分布式事务。
5. 单表拆分到数据1000万以内。 切分方 范围、枚举、时间、取模、哈希、指定等
案例分析
场景一
建立一个历史his系统,将公司的一些历史个人游戏数据保存到这个his系统中,主要是写入,还有部分查询,读写比约为1:4;由于是所有数据的历史存取,所以并发要求比较高;
分析:
历史数据
写多都少
越近日期查询越频繁?
什么业务数据?用户游戏数据
有没有大规模分析查询?
数据量多大?
保留多久?
机器资源有多少?
方案1:按照日期每月一个分片
带来的问题:1.数据热点问题(压力不均匀)
方案2:按照用户取模, --by Jerome 就这个比较合适了
带来的问题:后续扩容困难
方案3:按用户ID范围分片(1-1000万=分片1,xxx)
带来的问题:用户活跃度无法掌握,可能存在热点问题
场景二
建立一个商城订单系统,保存用户订单信息。
分析:
电商系统
一号店或京东类?淘宝或天猫?
实时性要求高
存在瞬时压力
基本不存在大规模分析
数据规模?
机器资源有多少?
维度?商品?用户?商户?
方案1:按照用户取模,
带来的问题:后续扩容困难
方案2:按用户ID范围分片(1-1000万=分片1,xxx)
带来的问题:用户活跃度无法掌握,可能存在热点问题
方案3:按省份地区或者商户取模
数据分配不一定均匀
场景3
上海公积金,养老金,社保系统
分析:
社保系统
实时性要求不高
不存在瞬时压力
大规模分析?
数据规模大
数据重要不可丢失
偏于查询?
方案1:按照用户取模,
带来的问题:后续扩容困难
方案2:按用户ID范围分片(1-1000万=分片1,xxx)
带来的问题:用户活跃度无法掌握,可能存在热点问题
方案3:按省份区县地区枚举
数据分配不一定均匀
分布式事务
什么是分布式事务?
分布式事务用于在分布式系统中保证不同节点之间的数据一致性。分布式事务的实现有很多种,最具有代表性的是 由Oracle Tuxedo系统提出的XA分布式事务协议。
XA协议包含两阶段提交(2PC)和三阶段提交(3PC)两种实现,这里我们重点介绍两阶段提交的具体过程。
XA两阶段提交(2PC)
在魔兽世界这款游戏中,副本组团打BOSS的时候,为了更方便队长与队员们之间的协作,队长可以发起一个“就位 确认”的操作:
在XA分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。 在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如 果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。
当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。
在XA分布式事务的第二阶段,如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出 Commit请求。
接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将 会向事务协调者返回“完成”消息。
当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。
以上所描述的是XA两阶段提交的正向流程,接下来我们看一看失败情况的处理流程:

在XA的第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。 于是在第二阶段,事务协调节点向所有的事务参与者发送Abort请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。
以上就是XA两阶段提交协议的详细过程。
XA两阶段提交的不足
XA两阶段提交究竟有哪些不足呢?
1.性能问题
XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才
会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。
2.协调者单点故障问题
事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
3.丢失消息导致的不一致问题。
在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。
如果避免XA两阶段提交的种种问题呢?有许多其他的分布式事务方案可供选择:
XA三阶段提交(3PC)
XA三阶段提交在两阶段提交的基础上增加了CanCommit阶段,并且引入了超时机制。一旦事物参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。但是性能问题和不一 致的问题仍然没有根本解决。
MQ事务
利用消息中间件来异步完成事务的后一半更新,实现系统的最终一致性。这个方式避免了像XA协议那样的性能问题。
TCC事务
TCC事务是Try、Commit、Cancel三种指令的缩写,其逻辑模式类似于XA两阶段提交,但是实现方式是在代码层面来人为实现。

常见的限流算法
计数器法
计数器法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数 不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来 的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说 明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter

那么滑动窗口怎么解决刚才的临界问题的呢?我们可以看上图,0:59到达的100个请求会落在灰色的格子中,而 1:00到达的请求会落在橘黄色的格 子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的 总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触 发了限流。 我再来回顾一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。
由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

令牌桶算法
令牌桶算法,又称token bucket。为了理解该算法,我们再来看一下维基百科上对该算法的示意图:


在秒杀活动中,用户的请求速率是不固定的,这里我们假定为10r/s,令牌按照5个每秒的速率放入令牌桶,桶中最
多存放20个令牌。仔细想想,是不是总有那么一部分请求被丢弃。
计数器 VS 滑动窗口
计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。
滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。
漏桶算法 VS 令牌桶算法
漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。因为默认的令牌桶算法,取走token 是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。当然我们需要具体情具体分析,只有最合适的算法,没有最优的算法。
dns域名解析负载均衡
原理:在DNS服务器上配置多个域名对应IP的记录。例如一个域名www.baidu.com对应一组web服务器IP地址, 域名解析时经过DNS服务器的算法将一个域名请求分配到合适的真实服务器上。
优点:将负载均衡的工作交给了DNS,省却了网站管理维护负载均衡服务器的麻烦,同事许多DNS还支持基于地理位置的域名解析,将域名解析成距离用户地理最近的一个服务器地址,加快访问速度吗,改善性能。
缺点:目前的DNS解析是多级解析,每一级DNS都可能化缓存记录A,当摸一服务器下线后,该服务器对应的DNS记录A可能仍然存在,导致分配到该服务器的用户访问失败。
DNS负载均衡的控制权在域名服务商手里,网站可能无法做出过多的改善和管理。
不能够按服务器的处理能力来分配负载。DNS负载均衡采用的是简单的轮询算法,不能区分服务器之间的差异, 不能反映服务器当前运行状态,所以其的负载均衡效果并不是太好。
可能会造成额外的网络问题。为了使本DNS服务器和其他DNS服务器及时交互,保证DNS数据及时更新,使地址 能随机分配,一般都要将DNS的刷新时间设置的较小,但太小将会使DNS流量大增造成额外的网络问题。
反向代理负载均衡
原理:反向代理处于web服务器这边,反向代理服务器提供负载均衡的功能,同时管理一组web服务器,它根据负载均衡算法将请求的浏览器访问转发到不同的web服务器处理,处理结果经过反向服务器返回给浏览器。
例如:浏览器访问请求的地址是反向代理服务器的地址114.100.80.10,反向代理服务器收到请求,经过负载均算法后得到一个真实物理地址10.0.03,并将请求结果发给真实无服务,真实服务器处理完后通过反向代理服务器 返回给请求用户。
优点:部署简单,处于http协议层面。
缺点:使用了反向代理服务器后,web 服务器地址不能直接暴露在外,因此web服务器不需要使用外部IP地址, 而反向代理服务作为沟通桥梁就需要配置双网卡、外部内部两套IP地址。
http重定向协议实现负载均衡
原理:根据用户的http请求计算出一个真实的web服务器地址,并将该web服务器地址写入http重定向响应中返回给浏览器,由浏览器重新进行访问。
优点:比较简单
缺点:浏览器需要两次次请求服务器才能完成一次访问,性能较差。
http重定向服务器自身的处理能力可能成为瓶颈。
使用http302响应重定向,有可能使搜索引擎判断为SEO作弊,降低搜索排名。
分层的负载均衡算法
一致性Hash算法


接下来使用如下算法定位数据访问到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在
环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。
例如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:

根据一致性哈希算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。
下面分析一致性哈希算法的容错性和可扩展性。现假设Node C不幸宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性哈希算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。
下面考虑另外一种情况,如果在系统中增加一台服务器Node X,如下图所示:
此时对象Object A、B、D不受影响,只有对象C需要重定位到新的Node X 。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。
综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
另外,一致性哈希算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。例如系统中只有两台服 务器,其环分布如下,

大数据与高并发:秒杀架构设计与数据库优化实战
本文探讨了大数据背景下秒杀活动的架构设计,包括限流、削峰、异步处理、缓存策略,以及数据库架构的发展,如Memcached、MySQL扩展性、NoSQL的选择,以及RDBMS与NoSQL的对比。还深入解析了数据库拆分、分布式事务和限流算法,如计数器法、滑动窗口和令牌桶,以及DNS负载均衡。
4789

被折叠的 条评论
为什么被折叠?



