专栏名称: 数据分析与开发
伯乐在线旗下账号,分享数据库相关技术文章、教程和工具,另外还包括数据库相关的工作。偶尔也谈谈程序员人生 :)
目录
相关文章推荐
数据分析与开发  ·  马斯克被“打脸”?150岁老人领社保,竟是程 ... ·  3 天前  
数据中心运维管理  ·  一台交换机能带动多少网络摄像机? ·  3 天前  
数据中心运维管理  ·  数据中心越建越大,怎么办? ·  4 天前  
数据中心运维管理  ·  2025年数据中心值得关注的冷却趋势和策略 ·  3 天前  
数据中心运维管理  ·  数据中心的等级划分 ·  5 天前  
51好读  ›  专栏  ›  数据分析与开发

一张优惠券引发的血案

数据分析与开发  · 公众号  · 数据库  · 2017-05-04 20:19

正文

(点击 上方公众号 ,可快速关注)


来源:伯乐在线专栏作者/玻璃猫(微信公号:dreamsee321)

如有好文章投稿,请点击 → 这里了解详情








一个月前——




整个优惠券中心分为前端和后端, 小灰所负责的是后端RPC接口的开发。 接口中包含“查券”和“ 领券 ”两个方法, 项目大体结构如下图:



两周后——



小灰:看,这是优惠券查询功能的效果!


小灰:看,这是优惠券领取功能的效果!



三天后——








小灰原本的优惠券查询接口是这样实现的:



优惠券列表在Redis中以List的形式存储,查询时的逻辑很简单:


1.查询缓存,如果缓存存在,返回结果


2.缓存不存在,查询数据库


3.把查询数据库的结果循环放入缓存


然而,当某个时间点缓存不存在,请求量又很大的时候,会出现 缓存并发 的问题。也就是多个线程会重复去查询DB,又重复去更新缓存。(注意,这并不是 缓存击穿 ,很多人在这两个概念上混淆。)


这其中重复查询DB是次要问题,而重复更新缓存则是主要问题。假如有两个线程同时进入上述的第三个阶段,各自进行rpush操作,那么最终会在 优惠券列表的 缓存中插入两组同样的数据。


怎么解决呢?用Java的锁机制?显然不行,因为线上环境通常都是多个服务器组成的集群。于是小灰想到了利用 分布式锁



所谓分布式锁有很多种,可以利用ZooKeeper、MemCache、Redis来实现。其中Redis的方式比较简单,无非是利用一个服务器之间共享的Key,以及Setnx指令。


当第一个线程执行Setnx,会存储对应的键值,相当于成功获得锁。当后续再有线程对同于的Key执行Setnx指令,则会返回空,相当于抢锁失败。同时,为了防止一个线程因意外情况而长久把持着锁,程序对Key设置了1秒的过期时间。


归纳一下修改后的逻辑:


1.查询缓存,如果缓存存在,返回结果


2.缓存不存在,查询数据库


3.争夺分布式锁


4.成功获得锁,把查询数据库的结果循环放入缓存


5.释放分布式锁




三天后——







诡异的bug又重现了,因为小灰上次的改动仍然存在一个致命的漏洞。在这里我们假定缓存不存在,刚好有两个线程A和B一后一先进入到代码块。


第一阶段,线程A刚开始查询优惠券缓存,线程B正尝试获取分布式锁:


第二阶段,由于缓存不存在,线程A开始查询数据库, 线程B成功获得锁,开始更新缓存:



第三阶段,线程A尝试获得分布式锁,而线程B已经释放分布式锁



第四阶段,线程A获得了锁, 又一次 更新缓存,而线程B已经成功返回:



就这样,缓存被重复更新了两次,所以再次出现数据重复的bug。


这种局面如何破解呢?其实不难,只需在线程成功得到锁以后,再次判断优惠券缓存的存在:








请到「今天看啥」查看全文