《Redis深度历险》 读书笔记 (1)

分布式锁

问题

  • 在分布式应用中,如果要修改用户的状态,需要先读取出用户的状态,在内存中修改之后再保存,如果这样的操作在同一个用户上进行,就会出现并发问题,因为读取和修改这两个操作不是原子性的。

解决方法

  • 对分布式应用加锁

    在Redis中,可以使用setnx(set if not exists)指令来进行加锁的操作:

    > setnx lock:operation true
    OK
    ... do something critical
    > del lock:operation
    (integer) 1
    
    • 可能出现的问题:
      逻辑执行到一半出现异常,未能正常退出,可能导致 del 指令没有被调用,锁就永远不会被释放,发生了死锁。

    因此需要对锁设置过期时间,防止锁被一直占用:

    > setnx lock:operation true
    OK
    > expire lock:operation 5
    ... do something critical
    > del lock:operation
    (integer) 1
    
    • 可能出现的问题:
      setnxexpire 两个指令之间,服务器进程挂掉,expire 无法执行,也会死锁。

    问题出现的原因是 setnxexpire 这两个指令不是原子性的,因此,需要将它们组合在一起,redis2.8之后引入了 set 指令的扩展参数:

    > set lock:operation true ex 5 nx 
    OK
    ... do something critical ...
    > del lock:operation
    

    上面的指令是将 setnxexpire 组合在一起的原子指令

  • 需要注意的问题:

    1. 超时问题:
      如果在加锁和释放锁之间的逻辑执行时间过长,导致锁过期,就可能出现这种情况:
      线程1持有一把锁,但执行时间过长导致锁过期,线程2由于锁过期的原因,持有了这把锁,
      线程1此时执行完毕,释放锁。这时线程3就可能在线程2执行的过程中拿到锁,导致一些预料之外的行为发生。

      如何避免? 尽量不要让Redis分布式锁用于较长时间的任务。其次可以在加锁的时候设置一个随机数,
      在释放锁时先匹配随机数是否一致,然后再执行释放锁的操作,但是需要注意匹配随机数和释放锁这两个动作应该是一个原子性动作,因此需要使用Lua脚本来执行

    2. 保证锁的可重入性:
      线程在持有某个锁的情况下,可以再次请求被这个锁保护的其他资源,这个锁就是可重入的。可重入性意味着线程可以进入任何一个它已经拥有的锁所同步着的代码块。
      使用可重入锁需要对set方法进行包装,使用threadlocal存储当前持有锁的计数。

      如果要实现完整的可重入锁,还需要考虑很多其他的问题,例如内存锁计数的过期时间等等。比较复杂。

      不推荐使用Redis来实现可重入锁

    3. 锁冲突处理
      客户端处理请求时加锁没加成功,有三种策略来解决

      • 直接抛出异常,通知稍后重试
      • sleep一会再请求
      • 将请求转移到延时队列,过一会再试

延时队列

比较适合异步消息处理,将当前冲突的请求扔到另一个队列延后处理以避开冲突

Redis的 list 常常被用来作为异步消息队列使用,使用 rpush/lpush 入队, lpop/rpop 出队

问题

  1. 队列为空
    队列为空时,客户端会陷入pop操作的死循环,导致空轮询,空轮询多的话,会导致Redis的慢查询显著增多

让线程sleep来解决这个问题,请求不到的话,让线程睡1秒钟,这样能降低客户端的CPU占用和Redis的QPS

  1. 队列延迟
    睡眠的方法虽然可以解决问题,但是会导致消息延迟增大,如何降低延迟呢?

使用blpop/brpop,这俩个指令的前缀 d 代表blocking,阻塞读。
阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻醒过来,消息延迟几乎为0.

  1. 空闲连接自动断开
    若果线程一直阻塞,Redis在连接闲置过久的时候会主动断开连接以减少闲置资源占用,这个时候 bpop/lpop 会抛出异常

在编写客户端消费者时要注意捕获异常,重新连接

延时队列的实现

延时队列可以通过Redis的zset(有序列表)实现,将消息序列化作为 zsetvalue ,将到期处理时间作为 score
使用多个线程轮询 zset 获取到期的任务进行处理(保证一个线程挂了其他的线程可以继续处理)

def delay(msg):
    msg.id = str(uuid.uuid4())  # 保证 value 值唯一
    value = json.dumps(msg)
    retry_ts = time.time() + 5  # 5 秒后重试
    redis.zadd("delay-queue", retry_ts, value)


def loop():
    while True:
        # 最多取 1 条
        values = redis.zrangebyscore("delay-queue", 0, time.time(), start=0, num=1)
        if not values:
            time.sleep(1)  # 延时队列空的,休息 1s
            continue
        value = values[0]  # 拿第一条,也只有一条
        success = redis.zrem("delay-queue", value)  # 从消息队列中移除该消息
        if success:  # 因为有多进程并发的可能,最终只会有一个进程可以抢到消息
            msg = json.loads(value)
            handle_msg(msg)...

使用 zrem 来保证任务被唯一的线程获取并执行

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

推荐阅读更多精彩内容