栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

redis分布式锁

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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  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; //获取锁的超时时间



    
    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 {

        }

    }


    
    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 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 {

    
    public static final String PREFIX = "Lock:";
    
    private ThreadLocal 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 keyList = new ArrayList<>();
        keyList.add(key);
        return (boolean) redisTemplate.execute((RedisCallback) 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) 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不匹配都会加锁,或者解锁

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/769004.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号