Redis 高级使用

生成全局 id

id 使用Long 类型 ,8个字节 , 64 bit

image

ID的组成部分:

符号位: 1bit,永远为0
时间戳:31bit,以秒为单位,可以使用69年
序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID

防止用户从id上推断出业务相关信息 ,为了增加ID的安全性,可以不直接使用Redis自增的数值,而是拼接一些其它信息 , 例如时间戳

@Component
public class RedisIdUtil {

    @Autowired
    private StringRedisTemplate  stringRedisTemplate;

    //  计算的开始时间 可以自定义
    private  static  final Long BEGIN_TIMESTAMP  =  000L ;

    private  static  final Integer COUNT_BITS  =  32 ;

    public long generateId(String keyPrefix) {

        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2.生成序列号
        // 2.1.获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2.自增长
        long count = stringRedisTemplate.opsForValue().increment(  "icr:" + keyPrefix + ":" + date);
        // 3.拼接并返回
        return timestamp << COUNT_BITS | count;
    }
}

实现分布式锁

Lua 脚本

        if(redis.call('get', KEYS[1]) ==ARGV[1]) then
        -- 郑放锁 del key
        end
        return redis.call('del', KEYS[1])
        return 0

使用

import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author: tang
 * @Date: 2022/12/8
 * @Description:
 */

public class RedisLockUtil {

    private  String  name  ;


    private StringRedisTemplate  stringRedisTemplate;

    public RedisLockUtil(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }


    private static final String KEY_PREFIX = "lock:";

    //  集群模式下 设置不同的jvm标识
    private static final String ID_PREFIX = UUID.randomUUID().toString()+"-"  ;


    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;

    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);

    }


    public boolean tryLock(long timeoutSec) {
        // 获取线程标识  防止锁超时后  误删
        String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent( KEY_PREFIX +name , threadId , timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);

    }


    public void unlock() {

        //限用Lua脚本  确保原子性操作
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name), ID_PREFIX + Thread.currentThread().getId());


/*        // 获取线程标示   防止锁超时后  误删
        String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁中的标示
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断标示是否一致
        if(threadId.equals(id))

        if(redis.call('get', KEYS[1]) ==ARGV[1]) then
        -- 郑放锁 del key
        end
        return redis.call('del', KEYS[1])
        return 0




        // 释放锁
            stringRedisTemplate .delete( KEY_PREFIX + name);*/
    }
}

存在的问题:

不可重入

同一个线程无法多次获取同一把锁

不可重试

获取锁只尝试一次就返回false,没有重试机制

超时释放

锁超时释放虽然可以避免死锁,但如果是业务执行耗时较长,也会导致锁释放,存在安全隐患

主从一致性

如果Redis提供了主从集群主从同步存在延迟,当主岩机时,如果从并同步主中的锁数据,则会出现锁实现

posted @ 2022-12-09 10:43  原来是晴天啊  阅读(56)  评论(0编辑  收藏  举报