太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己( 十 )


太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
在上面的这个图中 , 就导致了并发用户B也“抢购成功” , 多让一个人获得了商品 。这种场景 , 在高并发的情况下非常容易出现 。
悲观锁思路
解决线程安全的思路很多 , 可以从“悲观锁”的方向开始讨论 。悲观锁 , 也就是在修改数据的时候 , 采用锁定状态 , 排斥外部请求的修改 。遇到加锁的状态 , 就必须等待 。
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
虽然上述的方案的确解决了线程安全的问题 , 但是 , 别忘记 , 我们的场景是“高并发” 。也就是说 , 会很多这样的修改请求 , 每个请求都需要等待“锁” , 某些线程可能永远都没有机会抢到这个“锁” , 这种请求就会死在那里 。同时 , 这种请求会很多 , 瞬间增大系统的平均响应时间 , 结果是可用连接数被耗尽 , 系统陷入异常 。
FIFO队列思路
那好 , 那么我们稍微修改一下上面的场景 , 我们直接将请求放入队列中的 , 采用FIFO(First Input First Output , 先进先出) , 这样的话 , 我们就不会导致某些请求永远获取不到锁 。看到这里 , 是不是有点强行将多线程变成单线程的感觉哈 。
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
然后 , 我们现在解决了锁的问题 , 全部请求采用“先进先出”的队列方式来处理 。那么新的问题来了 , 高并发的场景下 , 因为请求很多 , 很可能一瞬间将队列内存“撑爆” , 然后系统又陷入到了异常状态 。
或者设计一个极大的内存队列 , 也是一种方案 , 但是 , 系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比 。也就是说 , 队列内的请求会越积累越多 , 最终Web系统平均响应时候还是会大幅下降 , 系统还是陷入异常 。
乐观锁思路
这个时候 , 我们就可以讨论一下“乐观锁”的思路了 。乐观锁 , 是相对于“悲观锁”采用更为宽松的加锁机制 , 大都是采用带版本号(Version)更新 。实现就是 , 这个数据所有请求都有资格去修改 , 但会获得一个该数据的版本号 , 只有版本号符合的才能更新成功 , 其他的返回抢购失败 。这样的话 , 我们就不需要考虑队列的问题 , 不过 , 它会增大CPU的计算开销 。但是 , 综合来说 , 这是一个比较好的解决方案 。
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
有很多软件和服务都“乐观锁”功能的支持 , 例如Redis中的watch就是其中之一 。通过这个实现 , 我们保证了数据的安全 。
总结互联网正在高速发展 , 使用互联网服务的用户越多 , 高并发的场景也变得越来越多 。电商秒杀和抢购 , 是两个比较典型的互联网高并发场景 。虽然我们解决问题的具体技术方案可能千差万别 , 但是遇到的挑战却是相似的 , 因此解决问题的思路也异曲同工 。

【太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己】


推荐阅读