redis分布式锁

为什么使用分布式锁

web应用中防止特别事务并发,或者需要保证业务操作顺序
在事务中,redis锁和数据库读写共同作用,达到线程安全
例如事务切面下

事务开始
//方法开始
redis.lock
select * from a
update a
redis.unlock
//方法结束
事务提交

那么可能就会发生事务A先占有锁并执行,执行完毕后释放锁,但是未提交,此时事务B也开始执行了,但是注意由于事务A未提交,a上的写锁未解锁,B事务依然卡在select * from a,因此依然达到了串行的效果

最简易锁

public boolean lock(String lockPrefix, String orderNo, int expireMillionSeconds) {
        String realKey = "RedisDistribLock_" + lockPrefix + "_" + orderNo;
        boolean res = this.redisService.setForLock(realKey, expireMillionSeconds);
        if (res) {
            this.logger.warn("====> 分布式锁 " + realKey + " 获取成功");
        } else {
            this.logger.warn("====> 分布式锁 " + realKey + " 获取失败");
        }

        return res;
    }

    public boolean unLock(String lockPrefix, String orderNo) {
        String realKey = "RedisDistribLock_" + lockPrefix + "_" + orderNo;
        boolean res = this.redisService.deleteByKey(realKey);
        if (res) {
            this.logger.warn("====> 分布式锁 " + realKey + " 删除成功");
        } else {
            this.logger.warn("====> 分布式锁 " + realKey + " 删除失败");
        }

        return res;
    }

setForLock

 public <T> boolean setForLock(String key, int expireMillionSeconds) {
        Jedis jedis = null;

        boolean var6;
        try {
            jedis = (Jedis)this.jedisPool.getResource();
            jedis.select(this.redisConfig.getSelectdb());
            SetParams setParams = new SetParams();
            setParams.nx();//
            setParams.px((long)expireMillionSeconds);
            String result = jedis.set(key, "1", setParams);
            if (result != null && result.equals("OK")) {
                var6 = true;
                return var6;
            }

            this.logger.info("=====> 分布式锁获取失败 key:" + key);
            var6 = false;
        } finally {
            this.returnToPool(jedis);
        }

        return var6;
    }

![image.png](https://img-blog.csdnimg.cn/img_convert/df05518d06764070436e840ea540a13d.png#clientId=ua202e097-c8aa-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=255&id=u496c04ef&margin=[object Object]&name=image.png&originHeight=382&originWidth=1368&originalType=binary&ratio=1&rotation=0&showTitle=false&size=94860&status=done&style=none&taskId=u0881cbce-1f8e-464f-a581-a8dffd7e41d&title=&width=912)deleteByKey

 public boolean deleteByKey(String key) {
        Jedis jedis = null;

        boolean var5;
        try {
            jedis = (Jedis)this.jedisPool.getResource();
            jedis.select(this.redisConfig.getSelectdb());
            long ret = jedis.del(key);
            var5 = ret > 0L;
        } finally {
            this.returnToPool(jedis);
        }

        return var5;
    }

这是最简单的redis分布式锁,简单的设置一个值,和过期时间占据锁,结算直接删除key,此种锁无阻塞

进阶redis锁

package com.xishan.store.item.server.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

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


@Component
public class RedisLock {

    @Autowired
    private RedisTemplate redisTemplate;


    private static final Long SUCCESS = 1L;
    //15秒未抢到,则失败,需要重新抢
    @Value("${lock.timeout:10000}")
    private long timeout; //获取锁的超时时间



    /**
     * 加锁,无阻塞
     *
     * @param
     * @param
     * @return
     */
    public Boolean  tryLock(String key, String value, long expireTime) {


        Long start = System.currentTimeMillis();
        try{
            for(;;){
                //SET命令返回OK ,则证明获取锁成功
                Boolean ret = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
                if(ret){
                    return true;
                }
                //否则循环等待,在timeout时间内仍未获取到锁,则获取失败
                long end = System.currentTimeMillis() - start;
                if (end>=timeout) {
                    return false;
                }
            }
        }finally {

        }

    }


    /**
     * 解锁
     *
     * @param
     * @param
     * @return
     */
    public Boolean unlock(String key, String value) {


        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Object result = redisTemplate.execute(redisScript, Collections.singletonList(key),value);
        if(SUCCESS.equals(result)) {
            return true;
        }

        return false;
    }

}

这种方式可以设置加锁失败的循环等待时间,并且可以多设置一个value,相当于多了一个参数,那么我们可以利用这个参数标识(例如UUID)加锁线程,这样就做到了 一个线程加的锁只有它自己能解锁。

可重入redis锁

@Slf4j
@Component
public class RedisDistributedLockImpl implements IRedisDistributedLock {

    /**
     * key前缀
     */
    public static final String PREFIX = "Lock:";
    /**
     * 保存锁的value
     */
    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

    private static final Charset UTF8 = Charset.forName("UTF-8");
    /**
     * 释放锁脚本
     */
    private static final String UNLOCK_LUA;

    /*
     * 释放锁脚本,原子操作
     */
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean lock(String key, long requireTimeOut, long lockTimeOut) {
        //可重入锁判断
        String originValue = threadLocal.get();
        if (!StringUtils.isBlank(originValue) && isReentrantLock(key, originValue)) {
            return true;
        }
        String value = UUID.randomUUID().toString();
        long end = System.currentTimeMillis() + requireTimeOut;
        try {
            while (System.currentTimeMillis() < end) {
                if (setNX(wrapLockKey(key), value, lockTimeOut)) {
                    threadLocal.set(value);
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private boolean setNX(String key, String value, long expire) {
        List<String> keyList = new ArrayList<>();
        keyList.add(key);
        return (boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            Boolean result = connection
                    .set(key.getBytes(UTF8),
                            value.getBytes(UTF8),
                            Expiration.milliseconds(expire),
                            RedisStringCommands.SetOption.SET_IF_ABSENT);
            return result;
        });

    }

    /**
     * 是否为重入锁
     */
    private boolean isReentrantLock(String key, String originValue) {
        String v = (String) redisTemplate.opsForValue().get(key);
        return v != null && originValue.equals(v);
    }

    @Override
    public boolean release(String key) {
        String originValue = threadLocal.get();
        if (StringUtils.isBlank(originValue)) {
            return false;
        }
        return (boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            return connection
                    .eval(UNLOCK_LUA.getBytes(UTF8), ReturnType.BOOLEAN, 1, wrapLockKey(key).getBytes(UTF8),
                            originValue.getBytes(UTF8));
        });
    }


    private String wrapLockKey(String key) {
        return PREFIX + key;
    }

}

threadLocal中存放线程生成的UUID 存为value
加锁,解锁都需要尝试取出这个value。如果和key不匹配都会加锁,或者解锁

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值