Redis 核心技术与实战
蒋德钧
中科院计算所副研究员
25203 人已学习
立即订阅
登录后,你可以任选4讲全文学习
推荐试读
换一换
02 | 数据结构:快速的Redis有哪些慢操作?
04 | AOF日志:宕机了,Redis如何避免数据丢失?
17 | 为什么CPU结构也会影响Redis的性能?
课程目录
已完结/共 53 讲
开篇词 (1讲)
开篇词 | 这样学Redis,才能技高一筹
基础篇 (10讲)
01 | 基本架构:一个键值数据库包含什么?
02 | 数据结构:快速的Redis有哪些慢操作?
03 | 高性能IO模型:为什么单线程Redis能那么快?
04 | AOF日志:宕机了,Redis如何避免数据丢失?
05 | 内存快照:宕机后,Redis如何实现快速恢复?
06 | 数据同步:主从库如何实现数据一致?
07 | 哨兵机制:主库挂了,如何不间断服务?
08 | 哨兵集群:哨兵挂了,主从库还能切换吗?
09 | 切片集群:数据增多了,是该加内存还是加实例?
10 | 第1~9讲课后思考题答案及常见问题答疑
实践篇 (28讲)
11 | “万金油”的String,为什么不好用了?
12 | 有一亿个keys要统计,应该用哪种集合?
13 | GEO是什么?还可以定义新的数据类型吗?
14 | 如何在Redis中保存时间序列数据?
15 | 消息队列的考验:Redis有哪些解决方案?
16 | 异步机制:如何避免单线程模型的阻塞?
17 | 为什么CPU结构也会影响Redis的性能?
18 | 波动的响应延迟:如何应对变慢的Redis?(上)
19 | 波动的响应延迟:如何应对变慢的Redis?(下)
20 | 删除数据后,为什么内存占用率还是很高?
21 | 缓冲区:一个可能引发“惨案”的地方
22 | 第11~21讲课后思考题答案及常见问题答疑
23 | 旁路缓存:Redis是如何工作的?
24 | 替换策略:缓存满了怎么办?
25 | 缓存异常(上):如何解决缓存和数据库的数据不一致问题?
26 | 缓存异常(下):如何解决缓存雪崩、击穿、穿透难题?
27 | 缓存被污染了,该怎么办?
28 | Pika:如何基于SSD实现大容量Redis?
29 | 无锁的原子操作:Redis如何应对并发访问?
30 | 如何使用Redis实现分布式锁?
31 | 事务机制:Redis能实现ACID属性吗?
32 | Redis主从同步与故障切换,有哪些坑?
33 | 脑裂:一次奇怪的数据丢失
34 | 第23~33讲课后思考题答案及常见问题答疑
35 | Codis VS Redis Cluster:我该选择哪一个集群方案?
36 | Redis支撑秒杀场景的关键技术和实践都有哪些?
37 | 数据分布优化:如何应对数据倾斜?
38 | 通信开销:限制Redis Cluster规模的关键因素
期中测试 (2讲)
期中测试题 | 一套习题,测出你的掌握程度
期中测试题答案 | 这些问题,你都答对了吗?
未来篇 (3讲)
39 | Redis 6.0的新特性:多线程、客户端缓存与安全
40 | Redis的下一步:基于NVM内存的实践
41 | 第35~40讲课后思考题答案及常见问题答疑
加餐篇 (7讲)
加餐(一)| 经典的Redis学习资料有哪些?
加餐(二)| 用户Kaito:我是如何学习Redis的?
加餐(三)| 用户Kaito:我希望成为在压力中成长的人
加餐(四) | Redis客户端如何与服务器端交换命令和数据?
加餐(五) | Redis有哪些好用的运维工具?
加餐(六)| Redis的使用规范小建议
加餐(七) | 从微博的Redis实践中,我们可以学到哪些经验?
结束语 (2讲)
期末测试 | 这些Redis核心知识,你都掌握了吗?
结束语 | 从学习Redis到向Redis学习
Redis 核心技术与实战
15
15
1.0x
00:00/00:00
登录|注册
开通超级会员可免费学习本课程,还可解锁海量内容免费学特权。

30 | 如何使用Redis实现分布式锁?

你好,我是蒋德钧。
上节课,我提到,在应对并发问题时,除了原子操作,Redis 客户端还可以通过加锁的方式,来控制并发写操作对共享数据的修改,从而保证数据的正确性。
但是,Redis 属于分布式系统,当有多个客户端需要争抢锁时,我们必须要保证,这把锁不能是某个客户端本地的锁。否则的话,其它客户端是无法访问这把锁的,当然也就不能获取这把锁了。
所以,在分布式系统中,当有多个客户端需要获取锁时,我们需要分布式锁。此时,锁是保存在一个共享存储系统中的,可以被多个客户端共享访问和获取。
Redis 本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁。而且 Redis 的读写性能高,可以应对高并发的锁操作场景。所以,这节课,我就来和你聊聊如何基于 Redis 实现分布式锁。
我们日常在写程序的时候,经常会用到单机上的锁,你应该也比较熟悉了。而分布式锁和单机上的锁既有相似性,但也因为分布式锁是用在分布式场景中,所以又具有一些特殊的要求。
所以,接下来,我就先带你对比下分布式锁和单机上的锁,找出它们的联系与区别,这样就可以加深你对分布式锁的概念和实现要求的理解。

单机上的锁和分布式锁的联系与区别

我们先来看下单机上的锁。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/1000字
划线
笔记
复制
02 | 数据结构:快速的Redis有哪些慢操作?
04 | AOF日志:宕机了,Redis如何避免数据丢失?
17 | 为什么CPU结构也会影响Redis的性能?
24 | 替换策略:缓存满了怎么办?
40 | Redis的下一步:基于NVM内存的实践
41 | 第35~40讲课后思考题答案及常见问题答疑
开通超级会员免费畅看本课程
开通会员
该文章仅可免费阅读部分内容,如需阅读完整文章,请开通超级会员或单独购买本课程。
登录 后留言

精选留言(56)

  • Kaito
    是否可以使用 SETNX + EXPIRE 来完成加锁操作?

    不可以这么使用。使用 2 个命令无法保证操作的原子性,在异常情况下,加锁结果会不符合预期。异常情况主要分为以下几种情况:

    1、SETNX 执行成功,执行 EXPIRE 时由于网络问题设置过期失败

    2、SETNX 执行成功,此时 Redis 实例宕机,EXPIRE 没有机会执行

    3、SETNX 执行成功,客户端异常崩溃,EXPIRE 没有机会执行

    如果发生以上情况,并且客户端在释放锁时发生异常,没有正常释放锁,那么这把锁就会一直无法释放,其他线程都无法再获得锁。

    下面说一下关于 Redis 分布式锁可靠性的问题。

    使用单个 Redis 节点(只有一个master)使用分布锁,如果实例宕机,那么无法进行锁操作了。那么采用主从集群模式部署是否可以保证锁的可靠性?

    答案是也很难保证。如果在 master 上加锁成功,此时 master 宕机,由于主从复制是异步的,加锁操作的命令还未同步到 slave,此时主从切换,新 master 节点依旧会丢失该锁,对业务来说相当于锁失效了。

    所以 Redis 作者才提出基于多个 Redis 节点(master节点)的 Redlock 算法,但这个算法涉及的细节很多,作者在提出这个算法时,业界的分布式系统专家还与 Redis 作者发生过一场争论,来评估这个算法的可靠性,争论的细节都是关于异常情况可能导致 Redlock 失效的场景,例如加锁过程中客户端发生了阻塞、机器时钟发生跳跃等等。

    感兴趣的可以看下这篇文章,详细介绍了争论的细节,以及 Redis 分布式锁在各种异常情况是否安全的分析,收益会非常大:http://zhangtielei.com/posts/blog-redlock-reasoning.html。

    简单总结,基于 Redis 使用分布锁的注意点:

    1、使用 SET $lock_key $unique_val EX $second NX 命令保证加锁原子性,并为锁设置过期时间

    2、锁的过期时间要提前评估好,要大于操作共享资源的时间

    3、每个线程加锁时设置随机值,释放锁时判断是否和加锁设置的值一致,防止自己的锁被别人释放

    4、释放锁时使用 Lua 脚本,保证操作的原子性

    5、基于多个节点的 Redlock,加锁时超过半数节点操作成功,并且获取锁的耗时没有超过锁的有效时间才算加锁成功

    6、Redlock 释放锁时,要对所有节点释放(即使某个节点加锁失败了),因为加锁时可能发生服务端加锁成功,由于网络问题,给客户端回复网络包失败的情况,所以需要把所有节点可能存的锁都释放掉

    7、使用 Redlock 时要避免机器时钟发生跳跃,需要运维来保证,对运维有一定要求,否则可能会导致 Redlock 失效。例如共 3 个节点,线程 A 操作 2 个节点加锁成功,但其中 1 个节点机器时钟发生跳跃,锁提前过期,线程 B 正好在另外 2 个节点也加锁成功,此时 Redlock 相当于失效了(Redis 作者和分布式系统专家争论的重要点就在这)

    8、如果为了效率,使用基于单个 Redis 节点的分布式锁即可,此方案缺点是允许锁偶尔失效,优点是简单效率高

    9、如果是为了正确性,业务对于结果要求非常严格,建议使用 Redlock,但缺点是使用比较重,部署成本高
    2020-10-28
    60
    216
  • Darren
    其实分布式锁选择Etcd的话,可能会更好。

    Etcd 支持以下功能,正是依赖这些功能来实现分布式锁的:

    1、Lease 即租约机制(TTL,Time To Live)机制:

    Etcd 可以为存储的 KV 对设置租约,当租约到期,KV 将失效删除;同时也支持续约,即 KeepAlive;

    Redis在这方面很难实现,一般假设通过SETNX设置的时间10S,如果发生网络抖动,万一业务执行超过10S,此时别的线程就能回去到锁;


    2、Revision 机制:

    Etcd 每个 key 带有一个 Revision 属性值,每进行一次事务对应的全局 Revision 值都会加一,因此每个 key 对应的 Revision 属性值都是全局唯一的。通过比较 Revision 的大小就可以知道进行写操作的顺序。 在实现分布式锁时,多个程序同时抢锁,根据 Revision 值大小依次获得锁,可以避免 “惊群效应”,实现公平锁;

    Redis很难实现公平锁,而且在某些情况下,也会产生 “惊群效应”;
     

    3、Prefix 即前缀机制,也称目录机制:

    Etcd 可以根据前缀(目录)获取该目录下所有的 key 及对应的属性(包括 key, value 以及 revision 等);

    Redis也可以的,使用keys命令或者scan,生产环境一定要使用scan;


    4、Watch 机制:

    Etcd Watch 机制支持 Watch 某个固定的 key,也支持 Watch 一个目录(前缀机制),当被 Watch 的 key 或目录发生变化,客户端将收到通知;

    Redis只能通过客户端定时轮训的形式去判断key是否存在;
    2020-10-28
    7
    51
  • 每天晒白牙
    配合这篇文章看,效果更佳 https://mp.weixin.qq.com/s/2P2-ujcdet9cUycokfyjHQ

    这篇是早晨通勤坐地铁开了一个番茄钟看完的,番茄钟开启学霸模式,将这些学习和读书的app加入白名单,在一个番茄钟周期内就不能打开那些未加入白名单的app,可以防止刷耗精力的app,工作时也经常用,效果不错

    如果自己能总结出来并给别人讲明白,就像课代表一样,每篇文章的留言都非常优秀,每次必看,这个知识点就掌握了,能用到工作实战上,那就锦上添花了
    2020-10-30
    2
    7
  • 小喵喵
    请教下老师文章中说的客户端,这个客户端指的是什么?比如浏览器下单,app下单,这个浏览器,app就是客户端吗?

    作者回复: 客户端是指向Redis发送读写请求的应用程序。你举的例子中,app的下单请求先是被发送到后端的服务器上,一般来说,服务器上还有业务程序先解析了app的请求,然后再向Redis发送读写请求,在这种情况下,服务器上的业务程序就是Redis的客户端。而app、浏览器算是服务器上业务程序的客户端。

    2020-11-13
    6
  • 凯文小猪
    这里老师漏了一点 就是session timeout处理 在分布式锁的场景中就是:
    一个key过期了 但是代码还没处理完 此时就发生了重复加锁的问题。

    通常我们有两种方式处理:
    1. 设置看门狗 也就是redision的处理方式
    2. 设置状态机 由最后的业务层来做代码回溯
    2021-06-10
    2
    5
  • 漂泊者及其影子

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

    释放锁为什么不能使用delete操作?
    2020-10-30
    11
    3
  • 范闲
    不可以。这两个命令不是原子的。发生异常的时候,可能会有正常的加锁结果。

    分布式锁:
    1.唯一性
    2.原子性
    3.可重入

    Redlock Redisson zookeeper etcd都是业界常用的分布式锁方案。
    2020-12-03
    3
  • 每天晒白牙
    是不能把setnx 和 expire 命令分开的,因为无法保证两个操作执行的原子性,可能遇到各种异常,无法满足预期
    2020-10-30
    2
  • 悟空聊架构
    这节课,我提到,我们可以使用 SET 命令带上 NX 和 EX/PX 选项进行加锁操作,那么,我想请你再思考一下,我们是否可以用下面的方式来实现加锁操作呢?



    // 加锁
    SETNX lock_key unique_value
    EXPIRE lock_key 10S
    // 业务逻辑
    DO THINGS

    答:非原子操作,所以不能用这种方式。
    2021-06-08
    1
  • 平凡之路
    老师,您好,使用redis集群,使用setnx能保障加锁成功吗?即使一个实例挂了,还有其他实例能读取并加锁吗?
    2021-06-03
    3
    1
  • COLDLY
    老师,请问锁的过期时间怎么设置,会不会因为客户端执行业务操作时耗时太久超过过期时间,导致锁过期被释放了
    2021-01-05
    1
    2
  • Geek_9a0c9f
    python的简单实现:
    import threading
    import time

    import redis

    redis_con = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)

    # redis_con.ttl('name')
    redis_con.set("global", 10)

    #守护进程,用来检测时间是否到期,快到期了,就续命
    def add_time(redis_con, key_name):
        while True:
            time = redis_con.ttl(key_name)
            if time == 1:
                print("=====")
                redis_con.expire(key_name, 2)
                print(redis_con.ttl(key_name))

    def test_distribute_lock():
        thead_id = str(time.time())
        if_has_key = redis_con.setnx("lock", thead_id)
        try:
            if not if_has_key:
                print("等待请稍后重试")
            else:
                redis_con.expire('lock', 2)
                # 开启守护进程
                t1 = threading.Thread(target=add_time, args=(redis_con, 'lock'))
                t1.setDaemon(True)
                t1.start()
                redis_con.decr('global', 1)
                time.sleep(2)
        except Exception as e:
            print(e)
        finally:
                     # 执行完毕,释放锁,保证每个线程只能删除自己的锁
            if str(redis_con.get("lock"), encoding="utf-8") == thead_id:
                redis_con.delete('lock')
            redis_con.close()

    if __name__ == '__main__':
        for i in range(5):
            t = threading.Thread(target=test_distribute_lock)
            t.start()
            t.join()
        print("===>")
    2020-10-29
    1
  • test
    不能这样做,因为两个命令就不是原子操作了。

    set nx px的时候如果拿到锁的客户端在使用过程中超出了其设置的超时时间,那么就有这把锁同时被两个客户端持有的风险,所以需要在使用过程中不断去更新其过期时间。
    2020-10-28
    1
  • Geek_lijia
    关于分布式锁,两个大神的争论,我站Martin这边。
    https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
    http://antirez.com/news/101
    2022-01-13
    1
  • InfoQ_小汤
    在 Redlock 算法中,释放锁的操作和在单实例上释放锁的操作一样,只要执行释放锁的 Lua 脚本就可以了。这样一来,只要 N 个 Redis 实例中的半数以上实例能正常工作,就能保证分布式锁的正常工作了。
    ————————————-
    老师可以说下具体删除的情况么? 是针对已加锁的半数以上redis删除还是分片集群的所有redis
    2021-11-25
  • adrian_xia
    也能够实现,会复杂很多
    需要在value中增加过期时间的标识
    若是没有释放锁的话,还需要判断value内的时间戳是否过期
    2021-11-14
  • A梦多啦A
    1. 使用set key value px xxx nx可以解决设置键时的原子性。

    2. 使用lua可以解决删除key时的原子性。

    3. 使用redlock可以解决单节点,出现Redis实例宕机的清空。

    4. 这里有个疑问,如何去避免锁超时呢?业务还没处理完,锁就过期了,总不能完全靠给锁设置一个很长的时间把。
    2021-10-26
    1
  • 郑印
    我理解Redlock 这种算法应该只应用在多实例但非集群(此时是在客户端程序控制分片逻辑)的情况下吧? 集群本身已经有了高可用的保证,因此按照单节点的加锁方式操作就可以了。请问是否理解正确。
    2021-09-08
    1
  • Heaven
    首先回答下这个问题,如果不保证原子性,那么实例宕机了,岂不是没有别人能拿到锁了,这是大问题
    第二个问题,按照老师说的Redlock,达到半数以上,那么由一个问题,就是出现集群实例宕机一部分,导致其他客户端也达到了获取锁的数量(即在集群半数上加锁成功),那时候怎么办
    2021-08-25
  • BingoJ
    目前这把锁还有一个问题,就是不能保证可重入
    2021-07-31
收起评论
56
返回
顶部