redis并发控制

Redis应对并发问题

并发访问

redis的并发访问,是指多个客户端,对同一份数据进行修改。并发访问控制对应的操作是数据修改,当客户端需要修改数据时,基本流程为:

  1. 客户端从redis读取数据到本地,在本地修改
  2. 修改完毕后,客户端写回redis

这个流程叫“读取-修改-写回”操作(read-modify-write, RMW操作),多个客户端对同一份数据的RMW代码,叫做临界区代码。并发的客户端必须互斥地进入临界区,否则可能发生并发问题,对数据的修改不符合预期。

对redis数据的互斥访问,有两种方法:

  • 对临界区代码加锁(应用代码实现,本质是让临界区代码具备原子性)
  • 让临界区代具备原子性(redis支持)

应用加锁会降低系统的并发访问性能,redis支持的原子操作更高效,对并发性能影响较小。

redis原子操作方法

redis提供两种方法来支持对redis数据的原子性操作:

  • 单命令操作:把多个操作在redis中实现成一个操作
  • lua脚本:把多个redis命令写到一个lua脚本中

本质上来说,这两种方式的原子性,都是由redis以单线程的方式执行命令实现的。redis在执行命令时,其他命令无法执行,命令之间是互斥地执行的。

单命令操作
RMW代码包含多个操作,redis提供了INCR/DECR两类单命令操作,将RMW转换为单命令操作。

lua脚本
redis提供的单命令操作毕竟有限,在复杂的业务场景下,有的RMW代码可能涉及多次多redis的操作,此时可将这些操作写入一个lua脚本,提交给redis互斥地执行。如下:

//获取ip对应的访问次数
current = GET(ip)
//如果超过访问次数超过20次,则报错
IF current != NULL AND current > 20 THEN
    ERROR "exceed 20 accesses per second"
ELSE
    //如果访问次数不足20次,增加一次访问计数
    value = INCR(ip)
    //如果是第一次访问,将键值对的过期时间设置为60s后
    IF value == 1 THEN
        EXPIRE(ip,60)
    END
    //执行其他操作
    DO THINGS
END

特别地,应该尽量保持lua脚本的简洁性、通用性、高效性。lua脚本执行时间过长,有损redis性能。在编写lua脚本时,避免将不需要做并发控制的代码放入其中。

Redis分布式锁

除了redis原子操作,还可以通过加锁的方式,来实现对RMW操作的互斥访问。因为多个客户端可能是分布式的,不在一个进程里,所以客户端本地的锁,无法对其他进程的客户端形成约束。

分布式环境下的锁,需要保存在一个第三方的共享存储系统中,可以被多个客户端共享访问和获取。redis读写性能高,可以应对高并发的锁操作场景,正好可以作为分布式锁的共享存储系统。

redis分布式锁的实现

使用一个String类型的值 lock 作为分布式锁的具体表示:

  • lock存在时,表示处于“加锁”的状态
  • lock不存在时,表示处于“未加锁”的状态

从操作上来说:

  • 加锁时,读取redis lock变量,判断是否存在;存在时,加锁失败;不存在时,设值lock,加锁成功。
  • 解锁时,删除lock即可。

这里,加锁是一个RMW操作,必须保证其原子性。

对分布锁的要求如下:

  • 分布锁的加锁、释放锁操作若涉及多个操作,必须保证加锁、释放锁的原子性;
  • 共享存储系统保持了锁变量,如果发生宕机,那么客户端将无法进行锁操作;所以必须保证共享存储系统的可靠性,从而保证分布锁的可靠性。

基于redis单节点的分布式锁

加锁解锁伪码如下

// 加锁, unique_value作为客户端唯一性的标识
SET lock_key unique_value NX PX 10000
// 业务逻辑
DO THINGS
// 释放锁, 调用释放锁的lua脚本
call delete script

加锁:

  • 为保证加锁的原子性,使用SET 命令的NX 选项,实现“如果不存在时,设值,存在时,加锁失败”的逻辑。
  • 增加了锁的有效期,防止业务逻辑异常导致的锁一直占用,无法释放的问题;设置锁有效期也是单命令的原子操作。
  • 将占位变量lock 的值利用起来,用以表示客户端标识;来实现“只有加锁的客户端,才能实现释放锁操作”,防止其他客户端误删除锁。

释放锁:
使用Lua脚本

//释放锁 比较unique_value是否相等,避免误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

为防止其他客户端将占位的redis变量 lock 误删,在释放锁时判断值;这也是一个RMW操作,所以使用lua脚本保证其原子性。

可以看到,redis分布式锁的实现,强依赖于客户端的逻辑。

基于多个redis节点实现高可用分布式锁

单个redis节点宕机时,redis锁就不可用了,是个不可靠的redis分布锁实现。基于多个redis节点实现的分布式锁更可靠。

Redis的开发者提出了分布式锁算法Redlock。基本思想是:让客户端和多个独立的redis实例依次请求加锁,如果客户端能够和半数以上的实例成功加锁,那么认为客户端成功获得分布式锁,否则加锁失败。

具体实现是:

  1. 客户端获取当前时间;
  2. 客户端按顺序依次向N个redis实例执行加锁操作;
    • 同样地,NX + 有效期
    • 加锁时设置超时时间,超时时间远小于有效期;超时时间内加锁未成功,尝试下一个redis实例
  3. 一旦客户端和所有redis实例完成了加锁操作,客户端计算整个加锁过程的总耗时;重新计算锁的有效期。满足以下两个条件认为加锁成功:
    • 客户端从超过半数redis实例上成功获得了锁
    • 客户端总耗时没有超过锁的有效期

Redlock分布式锁的缺点是很重,繁琐,优点是可靠。

问题:是否可以使用 setnx+expire来完成加锁操作?
答:不可以,不具备原子性

问题:基于主从集群redis的分布式锁可靠性如何?
答:主从集群也难以保证redis分布式锁的可靠性。如果在master上加锁成功,此时master宕机,由于主从复制是异步的,锁变量的变更可能尚未同步到slave,此时主从切换,新的master丢失锁状态,导致分布式锁失效。

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

推荐阅读更多精彩内容