【视频笔记】Java高并发秒杀API

视频地址:
http://www.imooc.com/learn/587
http://www.imooc.com/learn/631
http://www.imooc.com/learn/630
http://www.imooc.com/learn/632

这个视频一共分为四篇,前面三篇比较基础,我基本快速过了一下,没怎么记录,最后一篇开始讲高并发,这一篇还是很精彩的,很多干货。对Spring、Mybatis有基础的可以直接看最后一篇。

image.png

一、秒杀业务分析

image.png

上图是天猫的秒杀系统,比较复杂,本案例并不会实现这个,而是实现一个秒杀的基本功能,如下图所示。

秒杀的业务流程

通过这个简易版秒杀流程可以看出:秒杀系统有两个角色:商家和用户。

  • 对于商家来说,是添加秒杀商品,然后添加/调整库存,然后发货/合账。
  • 对于用户来说,参与秒杀,付款/退货。

秒杀的核心在于库存的管理。

因为判断用户的秒杀是否成功,是否成功取决于库存,被秒杀的数量和库存量必须一致,秒杀不能多(货不够),也不能少(货没卖掉)。

二、秒杀系统数据库原型

1、秒杀的事务性

一个基本的秒杀操作至少包含两个,一是减库存(update操作,库存数量-1),二是记录购买明细。

这两个操作都存在才能算是一个完整的秒杀,少一个就有问题了,所以两个操作必须在同一个事务中。

事务

2、秒杀的并发性

秒杀跟普通的业务相比,在于短时间内的大量并发操作,对于同一商品有限库存的竞争。

image.png

3、决定使用mysql

nosql类数据库能提供快速的插入能力,可以处理大量并发,但是在事务方面不行,所以我们关系型数据库mysql来保证数据的事务性。

mysql的行级锁可以保证数据的事务性,并且可以提供1秒2w左右的并发(单实例),很适合秒杀场景。

行级锁

三、秒杀系统增删改查

1、技术分层

跟普通系统一样分三层:dao、service、controller
dao这里选用mybatis,因为是自己写SQL,性能显然比hibernate、springdataJPA等高效一点,也为后面优化做铺垫。
service和controller就用spring和springmvc,提供rest接口,这块儿没什么特殊的

2、业务逻辑

1)【用户】查看秒杀商品;
2)【系统】返回秒杀商品详情介绍界面
3)【用户】浏览器端用js做一个倒计时,到时间自动向服务器请求秒杀链接;
4)【系统】判断是否在秒杀期限内,如果在期限内,则给用户返回秒杀链接,否则返回等待
5)【用户】得到秒杀链接,点击秒杀;
6)【系统】判断库存,如果有则减库存并记录秒杀日志;如果库存为0则返回秒杀失败。

四、秒杀系统优化

这里系统做的主要是步骤2、4、6,先来说2。

1、步骤2优化

第2步需要给用户返回商品详情,详情页面内容比较多,包括数据、图片、介绍视频等,会对数据库和web服务器的带宽造成压力。

这里因为秒杀的商品信息是不会变化的,所以可以认为是一个静态资源,我们可以采用cdn来加速用户访问,用户不需要连接到真的服务器就可以查询这个信息,也就减轻了服务器的压力。

ps:cdn也有个问题,他相当于一个缓存,如果你修改了秒杀商品,cdn并不会马上更新,所以会导致用户看到的商品跟服务器不一致。所以为了避免这个情况,一般都不去修改秒杀商品。如果非要修改,就把商品逻辑删除,重新发布一个商品。

2、步骤4优化

上面的业务逻辑中,实际上已经对步骤4做了优化,就是第3步这里,给用户做了一个倒计时,等秒杀开始,才给他返回第4步的秒杀链接,避免用户自己频繁刷新浏览器给第4步造成很大压力。

但是当秒杀开启短时间内,特别是秒杀开启瞬间,大家都都来请求秒杀链接,虽然是根据主键查询,但是仍然不够。这里我们可以采用redis,将主键查询的对象直接放到redis中,下次再有用户请求,直接从redis中获取返回。

关于redis,还有一个优化点是序列化,因为redis中存储的是bytes,我们需要将对象做序列化,这里建议使用google的protostuff框架做序列化,而不是用jvm自带的Serializable接口。下面的链接可看看常用序列化框架的对比,差距真的很大。

序列化效率,第一的是protostuff倒数第三个是java自带的
压缩后的大小,第一的是protostuff,倒数第三的是java自带的

查看更多对比:https://github.com/eishay/jvm-serializers/wiki

3、步骤6优化

这里是秒杀的最大难点,也就是处理竞争。我们前面用到行级锁来保证事务,相当于所有用户都要在这里排队,一个用户锁定后,只要他不释放锁,其他用户全部都要等待,所以我们肯定是尽量缩短用户对行级锁的占用时间。

如何做呢?

事务

上面这张图我们前面已经提到,再来看一次,基于行级锁的机制,用户1秒杀操作过来的时候,我们在update这一步锁掉行记录、减库存、insert购买明细,然后提交/回滚,然后解除行级锁。

所以用户1占用行级锁的时间是:锁记录、减库存、insert购买明细、提交/回滚、解除锁。

我们注意到,这里有一个insert购买明细的操作,如果我们把它拿到外面去,流程改为insert购买明细、锁记录、减库存、提交/回滚、解除锁,那么用户1占用行级锁的时间就缩短了。

不过,insert操作仍然是需要占用数据库资源的,不过,这个操作因为不在锁内,就可以并发执行的,所以可以忽略。

五、网络层面进一步优化步骤5、6

经过上一步,步骤1、2我们采用cdn,基本完全屏蔽了对服务器的压力;步骤3、4在业务逻辑中自动优化了;步骤5、6通过把insert逻辑踢出锁,也提高了性能。

大家经过这些优化后可以做一下性能测试,如果已经满足了自己的需求,就可以到此为止了,如果你的需求比较高,那么我们还可以进一步优化第6步。来进一步分析。

第6步是先执行减库存(update)
如果update成功,则执行commit,如果update失败,则执行rollback

前面我们提到过用户和系统的交互,刚才这个过程,我们也可以进一步细化为三个角色的交互:java、db和网络
1、【java】请求执行update
2、【网络】传输update请求
3、【db】执行update并返回结果
4、【网络】返回update结果
5、【java】判断update结果是否成功,请求commit/rollback
6、【网络】传输commit/rollback请求
7、【db】执行commit/rollback并返回结果
8、【网络】传输commit/rollback请求
9、【java】返回秒杀结果

这里可以看到4次网络传输的过程,有同学可能会说,这是不是过分了,程序不都是这么写的么,从来没想过网络传输的问题。

但是我们是秒杀系统,这些过程都是串行执行的,我们来做个数学运算。

通常来说,同城机房的网络传输是1ms,异地机房(假设北京到上海)的时间第5ms。

假设一次网络传输是1ms,那么一次秒杀请求至少需要串行4ms,那么1秒的秒杀次数=1000ms/4ms=250次。
假设一次网络传输是5ms,那么一次秒杀请求至少需要串行20ms,那么1秒的秒杀次数=1000ms/20ms=50次。

此外,如果中间某一次返回java的时候,来一下gc,就又是50ms。

所以如果你要抗1w人的在线秒杀,你就需要至少40台的mysql集群,而且保证你的mysql不受其他影响,网络也都是最好。

那么我们的优化思路也就很清晰了:减少网络交互。有两种方法,效果都是一样,就是把java跟db的两次请求(一次是update,一次是commit/rollback),改成一次请求。在第3步完成时,就完成了事务并释放行级锁,到这里网络请求只经过了一次。所以我们就可以成功的把4次网络交互缩短到1次,并且一定程度上避免了并发过程中的gc,大大提高并发。

1、修改mysql源码,发送定制SQL,如下图所示,我们的sql中加入了一部分/* */格式的注释,告诉mysql,如果update成功就commit,如果update失败就rollback


image.png

这种方式使用起来很简单,但是需要改mysql源码,很多公司可能不具备这样的能力

2、另外一种就是使用db自带功能:存储过程,也可以实现跟第一个类似的功能。
具体存储过程没什么特殊的,就是把我们上面的逻辑用存储过程实现一遍,我就不贴代码了。

六、总结

经过一系列的优化,我们就实现了高并发的秒杀API。
1、秒杀商品请求采用vpn
2、秒杀链接到时间才提供,避免无效请求;使用redis缓存秒杀链接
3、无效操作尽量不要放到行级锁过程中(insert秒杀记录日志)
4、减少行级锁过程中的网络交互

如果觉得有用就关注一下吧~

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

推荐阅读更多精彩内容

  • 1 优化分析前三张基本将秒杀的系统开发完成但是之前那种设计真的可以承受高并发下的秒杀么本篇文章结合该高并发系统考虑...
    意浅离殇阅读 2,501评论 0 6
  • 前言 本篇将完成高并发优化,包括: Redis后端缓存优化 并发优化 一、高并发优化分析 在优化之前要明白高并发发...
    MOVE1925阅读 2,944评论 0 9
  • 一、什么是高并发 高并发是指在同一个时间点,有大量用户同时访问URL地址,比如淘宝双11、定时领取红包就会产生高并...
    不知名的蛋挞阅读 13,506评论 1 31
  • 1 秒杀业务分析# 正常电子商务流程 (1)查询商品;(2)创建订单;(3)扣减库存;(4)更新订单;(5)付款;...
    七寸知架构阅读 15,581评论 12 197
  • 远方不在远方 /深山老林(千年桃妖) 远方 一条河 在静静流淌 划船的老者 冲我挥一挥手 展眼 变成了如花少年 豆...
    深山老林千年桃妖阅读 112评论 0 2