高并发秒杀抢购系统设计

概述

概述: 介绍高并发场景中非常容易出现的设计错误以及如何避免超卖和如何提高系统负载能力。

涉及知识点:

1.多进程、多线程,进程互斥

2.数据库乐观锁、悲观锁

3.缓存数据库

4.服务器性能衡量指标

简介

高并发秒杀抢购系统设计介绍了在高并发场应用景中最容易出现的两个系统设计问题,一个涉及多进程多线程下的互斥问题,另一个涉及内存式缓存数据库的应用,具体内容涉及数据库乐观锁和悲观锁、队列的应用和高并发系统的四个衡量指标(并发数、QPS、TPS、响应时间)。

讲述内容(40分钟)

有同学提问什么是高并发,是什么样的场景才会高并发。

高并发是指在比较短的时间内有大量的访问者访问目标系统,系统负载饱和或者过载宕机。高并发的应用,我们应该都有用过或者见过,比如京东、淘宝、天猫、亚马逊的秒杀抢购还有12306的抢票。

我们在体验这些应用轻松点击按钮的时候,可能并不会像到这种高并发系统背后的技术实现难度。高并发系统都存在这几种问题,高并发读、高并发写、访问高峰突发性、反馈结果的即时性。

在抢购的时候,尤其是抢购火车票的时候,我们经常会疯狂的刷库存,几亿用户产生非常大的高并发读;

高并发的发生时间具有突发性,比如8点开始抢购、12306早上6点开始售票;

秒杀抢购成功会在短时间带来高并发的写,需要将订单数据写入后端数据库系统;

秒杀抢购成功后要将抢购结果反馈给用户,这个时间一定不能太长,一天时间肯定不行,一个小时也不行,五分钟恐怕也会有开骂,这个时间一般是在十秒以内。

这种高并发系统最容易出现的问题有两个,一个是超卖、一个是系统宕机。当然具体项目要解决的问题不止这两个,还会有防刷、防作弊等等。今天我只抽取了其中这两个最重要的问题,也是最一些新手非常容易出现的问题。

首先说说超。超卖一般就是因为程序设计的问题,导致最终实际订单数超过实际库存数量,比如要销售的库存数量为100个,最终有效订单是200个,超卖出100个。

如果给你设计一个抢购系统,最容易想的处理思路是什么样的?看看是不是这样:

1、当有人来购买时,取出库存数据;

2、判断库存是否够;

3、如果够减掉库存,保存到数据库中;

4、反馈提示用户抢购成功。

image.png

这样的思路是不是很合情合理?应该很正确,不会有问题,是吗?

举例来看看。我们有数据库表记录商品库存,我们看看伪代码的实现。

image.png

在Web服务器中,处理动态请求一般都会采用多进程多线程,这种逻辑的代码如果在多进程或者多线程的情况下会有什么问题?

首先要理解一下,我们用多进程或者多线程跑这段程序,怎么才能看出是超卖了。超卖的结果库存一定是负的吗?答案是否定,超卖最终库存不一定是负的,有可能是0,有可能是负的,也有可能是正的。为什么?目标系统的负载一般不是长时间高负载,它具有短时突发性,有可能在高峰过后,活跃用户少不形成高并发场景,最后结束的数值就可能是0也有可能是大于0。下面有一段PHP按上面逻辑编写的代码,在多线程的情况下我们测试看看。设置库存100个,线程100个相当用户100个,先想一下库存数是多少就代表了会超卖?对,大于0一定就是超卖了。看下执行结果,库存大于0。也就是说100用户已经成功下单,库存应该是0并且系统显示已经抢光了才正确,但是还有剩余库存,也就说后来用户还可以下单,那么结果是一定超卖了。

这样的程序逻辑是不行的,那么我们想办法改进一下。问题是出在了对库存读取没有锁定,导致后续进程(或者线程)读出了同样的库存。我们可以使用数据库中的悲观锁来解决这个问题。

什么是悲观锁?悲观锁就是在事务处理过程中,将数据锁定。锁定的是整个事务过程,也就是库存的整个读写过程。下面有一个利用mysql行级锁实现的简化悲观锁。
begin;
select * from xxx where xx for update;
update xxx set xxx
commit;
rollback;

image.png

按这个处理逻辑,用PHP多线程进行模拟看看结果。

看结果,最终库存为0,因此不会超卖,理论上是可行的,是一个可选方案。但是,在突发高并发环境下并不能去使用。在有数据库应用的系统,在高负载情况下,各个组成部分,例如Web服务器(Nginx、Apache、IIS)、缓存数据库(Redis、Memcached)等等,数据库系统一般是最先达到负载饱和,也就是说前端访问压力直接穿透到数据库会让数据库最早出现访问瓶颈,最终数据库响应变慢,查询慢、写入慢甚至是服务无响应、宕机。在这种情况下,重启服务器也不能解决问题。因为在前端的访问压力在数据库重启后瞬间又传导到数据库,而且这种情况下,前端访问压力可能更大。我们是不是有这种操作系统,如果抢购系统迟迟没有响应,是不是会狂刷库存然后狂点下单?对,这种情况下会导致服务器产生更高的负载。这里有个词叫“雪崩效应”。什么是雪崩效应,在我们计算机应用系统里面因为某一台计算机产生错误异常导致应用系统服务异常,因而可能会让应用系统中的所有计算机崩溃,这种就是雪崩效应,非常恐怖,让应用系统彻底无法使用。

因为数据库会首先出现访问瓶颈,那我们再改进一下,改进的重点就是减缓数据库出现瓶颈的时间,因此引进缓存数据库,将操作非常频繁的数据在缓存中进行,然后将数据异步写回数据库。现在常用的缓存数据库,如Redis、Memcached,都是内存缓存,缓存的数据都是存放在内存中进行运算。将数据从传统数据库中拿到内存中运算,速度一定是很快,但是同时还要注意到要解决数据锁的问题,否则还会出现刚开始“超卖”的问题。

在缓存数据库中解决锁的问题,缓存数据库提供了两种解决方式。

第一个是乐观锁,第二个是队列。

先说什么是乐观锁。乐观锁,是在需要加锁的数据上增加版本号,每次读取时同时读取版本号,当有更新是缓存中的版本号会发生变化,当提交更新时会比较读取时的版本号和最新的版本号,如果版本号不同则更新失败,版本号相同则更新成功。有乐观锁一张示意图。

image.png

中间这条线是缓存中数据版本号,先看不会导致冲突的情况。当A计算机读取数据时,读取到的版本是1,A计算机处理完数据后将数据写回缓存,缓存中的版本号变为2;B计算机这个时候也读取了缓存中的数据,这个时候版本号是2,B计算机处理完数据将数据写回缓存,缓存中的版本号变为3。在看下冲突的情况,也是经常发生的情况,A、B两个计算机同时读取了数据拿到当时版本号为1,然后B计算机处理完数据,要将数据写到缓存,写的时候A计算没有提交请求,数据版本号依旧为1,B计算拿到的版本号也是1,因此成功提交数据到缓存,然后缓存中的数据版本号变为2,A计算机这个时候要提交数据,它当时拿到的数据版本号是1,可提交数据时缓存中的数据版本号已经变为了2,因此提交失败。如果A计算机想要成功提交数据,需要怎么办?对,再获取一次数据拿到版本号,将数据处理后再进行写入,可以是个循环直到写入成功为止。

乐观锁的常见应用是在我们常用的版本管理工具中,如SVN和Git。

在缓存中解决锁的问题,还有一种方式是队列,这种方式是将并行改串行。队列是什么样子的,先进先出,线性依次处理,用这种方式可以避免锁的问题。

下面是一段乐观锁实现的伪代码,分析看一下,对应实现逻辑可以用各种语言实现,效果都一样。

image.png

为了要成功提交数据,这里用了一个循环,循环体里面,首先是获取数据版本号,读取数据,进行库存判断,处理数据,数据写回缓存的时候进行判断,直到成功为止。按这个实现逻辑,用PHP实现了一段多线程程序,依旧是100个商品库存,100个线程,我们看运行结果。执行最终结果库存是0,不会超卖。

再来说一下队列的应用。在用户抢购后将抢购请求放入到队列中,服务器后端常驻服务进程处理队列中的任务。后端语言可以根据实际需要选用,不限语言。后端的处理程序要完成的处理任务大都是这几部分,处理抢购结果、记录抢购日志、抢购结果回写数据库。其中尤其要注意日志的记录,可能会在必要的时候起到非常重要的作用,日志一般是记录中文本中,介于内存和数据库之间。

抢购下单后,前端一般都是采用异步等待,响应时间一般要控制在10秒以内,前端可以Ajax等待回调或者定时获取抢购结果。

队列的应用就不具体再说,按照最先的处理思路都可以,就是因为队列是将并行改为串行,将请求一个一个依次处理。

下面再介绍几个高负载系统经常用到的几个衡量指标。有四个:

第一个是并发数、并发用户数;
第二个是QPS;
第三个是TPS;
第四个是响应时间和平均响应时间。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容