# redis相关

# redis相关,第1张

https://zhuanlan.zhihu.com/p/135864820

实现分布式锁

  • 在集群模式下,synchronized只能保证单个JVM内部的线程互斥,不能保证跨JVM的互斥

1、redisTemplate是基于某个具体实现的再封装,比如说springBoot1.x时,具体实现是jedis;而到了springBoot2.x时,具体实现变成了lettuce。封装的好处就是隐藏了具体的实现,使调用更简单,但是有人测试过jedis效率要10-30倍的高于redisTemplate的执行效率,所以单从执行效率上来讲,jedis完爆redisTemplate。redisTemplate的好处就是基于springBoot自动装配的原理,使得整合redis时比较简单。

2、jedis作为老牌的redis客户端,采用同步阻塞式IO,采用线程池时是线程安全的。优点是简单、灵活、api全面,缺点是某些redis高级功能需要自己封装。

3、lettuce作为新式的redis客户端,基于netty采用异步非阻塞式IO,是线程安全的,优点是提供了很多redis高级功能,例如集群、哨兵、管道等,缺点是api抽象,学习成本高。lettuce好是好,但是jedis比他生得早。

4、redission作为redis的分布式客户端,同样基于netty采用异步非阻塞式IO,是线程安全的,优点是提供了很多redis的分布式 *** 作和高级功能,缺点

是api抽象,学习成本高。

Jedissetnx

 

jedis是redis的java客户端,通过它可以对redis进行 *** 作。与之功能相似的还包括:Lettuce等

spring-boot-data-redis 内部实现了对Lettuce和jedis两个客户端的封装,默认使用的是Lettuce

/*场景一: 假如释放锁失败,则后面永远无法执行*/
lock_name 使用线程id或者一个唯一id

Jedis redis = getJedis();        
Long lockResult = redis.setnx(LOCK_NAME, LOCK_VALUE);
 if (1 == lockResult) {
            // 2. 执行业务
            executeBusiness();
            // 3. 释放锁
            redis.del(LOCK_NAME);
  } else {
            // 获取锁失败
            System.out.println("Can not get lock");
        }      


/*场景二: 释放锁失败,通过自动过期来保证*/
Jedis redis = getJedis();
String lockResult = redis.set(LOCK_NAME, LOCK_VALUE, "NX", "EX", EXPIRE_SECS);
if ("OK".equalsIgnoreCase(lockResult)) {
            executeBusiness();
            redis.del(LOCK_NAME);
} else {
            System.out.println("Can not get lock");
        }        
Redisson(Lua脚本)
KEYS[1]   key
ARGV[1]   key 的默认生存时间
ARGV[2]   加锁的客户端的 ID


if (redis.call('exists', KEYS[1]) == 0) then " +              //值过来先判断是否存在
   "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +           //命令设置一个 hash 结构
   "redis.call('pexpire', KEYS[1], ARGV[1]); " +              //设置存活时间
   "return nil; " +
   "end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +          //myLockkey的hash 数据结构中是否包含客户端2的ID
    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
    "return nil; " +
    "end; " +
"return redis.call('pttl', KEYS[1]);"  //返回myLock 这个锁key 的剩余生存时间

续期机制

Redisson 提供了一个续期机制, 只要客户端 1 一旦加锁成功,就会启动一个 Watch Dog。

private  RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
    if (leaseTime != -1) {
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    RFuture ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        if (e != null) {
            return;
        }

        // lock acquired
        if (ttlRemaining == null) {
            scheduleExpirationRenewal(threadId);
        }
    });
    return ttlRemainingFuture;
}

 

Watch Dog 机制必须使用默认的加锁时间为 30s。(`leaseTime` 为 -1 开启 Watch Dog 机制,)
自定义时间,超过这个时间,锁就会自定释放,并不会延长。

Watch Dog 机制是一个后台定时任务线程
获取锁成功之后,会将持有锁的线程放入到一个 `RedissonLock.EXPIRATION_RENEWAL_MAP`里面
每隔 10 秒 检查一下,如果客户端 1 还持有锁 key(判断客户端是否还持有 key,其实就是遍历 `EXPIRATION_RENEWAL_MAP` 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),那么就会不断的延长锁 key 的生存时间。

 

Lettuce
public class RedisLockUtil {

    /**
     * redis分布式锁-加锁
     * @param redisTemplate redis 加锁解锁要保证同一个
     * @param key 分布式锁key
     * @param value 分布式锁value 一般为随机数
     * @param timeout 分布式锁过期时间 秒
     * @param number 重试次数
     * @param interval 重试间隔 毫秒
     * @return
     */
    public static boolean lock(RedisTemplate redisTemplate, String key, String value, int timeout, int number, int interval) {
        //加锁
        for (int i = 0; i < number; i++) {
            //尝试获取锁,成功则返回不成功则重试
            if (redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(timeout))) {
                return true;
            }
            //暂停
            try {
                TimeUnit.MILLISECONDS.sleep(interval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //最终获取不到锁返回失败
        return false;
    }

    public static boolean lock(RedisTemplate redisTemplate, String key, String value) {
        return lock(redisTemplate, key, value, 30, 3, 1000);
    }

    /**
     * 解锁脚本,防止线程将其他线程的锁释放
     */
    private static String UN_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    /**
     * redis分布式锁-解锁
     * @param redisTemplate redis 加锁解锁要保证同一个
     * @param key 分布式锁key
     * @param value 分布式锁value 一般为随机数
     * @return
     */
    public static void unLock(RedisTemplate redisTemplate, String key, String value) {
        //解锁
        redisTemplate.execute(new DefaultRedisScript<>(UN_LOCK_SCRIPT, Long.class), Collections.singletonList(key), value);
    }

    /**
     * 获取调用者的类名和方法名
     * @return
     */
    public static String getMethodPath() {
        StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2];
        //获取当前类名
        String className = stackTraceElement.getClassName();
        //获取当前方法名
        String methodName = stackTraceElement.getMethodName();
        return className + "#" + methodName;
    }

    /**
     * 获取随机数
     * @return
     */
    public static String getRandom() {
        return Objects.toString(ThreadLocalRandom.current().nextInt(0, 100000));
    }
}

 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/720738.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存