目录
一、秒杀系统
1、秒杀系统的设计(前端-后端-数据库-监控系统-中间代理)
2、秒杀系统的架构图
3、秒杀系统的案例
二、高并发
1、高并发的概念
2、高并发的问题
3、高并发的解决方案
4、简单模拟高并发
三、(服务|缓存)雪崩
秒杀系统 | 问题 | 解决 |
问题(3个) | 1.(a)每次都跟数据库交互,高并发的情况下,数据库吃不消,宕机。(b)多进程或多线程的情况下会导致数据脏读,导致秒杀产品超卖,或者存在读写锁冲突,请求超时。 | (a)将请求拦截在系统上游,降低下游压力。(b)数据库行锁,先扣库存,成功后再创建订单,防止超卖。 |
2.秒杀是一个网站营销的一个附加活动,时间短,并发量大,短暂的高流量,对现有网站业务造成冲击。 | 增加服务器进行负载均衡,平摊并发量。租借秒杀活动网络带宽,为了减轻服务器的压力,需要将秒杀商品页面缓存在CDN,同样CDN服务器也需要临时租借CDN带宽。 |
|
3.如果和网站原有应用部署在一起,必然会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪。 | 采用“独立部署”,避免对当前系统造成影响。 | |
前端(2个) | 1.秒杀请求频繁:a.用户秒杀开始前频繁的访问程序、对服务器造成负载压力。b.频繁点击秒杀按钮,增加了很多没必要的请求。c.绕过按钮点击,直接通过直接调用API去请求。 | 1.秒杀商品页面静态化和缓存化,减少后端请求,将商品描述、参数、详情,全部写到一个静态页面,不用进行程序的逻辑处理,不需访问数据库,不用部署动态的服务器和数据库服务器;这边从产品层面可以让按钮点击一次之后就变成灰色,避免重复点击;针对API调用,可以设置在规定的时间内,一个IP只能请求一次,其他时间返回缓存。 |
2.直接下单:秒杀的游戏规则是到了秒杀才能开始对商品下单购买,在此时间点之前,只能浏览信息不可下单。而下单页面也是一个普通的URL,如果得到这个URL,不用等到秒杀开始就可以下单了。 | 2.动态生成随机下单页面的URL,为了避免用户直接访问下单URL,需要将URL动态化,用随机数作为参数,只能秒杀开始的时候才生成。 | |
后端(5个) | 1.限流:屏蔽掉无用的流量,允许少部分流量流向后端。 | |
2.削峰:瞬时大流量峰值容易压垮系统,解决这个问题是重中之重。常用的消峰方法有异步处理、缓存和消息中间件等技术。 | ||
3.异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。 | ||
4.内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。 | ||
5.可拓展(消息队列):为支持多用户高并发,将系统设计成弹性可拓展的,流量来了拓展机器就好。消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。 | ||
数据库(3个) | 1.利用锁(单机-本地锁,集群-分布式锁),先扣库存,成功后再创建订单,防止超卖。 | |
2.唯一索引,防止重复购买。 | ||
3.数据库读写分离,如mycat。 | ||
监控系统(5个) | 1.操作系统的cpu,memory,i/o的监控。 | |
2.各个系统的log:访问次数,warning, error log的条数。 | ||
3.DBA对数据库的监控。 | ||
4.业务层面的监控:每分钟各个子系统的交易量(DB查询)。 | ||
5.核心路径监控。非常重要的部分,单笔请求在交易系统中的流程,一笔实时交易,从接收到http请求,到各个子系统之间的互相访问,到DB的读写,到返回抢购结果。 | ||
中间代理(1个) | 利用负载均衡(例如反向代理Nginx等)使用多个服务器并发处理请求,减小服务器压力。 |
其中,网络带宽:如果秒杀页面的大小为200K,如果最大并发数为10000次,那么需要的网络和服务器带宽是2G(200K×10000)。这些网络带宽是因为秒杀活动新增的,超过网站平时使用的带宽。
网络带宽:全称是Content Delivery Network,即内容分发网络。 CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
redis分布式锁的实现思路:
线程进来之后先执行redis的setnx,若是key存在就返回0,否则返回1.返回1即代表拿到锁,开始执行代码,执行完毕之后将key删除即为解锁。存在两个问题,有可能存在死锁,就是一个线程执行拿到锁之后,解锁之前的代码时出现bug,导致锁释放不出来,下一个线程进来之后一直等待上一个线程释放锁。解决方案就是加上超时时间,超时过后自行无论执行是否成功都将锁释放出来。但是又会出现第二个问题,在超时的情况下,多个线程同时等待锁释放出来,然后竞争拿到锁,此时又会出现线程不安全(多线程竞争锁)现象,解决方案是使用redis的getandset方法,其中一个线程拿到锁之后立即将value值改变,同时将oldvalue与原来的value值比较,这样就保证了多线程竞争锁的安全性。
------------------------------------------------------------------------------------------
|
(3.1)利用消息中间件和缓存实现简单的秒杀系统
Redis是一个分布式缓存系统,支持多种数据结构,可利用Redis轻松实现一个强大的秒杀系统。
a.我们可以采用Redis最简单的key-value数据结构,用一个原子类型的变量值(AtomicInteger)作为key,把用户id作为value,库存数量便是原子变量的最大值。对于每个用户的秒杀,我们使用rpush key value(插入命令)插入秒杀请求,当插入的秒杀请求数达到上限时,停止所有后续插入。
b.然后我们可以再启动多个工作线程,使用lpop key读取秒杀成功者的用户id,然后再操作数据库做最终的下订单减库存操作。
c.当然,上面Redis也可以替换成消息中间件如ActiveMQ、RabbitMQ等,也可以将缓存和消息中间件组合起来,缓存系统负责接收记录用户请求,消息中间件负责将缓存中的请求同步到数据库。
PS:(1)使用Redis中间件缓存动态资源的好处?
提高访问速度,减少对数据库的链接的打开、关闭,
(2)为什么不用JVM内存而使用Redis作为缓存呢?
JVM内存较小,隔一段时间会自动进行垃圾回收。JVM和业务程序绑定在一起了,如果程序出错,JVM也会停止,这样就导致缓存数据丢失。如果使用Redis,除了缓存比较大之外,还实现了缓存数据和业务程序的分离,即使运行程序出现错误,也不会影响缓存。
(3.2)抢红包的秒杀系统
3.2.1)架构图
做法一:使用加锁操作先占有锁资源,再占有红包。可以使用分布式全局锁的方式(各种分布式锁组件或者数据库锁),先申请 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。
3.3.3)2种优化:服务降级、完善监控
服务降级
当服务器压力剧增的时候,如果某些依赖的服务设施或者基础组件超出了工作负荷能力,发生了故障,这时候极其需要根据当前的业务运行情况对系统服务进行有策略的降级运行措施,使得核心的业务流程能够顺利进行,并且减轻服务器资源的压力,最好在压力减小后还能自动恢复升级到原工作机制。数据库、缓存节点故障,以及应用服务环境的崩溃、网络抖动,我们都认为随时可能出问题,都需要对应的自动替换降级策略,严重时甚至可手动触发配置开关修改策略。当然,如果组件自身具有降级功能,可以给上层业务节约很多成本资源,要自己实现全部环节的降级能力的确是一件比较耗费资源的事情,这也是一个公司技术慢慢积累的过程。
完善监控
系统在线上运行过程中,我们需要对运行情况实时获取信息,以便能够对出现的问题进行排查定位,及时采取措施。所以我们必须有一套有效的监控系统,能够帮我们观测到关键的指标。在实际的操作层面,我们主要关注了如下指标:
a.服务基础设施:应用服务器的 CPU、Memory、磁盘 IO 状况,缓存节点和数据库的相应的数据,以及他们的连接数、连接时间、资源消耗检测数据,及时的去发现资源不足的预警信息。
b.服务接口的性能指标:借助系统的请求日志,观测服务接口的 QPS,接口的实时响应总时间。同时通过 HTTP 的状态码观测服务的语义层面的可用性。
c.系统健康度:结合总的性能指标以及各个模块应用层的性能日志,包括模块接口返回耗时,和应用层日志的逻辑错误日志等,判断系统的健康度。
d.整体的网络状况:尽量观测每个点到点之间的网络状态,包括应用服务器的网卡流量、Redis 节点、数据库节点的流量,以及入口带宽的占用情况。如果某条线路出现过高流量,便可及时采取扩容等措施缓解。
对于关键的数据指标,在超过预估时制定的阈值时,还需要监控系统能够实时的通过手机和邮件实时通知的方式让相关人员知道。另外,我们在系统中还做了若干逻辑开关,当某些资源出现问题并且自动降级和过载保护模块失去效果时,我们可以根据状况直接人工介入,在服务不停机的前提下,手动触发逻辑开关改变系统逻辑,达到快速响应故障,让服务尽快恢复稳定的目的。
由于分布式系统的问世,高并发(High Concurrency)通常是指通过设计保证系统能够同时并行处理很多请求。通俗来讲,高并发是指在同一个时间点,有很多用户同时的访问同一API接口或者Url地址。它经常会发生在有大活跃用户量,用户高聚集的业务场景中。
a.响应时间(Response Time):系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。
b.吞吐量(Throughput):单位时间内处理的请求数量。
c.QPS(Query Per Second):每秒请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
d.并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。
服务雪崩的解决:重启与过载保护
如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。
秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回
超发问题解决:详见PHP如何解决高并发问题
1、将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回fals
2、悲观锁
3、FIFO队列-redis
4、文件锁
6、乐观锁
PS1:最大连接数越高,可以处理的并发请求越高。通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键。举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台Apache的Web服务器,配置MaxClients为500个(表示Apache的最大连接数目)。
PS2:主要影响服务器的速度从小到大:网络 < 硬盘读写速度 < 内存大小 < cpu处理速度。就Web服务器而言,Apache打开了越多的连接进程,CPU需要处理的上下文切换也越多,额外增加了CPU的消耗,然后就直接导致平均响应时间增加。因此上述的MaxClient数目,要根据CPU、内存等硬件因素综合考虑,绝对不是越多越好。可以通过Apache自带的abench来测试一下,取一个合适的值。然后,我们选择内存操作级别的存储的Redis,在高并发的状态下,存储的响应时间至关重要。网络带宽虽然也是一个因素,不过,这种请求数据包一般比较小,一般很少成为请求的瓶颈。负载均衡成为系统瓶颈的情况比较少。
5个处理高并发的业务逻辑:
a.前端:异步请求+资源静态化+cdn --->动静分离、页面缓存
b.后端:请求队列+轮询分发+负载均衡+共享缓存 --->集群和分布式、反向代理
c.数据层:redis缓存+数据分表+写队列
d.存储:raid阵列+热备
e.网络:dns轮询+DDOS攻击防护
--->
4个PHP解决高并发的一些方法:
a.应用和静态资源分离:将静态资源(js,css,图片等)放到专门的服务器中。
b.页面缓存:将应用生成的页面缓存起来可以节省大量cpu资源。对于部分页面经常变换数据的,可以使用ajax来处理。
c.集群和分布式:集群,多台服务器具有相同的功能,主要起分流的作用。分布式,将不同的业务放到不同的服务器中,处理一个请求可能需要多台服务器,进而提高一个请求的处理速度。又分为静态资源集群和应用程序集群。后者较复杂,经常要考虑session同步等问题。详见:高并发场景下的集群与分布式
d.反向代理:客户端直接访问的服务器并不是直接提供服务的服务器,它从别的服务器获取资源,然后将结果返回给用户。
代理服务器和反向代理服务器:代理服务器是代我们访获取资源,然后将结果返回。例如,访问外网的代理服务器。反向代理服务器是我们正常访问一台服务器的时候,服务器自己调用了别的服务器。代理服务器我们主动使用,是为我们服务的,不需要有自己的域名;反向代理是服务器自己使用的,我们并不知道,有自己的域名。
详见:PHP模拟高并发
详见:服务雪崩+缓存雪崩+解决方案
转载:
高并发解决(如何设计一个秒杀系统)
怎么解决高并发的后台秒杀问题
京东高并发秒杀系统
php解决高并发问题
php如何解决高并发问题?
PHP模拟高并发