当我们再点下一页,程序又正常工作了。刚刚出现的奇怪的那一幕,其实就是由于并发产生的冲突。
-
在什么地方我们做了并发操作了呢?其实就是在一个用户浏览页面的同时,
还有人在往数据库里面写入数据。你会发现thinkPHP这样的框架,还是PHPCMS
这样的开源系统,他们都存在这样的bug。并发产生的问题,往往难以捕捉,更
难以重现,而我们准备的这个案例,算是并发案例冲突中相对容易重新的典型案例 -
我们如果调整一下刚刚操作的顺序,我们会得到一些其他的结果。
比如说:我们分页条目数是每页5条,在用户浏览某一页的时候,后
台管理员发布了新的新闻,新闻的数量小于5条的情况下,我们点下
一页,会看到有几条重复的新闻,也有更早的新闻这样影响用户体验,
但毕竟我们点一下一的页时候内容还是能接的上的,用户浏览某一页
的时候,后台发布的新闻数量大于分页条目数(5条)
那么再点击下一页,其中会有若干条新闻被跳过去了,无论用户点多
少次下一页,都看不到那些条目,这种产生的并发冲突后果是很严重
的,而且普遍存在。他具有很好的隐蔽性,在过去很多年几乎不被察觉 -
在大门户网站时代,网站编辑并不会频繁的发布新闻,而用户也很
少守着新闻列表去逐篇阅读,然而在微博诞生以后,这种问题就暴露出来了。
由于社区类应用信息的产生室友用户产生的,不在是靠编辑在后台发布的
,就好像微博,每时每分秒,都有很多用户分享心得内容 -
这样一来你在查看微博列表的同时,内容就已经更新 了 很多
,而且很多人打发无聊的时间,很多人也会去逐一查看,不停的上划,
把所有遗漏的条目全部看一遍 -
这是如果项目设计不合理,产生并发事故,就会对用户体验造成极大的影响。
-
来看一个更为常见的例子,大家可能还记得,我们在微信群搞的抽奖的互动,
我们的上百份礼品,瞬间就被秒杀光。 -
在做这类抢购与秒杀抽奖等应用的时候,并发将导致更多的问题。通常
比较容易出现的bug有实际商品的订单量大于库存量。通俗点来说就是,明
明已经售完,但还是有用户买到了商品,库存值变为负的。又或者明明秒杀
到商品的用户,订单失败。 -
还有企业的项目,在商品秒杀期间,明明用户数量不多,却导致服务器
宕机。诸如此类的问题就不一一列举了。由于是文字直播,打字速度比较慢,
大家可以看现在正在进行的视屏直播 -
看一下用常规思维来梳理业务流程程序是怎样编写的。
-
还是以商城秒杀业务为例。首先我们需要用产品库存这样的一个字段来记
录库存信息,每当有用户购买商品的时候,先查看库存,判断库存大于0的时
候,用户才能购买 -
当用户完成购买流程后,将库存数量减一,直到所有商品卖完,重复此过
程,直到库存卖完秒杀活动结束。如果按常规的思路来设计,这样的流程是没
有问题的,商品毕竟是一件一件卖出的,但是,在互联网并发的情况下,就完
全不是这样的。 -
要知道热销商品很有可能在同一时间,有多个用户都在进行购买流程操作
-
按照之前的业务设计,假如有ABCD 4个用户同时在秒杀某件商品时,库存
仅剩2件,按照之前的业务流程设计查询库存大于0,就可以继续后面的购买操
作并付款然而当任意用户购买成功后库存即减一,ABCD4个用户都认为自己查询
时都有库存,因此他们都可以完成购买流程,导致的结果就是库存数为负数。也
就是说,商品实际销售量大于活动的商品数量,这样会导致公司的亏损。
有些公司为了解决这个问题,采用了一种思路,虽说4个人同时操作,但是交易
成功的这次网络请求到达服务器的时间总会有个先后顺序 -
那么可以将订单支付成功之后的库存减一之后的值也随订单保存,如果这个
值小于0,就证明有用户购买了产品,已经是卖完的,于是标记订单失败。 -
这样看上去避免公司造成额外的损失,但却会给用户带来极大的不满,是一
种极差的用户体验。它并没有真正的解决我们的问题。当然还有些公司解决方案
也不高明,我们知道无论是数据库还是文件都可以给他加锁,在很早期的程序设
计和软件开发里面,锁是解决并发问题的万能灵药。无论是c++,或java,提到多
进程或多线程的时候,往往也会提到锁这个字。那么作为最早期的通用解决方案,
用到秒杀方案是否合适呢? -
看一下加锁后的工作流程:还是ABCD 4个用户同时秒杀,他们都去查询库存。
当某一个用户,比如A的请求,优先到达时,我们就将数据表锁住,不让其他的数
据库连接来动这张表,待用户A完成购买流程,将库存量减一后,把锁打开,其他
的连接才可以再次操作这张表。 -
如此一来,可以保障一个用户查看库存以及库存减一这段时间内,不可能还有
其他用户可以对表做出修改,这一并发冲突的问题就没有了。不过这样的做法真的
合适吗?要知道ABCD 4个用户都是在同一时间段去秒杀的,由于A用户在操作中锁
表,导致其他用户只能等待,而且A完成整个业务需要消耗一段时间,只能等A完成
以后其他用户才能操作 -
这样一来单位时间内的业务处理量会大幅降低,我们所看到的现象就是网站卡
死,或者服务器宕机关于并发性能如何设计,我们可能需要单独的一次或几次课来
为大家讲解。不过锁这种很原始的并发冲突解决方案,我们可以看到他并不适合互
联网项目。之所以大家会有并发冲突的程序,是因为大部分程序员,思维模式都是
线性的。 -
作为程序逻辑思维来讲,线性思维是没有错的,因为计算机执行指令的时候本
身就是线性的。然而如果把业务也看做是线性的,就会产生问题了。 -
任何一个程序操作,他都会消耗一定的时间,即便你的CPU速度再快,也只是
缩短了这个时间范围而已,如果只有一个用户操作,比如我们在后台发布文章,看
自己发布的新闻,我们是无法感知并发带来的冲突的。这就对我们的程序员提出了
更高的要求。 -
理论上来讲,所有跨越时间段的操作过程中如果涉及到数据修改就会有可能产
生并发冲突,因此我们在设计程序的时候,要保障应用程序的质量,就需要去做并
发冲突处理,只是实现业务需求与实现业务的同时做好质量需求,就是好程序员与
坏程序员的差别。 -
那么分析了产生并发冲突的原因以后,就比较容易思考解决方案了。大体的思
路有两种:一种是将并发操作变为单线操作,另一种是让所有跨越时间的段的操作
不去更改数据。 -
我们现在来看一下分页,或者上拉或者下拉刷新的解决方法。我们刚刚提出的
2种的解决思路,哪一种比较合适呢?对于发布数据和浏览数据,比如微博,我们有
可能把这种并发操作变为单线操作吗?好像不太容易。那么我们能够走得路就剩下
第二条,也就是跨时间段的过程中不要改变数据,我们刚刚产生的bug到底是什么数
据改变导致了bug。回顾下我们的代码实现的本质,就容易找到其中的缘由了。 -
通常我们在实现分页的时候,首页看到的是最新的数据,那么从数据库中取数
据的sel语句是select * from news order by desc limt 0,10,这样取到
最新的数据,如果点击下一页,查询语句不变,只是分页条目不在是第0-9,而是
第10-19条,如果在这个过程中有新的数据插入,我们会发现有一个东西变了,就
是原有数据在数据库的排序序号变了,如果我新发布一条数据,原来的第一条最新
的新闻就会变成第二条,原来的第10条会变成第11条。这就是一个时间段内的操作
过程中有数据发生了改变。 -
既然我们无法把这样的并发操作变成单线操作,我们可以选择不让数据发生改
变,这样并发bug就可以得到很好的解决了。 -
需要了解详细解决方案的,我会把无bug程序实例分享给大家。课后可以联系
赫赫要资料,或者是听我们的视屏直播课,陈老师有详细的解决。 -
跨时间段的让数据不改变不好走,那我们可以选择第一种思路,让并发操作变
为单线操作,之前提到的加锁是解决方案之一,但是对用户体验不好性能很差,基
本上无法再互联网项目中使用。如果不能加锁,那么常用的解决方案是什么? -
我们可以用队列。如果我们将所有的用户请求进行排队,有一个服务来订阅这
个队列,那么不管有多少用户访问,最终到服务器端,处理服务的就只有一个进程。
这样就实现了一个由并发操作转换成单线操作。 -
关于消息队列的使用以及本地服务的相关知识,在源代码的技术经理/架构师
在线课中为大家详尽的展示。这个课程的众筹将会在明天晚上8点开始,众筹的规
则和细节都已经出来了,前期众筹的费用主要用来购买阿里云的服务,搭建实验环境。
网友评论