redis实现分布式锁

分布式锁的必要性

在分布式集群系统中,由于分布式系统的多线程、多进程分布在不同的机器上,为了在这种情况下解决并发控制而引入了分布式锁技术。通过分布式锁可以在跨JVM的互斥控制机制来处理共享资源的多线程、多进程访问问题。

分布式锁应该具备的条件

  1. 在分布式系统环境下,同一个时间点一个方法只能被一个机器的一个线程执行。
    2)高性能的获取锁和释放锁。
    3)高可用的获取锁和释放锁。
    4)具有锁失效机制,防止死锁。
  2. 具备可重入特性。
    6)具有非阻塞锁特性,即没有获取到锁立即返回获取锁失败。

分布式锁的三种实现

目前几乎所有的大型网站或应用都是分布式进行部署的,分布式场景中数据一致性问题一直是一个比较重要的话题。分布式CAP理论告诉我们,任何一个分布式系统都不可能同时满足一致性(Consistency),可用性(Availability),分区容错性(Partition Tolerance),最多同时满足两项。在互联网环境下,一般会牺牲强一致性来换取系统的高可用性,往往系统只需要保证最终一致性,只要最终时间是用户可接受的时间范围内即可。
在很多场景下,为了达到最终一致性,会使用到技术分布式锁,分布式事务等技术。有的时候一个方法在一个时间范围内只能被一个线程执行。
基于数据库实现分布式锁
基于缓存(Redis等)实现分布式锁
基于Zookeeper实现分布式锁

1. 单机版分布式锁的实现

第一步:锁请求实体封装

@Data
public class LockParam {

    private String key;
    private String type; //0-只执行一次;1-按次数; 2-按时间
    private long times;  //当type为1或2时,此值为必传参数;为1是代表次数;为2时代表等待时长
    private long lockExpireTime = 2 * 60 * 1000;
}

锁的执行方式枚举

@AllArgsConstructor
@Getter
public enum  LockUseTypeEnum {

    JUST_ONCE("0", "只获取一次锁"), BY_FREQUENCY("1", "按次数获取锁"), BY_TIME("2", "按时间获取锁");

    private String code;
    private String name;
}

第二步:锁的具体实现

/**
 * 分布式锁的实现
 */
public class DistributedLock {

    /** 分布式锁前缀 **/
    private static final String LOCK_PREFIX = "distribute_lock_";

    private static final long DEFAULT_SLEEP_TIME = 100L;

    public boolean lock(LockParam lockParam) {

        if(LockUseTypeEnum.JUST_ONCE.getCode().equals(lockParam.getType()) || (!LockUseTypeEnum.JUST_ONCE.getCode().equals(lockParam.getType()) && lockParam.getTimes() <=0L)){
            return getNewLock(lockParam.getKey(), lockParam.getLockExpireTime());
        }else if(LockUseTypeEnum.BY_FREQUENCY.getCode().equals(lockParam.getType())){
            do{
                if(getNewLock(lockParam.getKey(), lockParam.getLockExpireTime())){
                    return true;
                }
                lockParam.setTimes(lockParam.getTimes()-1);
                try{
                    Thread.sleep(DEFAULT_SLEEP_TIME);
                }catch (InterruptedException e){
                    return false;
                }
            }while(lockParam.getTimes() > 0);
        }else {
            do{
                if(getNewLock(lockParam.getKey(), lockParam.getLockExpireTime())){
                    return true;
                }

                long thisSleepTime  = 0L;

                if(lockParam.getTimes()<100L){
                    thisSleepTime = lockParam.getTimes();
                }else{
                    thisSleepTime = NumberUtils.getRandom(50, 200);
                }
                lockParam.setTimes(lockParam.getTimes() - thisSleepTime);
                try{
                    Thread.sleep(thisSleepTime);
                }catch (InterruptedException e){
                    return false;
                }
            }while(lockParam.getTimes()>0L);
        }
        return false;
    }

    private boolean getNewLock(String key, long lockExpireTime){
        RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
        String lockKey = LOCK_PREFIX + key;
        long expires = System.currentTimeMillis() + lockExpireTime + 1;
        String expiresStr = String.valueOf(expires); // 锁到期时间
        if(redisUtil.setNX(lockKey, expiresStr)){
            return true;
        }

        String currentValueStr = redisUtil.getNX(lockKey); //this.get(lockKey); // redis里的时间
        if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
            String oldValueStr = redisUtil.getSet(lockKey, expiresStr);
            if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                return true;
            }
        }
        return false;
    }

    public void unlock(String key){
        RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
        String lockKey = LOCK_PREFIX + key;
        redisUtil.delete(lockKey);
    }
}

第三步:redis操作相关方法

public boolean setNX(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute((RedisCallback<Object>) connection -> {
                StringRedisSerializer serializer = new StringRedisSerializer();
                Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
                connection.close();
                return success;
            });
        } catch (Exception e) {
            return false;
        }
        return obj != null ? (Boolean) obj : false;
    }

    public String getNX(final String key) {
        Object obj = null;
        try {
            obj = redisTemplate.execute((RedisCallback<Object>) connection -> {
                StringRedisSerializer serializer = new StringRedisSerializer();
                byte[] data = connection.get(serializer.serialize(key));
                connection.close();
                if (data == null) {
                    return null;
                }
                return serializer.deserialize(data);
            });
        } catch (Exception e) {
            return null;
        }
        return obj != null ? obj.toString() : null;
    }

    public String getSet(final String key, final String value) {
        Object obj = null;
        try {
            obj = redisTemplate.execute((RedisCallback<Object>) connection -> {
                StringRedisSerializer serializer = new StringRedisSerializer();
                byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
                connection.close();
                return serializer.deserialize(ret);
            });
        } catch (Exception e) {
            return null;
        }
        return obj != null ? (String) obj : null;
    }

参考:https://blog.csdn.net/weixin_38003389/article/details/89434629
参考:https://blog.csdn.net/wuzhiwei549/article/details/80692278
参考:https://blog.csdn.net/russle/article/details/103106238

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 学习开发资料 自己搜索使用过的一些资料,仅供参考。 资源中心 - 微信开放平台:https://open.weix...
    猿王阅读 704评论 0 1
  • 1说说JVM垃圾回收机制。 http://blog.csdn.net/xiajian2010/article/de...
    Alfred泉阅读 802评论 0 2
  • 原创链接 一、Java面试题java有多重要,对于做android的我们,不需要多说了,let’s go (1)J...
    李福来阅读 2,366评论 0 5
  • CSS 微软雅黑写法 “微软雅黑”使用英文“Microsoft YaHei” 内部阴影 css3 -- box-s...
    zlf_j阅读 2,469评论 0 2
  • 返回上一页: 点击回到顶部 解决办法: a标签跳转锚点到页面指定位置 https://blog.csdn.net/...
    zlf_j阅读 1,462评论 0 1