• Redis分布式锁


    1. 分布式锁的特点

    1. 互斥性:同一时刻只有一个客户端可以持有锁
    2. 容错性:只要锁服务集群中大部分节点正常运行,客户端就可以进行锁操作
    3. 避免死锁:保证锁一定能释放,正常释放或超时释放
    4. 加锁和解锁是同一个客户端

    2. 分布式锁的实现方式

    1. 基于数据库实现分布式锁(乐观锁、悲观锁)
    2. 基于zookeeper时节点的分布式锁
    3. 基于Redis的分布式锁
    4. 基于Etcd的分布式锁

    3. Redis方式实现分布式锁

      3.1 获取分布式锁

      在Redis2.6.12版本之前,使用Lua脚本保证设置键值对设置过期时间两个操作的原子性。

      在Redis2.6.12版本开始,使用Set命令实现加锁

    public static boolean tryLock(Jedis jedis,String lockKey,String lockValue,int expireTime){
        String result = jedis.set(lockKey, lockValue,"NX","PX",expireTime);
        if("OK".equals(result)) return true;
        return false;
    }

      其中,过期时间短业务流程设置2秒,超长业务流程设置6秒,一般在2~6秒之间。必须设置过期时间,否则在释放锁时服务宕机,则造成死锁

      "NX":SET IF NOT EXISTS,如果键不存在则设置,否则不做操作 / 若为"XX":SET IF EXISTS,只有键存在时才设置

      "PX":需要设置过期时间,单位毫秒,由expireTime决定 / 若为"EX",则单位为秒

      3.2 释放分布式锁

      使用Lua脚本,在删除锁的之前必须确保删除的是当前线程占有的锁(比对释放时的value和获取时的value)。

    //解锁Lua脚本,必须先比较value,防止误解锁
    private static final String SCRIPT_UNLOCK = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
    
    public static boolean unLockLua(Jedis jedis,String lockKey,String lockValue){
        long result = (long)jedis.eval(SCRIPT_UNLOCK,Arrays.asList(lockKey),Arrays.asList(lockValue));
        if(result == 1) return true;
        return false;
    }

       锁value的设置方式

    1. 将加锁的线程ID(服务标识+线程ID)
    2. 时间戳+服务标识生成随机数
    3. 官方推荐从 /dev/urandom 中取20个byte作为随机数

       3.3 守护线程"续航"锁

       问题:当锁的超时时间设置短于实际业务需求,导致Redis意外删除锁。

      在线程获取锁的同时,创建一个守护线程,该守护线程周期性延长锁的过期时间。在主线程处理完,安全释放锁的同时,删除守护线程。

      参考:https://www.cnblogs.com/wangzaiplus/p/10864135.html

    4. Redis分布式锁优缺点

      基于内存,速度快。需要程序处理原子性、超时、误删的情况,在获取锁失败时,客户端只能自旋等待,在高并发的情况下,性能消耗比较大

      CAP原理:在分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)三者不可兼得,分为CP(强一致模型)和AP(高可用模型)

    • 一致性:所有节点的数据同一时刻是一致的
    • 可用性:每个请求不管成功或者失败在一定时间内都有响应
    • 分区容错性:当网络出现异常导致分区节点间无法通信,要保证整个系统是可用的

    5. 生产环境中的分布式锁-Redisson

      加锁机制

      加锁命令 hset lockKey key value

      其中key的生成规则:UUID + ":" + threadId;value的值初始为1,代表重入次数。加锁时长默认30秒

      锁互斥机制、可重入机制

      当客户端尝试加锁时,判断lockKey是否存在,若存在,查看key是否对应当前客户端,若不是则加锁失败返回锁剩余时长,客户端进入轮训。否则重入次数+1

      自动延时机制

      加锁成功后,启动一个看门狗线程,是一个辅助线程,每10秒检查一次,若当前加锁客户端依然持有锁,则延长加锁时间

      锁释放机制

      每次释放,锁的重入次数减1。当重入次数为0时,del lockKey

     6. Redis集群模式下的分布式锁

      之前讨论单机模式下的加锁,在主从模式下,主从复制之间存在延迟,如果主机尚未将加锁key信息同步到从机,主机下线了,从机上线,此时加锁信息就丢失了。为了处理这种情况,可以部署多组独立的主从服务,对每组都采用一样的加锁操作,只有在半数以上加锁成功才表示真正获得锁,否则认为加锁失败。

       参考:https://www.jianshu.com/p/8605713cb83b

    人生就像蒲公英,看似自由,其实身不由己。
  • 相关阅读:
    后端程序员之路 28、一个轻量级HTTP Server的实现
    后端程序员之路 27、LogStash
    后端程序员之路 26、CAP理论
    后端程序员之路 25、Redis Cluster
    后端程序员之路 24、Redis hiredis
    后端程序员之路 23、一个c++的api framework
    后端程序员之路 22、RESTful API
    后端程序员之路 21、一个cgi的c++封装
    后端程序员之路 20、python复习
    flask框架的学习
  • 原文地址:https://www.cnblogs.com/walker993/p/14501372.html
Copyright © 2020-2023  润新知