Redission 分布式锁(八)读写锁

197 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

1 前言

研究源码 是为了解决实际问题 - 雨夜

先看 思考和应用场景的问题 思考下,然后再继续往下看

如果可以的话 点个赞/评论下 哈哈 好有点动力 有问题 也请留言

2 背景

3 思考

1 正常lock 和 读锁 互斥么?为什么?对同一个lockKey 加锁 应该互斥啊
2 不同一个线程 读写互斥么? 写写互斥么? 同一个线程呢?
3 

4 本节内容

读写锁的概念和原理

5 读写锁

5.1 概念

多个客户端同时加读锁 不互斥
如果有人用读锁 另一个人用写锁 读锁和写锁 互斥

5.2 加读锁的逻辑

5.2.1 参数

KEY[1] 
    this.getName()
    readLockKey
KEY[2]
    this.getReadWriteTimeoutNamePrefix(threadId)
    {readLockKey}:9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:rwlock_timeout
ARGV[1]
    this.internalLockLeaseTime
    30000
ARGV[2]
    this.getLockName(threadId)
    9a4a13b1-789e-48db-b6bf-3c817c6e1667:1
ARGV[3]
    this.getWriteLockName(threadId)
    9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:write

       

5.2.2

local mode = redis.call('hget', KEYS[1], 'mode');
 if (mode == false) then 
    redis.call('hset', KEYS[1], 'mode', 'read');
    redis.call('hset', KEYS[1], ARGV[2], 1); 
    redis.call('set', KEYS[2] .. ':1', 1); 
    redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); 
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
end; 
if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then 
    local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); 
    local key = KEYS[2] .. ':' .. ind;
    redis.call('set', key, 1); redis.call('pexpire', key, ARGV[1]); 
    local remainTime = redis.call('pttl', KEYS[1]);
    redis.call('pexpire', KEYS[1], math.max(remainTime, ARGV[1]));
    return nil; 
end;
    return redis.call('pttl', KEYS[1]);

5.2.3 参数带入

local mode = redis.call('hget', readLockKey, 'mode');
 if (mode == false) then 
    redis.call('hset', readLockKey, 'mode', 'read');
    redis.call('hset', readLockKey, 9a4a13b1-789e-48db-b6bf-3c817c6e1667:1, 1); 
    redis.call('set', {readLockKey}:9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:rwlock_timeout .. ':1', 1); 
    redis.call('pexpire', {readLockKey}:9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:rwlock_timeout .. ':1', 30000); 
    redis.call('pexpire', readLockKey, 30000); 
    return nil; 
end; 
if (mode == 'read') or (mode == 'write' and redis.call('hexists', readLockKey, 9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:write) == 1) then 
    local ind = redis.call('hincrby', readLockKey, 9a4a13b1-789e-48db-b6bf-3c817c6e1667:1, 1); 
    local key = KEYS[2] .. ':' .. ind;
    redis.call('set', key, 1); 
    redis.call('pexpire', key, 30000); 
    local remainTime = redis.call('pttl', readLockKey);
    redis.call('pexpire', readLockKey, math.max(remainTime, 30000));
    return nil; 
end;
    return redis.call('pttl', readLockKey);

5.2.4 加锁成功之后 redis的样子

hash
readLockKey 
{
“mode”:read"9a4a13b1-789e-48db-b6bf-3c817c6e1667:1": 1
}

string
{readLockKey}:9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:rwlock_timeout:1 1
    
 加锁成功 返回null

5.3 解锁

5.4 读锁的 watch dog

6 写锁

6.1 加写锁

hash
readLockKey 
{
“mode”:write,
"9a4a13b1-789e-48db-b6bf-3c817c6e1667:1": 1
}

string
{readLockKey}:9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:rwlock_timeout:1 1
    
 加锁成功 返回null
local mode = redis.call('hget', KEYS[1], 'mode');
if (mode == false) then
    redis.call('hset', KEYS[1], 'mode', 'write'); 
    redis.call('hset', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]); 
    return nil; 
 end;
 if (mode == 'write') then 
     if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
         redis.call('hincrby', KEYS[1], ARGV[2], 1); 
         local currentExpire = redis.call('pttl', KEYS[1]);
         redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); 
         return nil; 
      end; 
 end;
 return redis.call('pttl', KEYS[1]);

7

7.1 读锁与读锁 非互斥

hash
readLockKey 
{
“mode”:read,
"9a4a13b1-789e-48db-b6bf-3c817c6e1667:1": 1,
"xxx:1": 1
}

string
{readLockKey}:9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:rwlock_timeout:1 1
 {readLockKey}:xxx:1:rwlock_timeout:1 1

都可以加锁成功

7.2 读写互斥

无论哪个在前加锁, 都会加锁失败,跳过所有的判断逻辑

7.3 同一个线程 先加读锁 再加一次写锁

加 写锁失败

7.4 同一个线程 先写锁 再来一个读锁

加锁成功
之后redis 结构是什么样子
hash
readLockKey 
{
“mode”:read"9a4a13b1-789e-48db-b6bf-3c817c6e1667:1": 1,
"xxx:write": 1
}

string
{readLockKey}:9a4a13b1-789e-48db-b6bf-3c817c6e1667:1:rwlock_timeout:1 1

7.5 同一个线程 先写锁 再来一个写锁

加锁成功
之后redis 结构是什么样子
hash
readLockKey 
{
“mode”:write,
"9a4a13b1-789e-48db-b6bf-3c817c6e1667:1": 2,
"xxx:write": 1
}

7.6 同一个线程 先写锁 再来一个写锁

加锁成功
之后redis 结构是什么样子
hash
readLockKey 
{
“mode”:write,
"9a4a13b1-789e-48db-b6bf-3c817c6e1667:1": 2,
"xxx:write": 1
}

7 思考题回答

7.1 redLock MultiLock 锁都是锁多个,有什么区别呢?

都假设 要锁3个
redLock   每个lockKey 超时时间分别为1500ms RedissonRedLock是基于RedissonMultiLock 继承的
MultiLock 3个锁共用4500ms的超时时间 可能一个锁就用了4.5s的超时时间

8 总结:

不同线程 
    读读不互斥 读写互斥 写写互斥
同一个线程
    读读不互斥 先读后写互斥, 先写后读不互斥 写写不互斥

9 下节预知

后面会快速过
然后找实际的问题,来分析问题再来补充这些文档

10 目标:

  1. 随着学习 把一些坑解决 形成一个 redission的基础组件

代码地址: gitee.com/gf-8/yuye-p…

项目: yuye-test-redission

类地址: 对应的test 包之下Test类

11 思考题

读写锁的 应用场景