高并发解决(如何设计一个秒杀系统)

如果给你设计一个抢购系统,最容易想的处理思路是什么样的?看看是不是这样:
1.当有人来购买时,取出库存数据;
2.判断库存是否够;
3.如果够减掉库存,保存到数据库中
4.反馈提示用户抢购成功。

这样的做法会存在什么问题?

  1. 每次都跟数据库交互,高并发的情况下,数据库吃不消,宕机。
  2. 多进程或多线程的情况下会导致数据脏读,导致秒杀产品超卖,或者存在读写锁冲突,请求超时。

 

What & How?

设计思路:将请求拦截在系统上游,降低下游压力。

系统配置(部署方式、服务器、网络、CDN等)=> 前端 => 中间代理 => 后端服务=> 数据库层 => 监控系统

   

 

  • 系统配置
  1. 秒杀系统存在的挑战?

a.短暂的高流量,对现有网站业务造成冲击。

b.秒杀是一个网站营销的一个附加活动,时间短,并发量大。

c.如果和网站原有应用部署在一起,必然会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪。

 

 

解决方案:可采用“独立部署”,避免对当前系统造成影响。

 

  1. 网络及服务器带宽增长压力,网络带宽的问题比超过平时好多倍。

如果秒杀页面的大小为200K,如果最大并发数为10000次,那么需要的网络和服务器带宽是2G(200K×10000)。这些网络带宽是因为秒杀活动新增的,超过网站平时使用的带宽。

 

解决方案:增加服务器进行负载均衡,平摊并发量。租借秒杀活动网络带宽,为了减轻服务器的压力,需要将秒杀商品页面缓存在CDN,同样CDN服务器也需要临时租借带宽。

 

CDN:全称是Content Delivery Network,即内容分发网络。 CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。

 

  1. 服务降级

把好资源留给当前重要的成员(高考)

 

  • 前端

1.用户秒杀开始前,通过不断刷新浏览器来保证不会错过秒杀活动,频繁的访问程序、数据库会对应用服务器和数据库服务器造成负载压力。

2.频繁点击秒杀按钮,增加了很多没必要的请求。

3.绕过按钮点击,直接通过直接调用API去请求。

 

解决方案:秒杀商品页面静态化和缓存化,减少后端请求,将商品描述、参数、详情,全部写到一个静态页面,不用进行程序的逻辑处理,不需访问数据库,不用部署动态的服务器和数据库服务器;这边从产品层面可以让按钮点击一次之后就变成灰色,避免重复点击;针对API调用,可以设置在规定的时间内,一个IP只能请求一次,其他时间返回缓存。

 

4.避免直接下单

秒杀的游戏规则是到了秒杀才能开始对商品下单购买,在此时间点之前,只能浏览信息不可下单。而下单页面也是一个普通的URL,如果得到这个URL,不用等到秒杀开始就可以下单了。

 

解决方案:动态生成随机下单页面的URL,为了避免用户直接访问下单URL,需要将URL动态化,用随机数作为参数,只能秒杀开始的时候才生成。

 

  • 中间代理

可利用负载均衡(例如反向代理Nginx等)使用多个服务器并发处理请求,减小服务器压力。

 

四、后端服务

 

限流:屏蔽掉无用的流量,允许少部分流量流向后端。

 

削峰:瞬时大流量峰值容易压垮系统,解决这个问题是重中之重。常用的消峰方法有异步处理、缓存和消息中间件等技术。

 

异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。

 

内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。

 

消息队列:消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。

 

 

五、数据库

数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧

 

常用解决方案:

1.利用行级锁,先扣库存,成功后再创建订单,防止超卖。
2.唯一索引,防止重复购买。
3.数据库读写分离,如mycat。

4.乐观锁、悲观锁等锁机制

 

 

六、监控系统

A: 操作系统的cpu, memory,i/o 。

B: 各个系统的log:访问次数,warning, error log的条数。

C: dba对数据库的监控。

D: 业务层面的监控:每分钟各个子系统的交易量(DB查询)。

E: 核心路径监控,非常重要的部分,单笔请求在交易系统中的流程,一笔实时交易,从接收到http请求,到各个子系统之间的互相访问,到DB的读写,到返回抢购结果。

 

案例:利用消息中间件和Redis缓存实现。

Redis是一个分布式缓存系统,支持多种数据结构,可利用Redis轻松实现一个强大的秒杀系统。

我们可以采用Redis 最简单的key-value数据结构,用一个原子类型的变量值(AtomicInteger)作为key,把用户id作为value,库存数量便是原子变量的最大值。对于每个用户的秒杀,我们使用RPUSH key value(插入命令)插入秒杀请求,当插入的秒杀请求数达到上限时,停止所有后续插入。

然后我们可以再启动多个工作线程,使用 LPOP key 读取秒杀成功者的用户id,然后再操作数据库做最终的下订单减库存操作。当然,上面Redis也可以替换成消息中间件如ActiveMQ、RabbitMQ等,也可以将缓存和消息中间件 组合起来,缓存系统负责接收记录用户请求,消息中间件负责将缓存中的请求同步到数据库。

 

(1)使用Redis中间件缓存动态资源的好处?

提高访问速度,减少对数据库的链接的打开、关闭,

 

(2)为什么不用JVM内存而使用Redis作为缓存呢?

JVM 内存较小,隔一段时间会自动进行垃圾回收。

   JVM和业务程序绑定在一起了,如果程序出错,JVM也会停止,这样就导致缓存数据丢失。

   如果使用Redis,除了缓存比较大之外,还实现了缓存数据和业务程序的分离,即使运行程序出现错误,也不会影响缓存。

 

 

 

 

抢红包案例:

 

架构,可参考秒杀系统。

f286303af66c5d408d12c0d214129438385.jpg

 

做法一:使用加锁操作先占有锁资源,再占有红包。

可以使用分布式全局锁的方式(各种分布式锁组件或者数据库锁),先申请 lock 该红包资源且成功后再做后续操作。优点是不会出现脏数据问题,某一个时刻只有一个应用线程持有 lock,红包只会被至多一个用户抢到,数据一致性有保障。缺点是,所有请求同一时刻都在抢红包 A,下一个时刻又都在抢红包 B,并且只有一个抢成功,其他都失败,效率很低。

 

做法二:单独开发请求排队调度模块。

排队模块接收用户的抢红包请求,以 FIFO 模式保存下来,调度模块负责 FIFO 队列的动态调度,一旦有空闲资源,便从队列头部把用户的访问请求取出后交给真正提供服务的模块处理。优点是,具有中心节点的统一资源管理,对系统的可控性强,可深度定制。缺点是,所有请求流量都会有中心节点参与,效率必然会比分布式无中心系统低,并且,中心节点也很容易成为整个系统的性能瓶颈。

 

做法三:巧用 Redis 特性,使其成为分布式序号生成器(我们最终采用的做法)。

前文已经提到,红包系统所使用的红包数据都是预先生成好的,我们使用数字 ID 来标识,这个 ID 是全局唯一的,所有围绕红包的操作都使用这个 ID 作为数据的关联项。在实际的请求流量过来时,我们采用了"分组"处理流量的方式,如下图 3 所示。

 

访问请求被负载均衡器分发到每个 Service Cluster 的分组 Bucket,一个分组 Bucket 包含若干台应用容器、独立的数据库和 Redis 节点。Redis 节点内存储的是这个分组可以分发的红包 ID 号段,利用 Redis 特性实现红包分发,各服务节点通过 Redis 原语获取当前 拆到的红包。这种做法的思路是,Redis 本身是单进程工作模型,来自分布式系统各个节点的操作请求天然的被 Redis Server 做了一个同步队列,只要每个请求执行的足够快,这个队列就不会引起阻塞及请求超时。而本例中我们使用了 DECR 原语,性能上是可以满足需求的。Redis 在这里相当于是充当一个分布式序号发生器的功能,分发红包 ID。

 

 

完善监控

系统在线上运行过程中,我们需要对运行情况实时获取信息,以便能够对出现的问题进行排查定位,及时采取措施。所以我们必须有一套有效的监控系统,能够帮我们观测到关键的指标。在实际的操作层面,我们主要关注了如下指标:

 

服务接口的性能指标

借助系统的请求日志,观测服务接口的 QPS,接口的实时响应总时间。同时通过 HTTP 的状态码观测服务的语义层面的可用性。

 

系统健康度

结合总的性能指标以及各个模块应用层的性能日志,包括模块接口返回耗时,和应用层日志的逻辑错误日志等,判断系统的健康度。

 

整体的网络状况

尽量观测每个点到点之间的网络状态,包括应用服务器的网卡流量、Redis 节点、数据库节点的流量,以及入口带宽的占用情况。如果某条线路出现过高流量,便可及时采取扩容等措施缓解。

 

服务基础设施

应用服务器的 CPU、Memory、磁盘 IO 状况,缓存节点和数据库的相应的数据,以及他们的连接数、连接时间、资源消耗检测数据,及时的去发现资源不足的预警信息。

 

对于关键的数据指标,在超过预估时制定的阈值时,还需要监控系统能够实时的通过手机和邮件实时通知的方式让相关人员知道。另外,我们在系统中还做了若干逻辑开关,当某些资源出现问题并且自动降级和过载保护模块失去效果时,我们可以根据状况直接人工介入,在服务不停机的前提下,手动触发逻辑开关改变系统逻辑,达到快速响应故障,让服务尽快恢复稳定的目的。

 

服务降级

当服务器压力剧增的时候,如果某些依赖的服务设施或者基础组件超出了工作负荷能力,发生了故障,这时候极其需要根据当前的业务运行情况对系统服务进行有策略的降级运行措施,使得核心的业务流程能够顺利进行,并且减轻服务器资源的压力,最好在压力减小后还能自动恢复升级到原工作机制。

 

我们在开发红包系统时,考虑到原有 IDC 机房的解决方案对于弹性扩容和流量带宽支持不太完美,选择了使用 AWS 的公有云作为服务基础环境。对于第三方的服务,缺少实践经验的把握,于是从开发到运维过程中,我们都保持了一种防御式的思考方式,包括数据库、缓存节点故障,以及应用服务环境的崩溃、网络抖动,我们都认为随时可能出问题,都需要对应的自动替换降级策略,严重时甚至可手动触发配置开关修改策略。当然,如果组件自身具有降级功能,可以给上层业务节约很多成本资源,要自己实现全部环节的降级能力的确是一件比较耗费资源的事情,这也是一个公司技术慢慢积累的过程。

转载于:https://my.oschina.net/u/4085644/blog/3017015

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值