Redis 核心技术与实战
蒋德钧
中科院计算所副研究员
81696 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 53 讲
开篇词 (1讲)
实践篇 (28讲)
Redis 核心技术与实战
15
15
1.0x
00:00/00:00
登录|注册

29 | 无锁的原子操作:Redis如何应对并发访问?

避免不需要做并发控制的操作
保证原子性执行
包含多个操作
适用范围有限
INCR/DECR命令
减少对系统并发性能的影响
保证并发控制
问题:降低系统并发性能
问题:数据更新错误
读取-修改-写回操作
Lua脚本中的操作
Lua脚本的注意事项
原子操作的优势
Lua脚本
单命令操作
原子操作
加锁
临界区代码
每课一问
总结
Redis的两种原子操作方法
并发访问控制
Redis如何应对并发访问?

该思维导图由 AI 生成,仅供参考

你好,我是蒋德钧。
我们在使用 Redis 时,不可避免地会遇到并发访问的问题,比如说如果多个用户同时下单,就会对缓存在 Redis 中的商品库存并发更新。一旦有了并发写操作,数据就会被修改,如果我们没有对并发写请求做好控制,就可能导致数据被改错,影响到业务的正常使用(例如库存数据错误,导致下单异常)。
为了保证并发访问的正确性,Redis 提供了两种方法,分别是加锁和原子操作。
加锁是一种常用的方法,在读取数据前,客户端需要先获得锁,否则就无法进行操作。当一个客户端获得锁后,就会一直持有这把锁,直到客户端完成数据更新,才释放这把锁。
看上去好像是一种很好的方案,但是,其实这里会有两个问题:一个是,如果加锁操作多,会降低系统的并发访问性能;第二个是,Redis 客户端要加锁时,需要用到分布式锁,而分布式锁实现复杂,需要用额外的存储系统来提供加解锁操作,我会在下节课向你介绍。
原子操作是另一种提供并发访问控制的方法。原子操作是指执行过程保持原子性的操作,而且原子操作执行时并不需要再加锁,实现了无锁操作。这样一来,既能保证并发控制,还能减少对系统并发性能的影响。
这节课,我就来和你聊聊 Redis 中的原子操作。原子操作的目标是实现并发访问控制,那么当有并发访问请求时,我们具体需要控制什么呢?接下来,我就先向你介绍下并发控制的内容。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Redis并发访问控制技术总结: Redis提供了两种原子操作方法来解决并发访问控制问题。首先是单命令操作,Redis使用单线程来串行处理客户端的请求操作命令,保证了命令操作的互斥执行。对于复杂的操作,Redis提供了Lua脚本,将多个操作以原子性方式执行,避免了单命令操作的限制。在并发访问时,原子操作能够保证临界区代码的互斥执行,同时维持较高的系统并发性能。然而,需要注意的是,编写Lua脚本时应避免将不需要并发控制的操作写入脚本中,以免影响Redis的并发性能。总的来说,Redis的原子操作方法能够有效应对并发访问问题,保证数据的正确性,并减少对系统并发性能的影响。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Redis 核心技术与实战》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(54)

  • 最新
  • 精选
  • Geek_fb6ea6
    Redis不是单线程吗?怎么还会有并发读写的问题呢

    作者回复: 这里的并发是指有多个客户端同时访问Redis,而客户端执行的业务逻辑不只一个命令操作。假设客户端A要先从Redis读数据,然后做了修改把数据再写回Redis,此时,在读数据和写回数据的期间就可能有其他客户端的并发操作执行,所以仍然存在并发读写问题。

    2020-11-04
    8
    27
  • Kaito
    是否需要把读取客户端 ip 的访问次数 GET(ip),以及判断访问次数是否超过 20 的判断逻辑,也加到 Lua 脚本中? 我觉得不需要,理由主要有2个。 1、这2个逻辑都是读操作,不会对资源临界区产生修改,所以不需要做并发控制。 2、减少 lua 脚本中的命令,可以降低Redis执行脚本的时间,避免阻塞 Redis。 另外使用lua脚本时,还有一些注意点: 1、lua 脚本尽量只编写通用的逻辑代码,避免直接写死变量。变量通过外部调用方传递进来,这样 lua 脚本的可复用度更高。 2、建议先使用SCRIPT LOAD命令把 lua 脚本加载到 Redis 中,然后得到一个脚本唯一摘要值,再通过EVALSHA命令 + 脚本摘要值来执行脚本,这样可以避免每次发送脚本内容到 Redis,减少网络开销。
    2020-10-26
    36
    326
  • 好运来
    “对于这些操作,我们同样需要保证它们的原子性。否则,如果客户端使用多线程访问,访问次数初始值为 0,第一个线程执行了 INCR(ip) 操作后,第二个线程紧接着也执行了 INCR(ip),此时,ip 对应的访问次数就被增加到了 2,我们就无法再对这个 ip 设置过期时间了。这样就会导致,这个 ip 对应的客户端访问次数达到 20 次之后,就无法再进行访问了。即使过了 60s,也不能再继续访问,显然不符合业务要求。" 对于这段话我有疑惑,假如有两个线程A和线程B,初始ip计数是0,线程A和线程B并发执行,不管线程A和线程B谁先执行到 value = INCR(ip) ,获取到的value值总会有一个是1,而value作为线程的局部变量,也是可以继续执行下去,那不就是能够执行到 IF value == 1 THEN EXPIRE(ip,60) END 这个判断逻辑了吗,不明白为什么说不能设置先到的ip过期时间60s了?
    2020-11-01
    7
    19
  • dfuru
    获取访问次数和判断访问是否大于20, 若放到lua脚本中,获取到的访问次数是准确的最新值,进行判断更准确; 当放到lua脚本外,并发访问时某线程获取到的访问次数可能旧(偏小),当获取到访问次数为19时(实际可能已经达到20了),该线程仍然会对访问次数执行+1,所以应该放到lua中。
    2020-11-07
    3
    18
  • Mr.蜜
    是否需要把读取客户端 ip 的访问次数 GET(ip),以及判断访问次数是否超过 20 的判断逻辑,也加到 Lua 脚本中? 我觉得需要分3步来优化: 1.在执行lua之前,get(ip),进行判断,如果大于20,就直接报错了,这样也就减少了执行lua的开销; 2.在执行lua时,判断incr(ip)的返回值,这个值是加一之后的值,直接判断这个值是否大于20,返回错误。 3.如果并发量特别大的时候,可以在incr前再判断一次get(ip),减少incr的开销。
    2020-11-14
    1
    9
  • sky
    对于这些操作,我们同样需要保证它们的原子性。否则,如果客户端使用多线程访问,访问次数初始值为 0,第一个线程执行了 INCR(ip) 操作后,第二个线程紧接着也执行了 INCR(ip),此时,ip 对应的访问次数就被增加到了 2,我们就无法再对这个 ip 设置过期时间了。这样就会导致,这个 ip 对应的客户端访问次数达到 20 次之后,就无法再进行访问了。即使过了 60s,也不能再继续访问,显然不符合业务要求。 不太明白为何过了 60 秒,也不能继续访问呢
    2020-10-29
    6
    8
  • 永不止步
    除了技术之外,我觉得那段代码的判断逻辑应该属于业务范畴,最好不要放lua中,因为业务是经常变化的,lua脚本最好与具体业务无关
    2021-01-18
    1
    7
  • 郑印
    感觉 “Redis 在执行 Lua 脚本时,是可以保证原子性的” 这句话有误导,当lua脚本中间有命令执行出错时,以执行的命令还是生效了的,这样不能完全称之为原子性。redis只是通过lua脚本让一组命令想一个命令一样执行在加上单线程的特性避免了并发产生的问题,而不能称之为原子性。
    2021-09-08
    3
    5
  • huiye
    除了把多个操作在 Redis 中实现成一个操作,和使用lua脚本,使用redis的事务,把多个命令放入队列里一起执行,是不是也能保证原子性呢
    2021-02-02
    2
    3
  • Geek_750e24
    value = INCR(ip) //如果是第一次访问,将键值对的过期时间设置为60s后 IF value == 1 THEN EXPIRE(ip,60) END 执行 INCR命令返回value不是原子操作么?就算两个线程执行了INCR,第一个线程返回的不是1么?
    2020-11-28
    1
    3
收起评论
显示
设置
留言
54
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部