Redis的分布式锁实现方案

Springboot集成Klock简单实现分布式锁 同一个方法,不同参数,来修改一个hash结构缓存的不同field,可以将field作为二级key,那么不同的field就有不同的lockKey,不会互相影响的业务,就可以不用阻塞了。但是如果是订单中,同一个订单中有不同的品名ID的商品,这些品名对应不同的key,在下单的时候,防止超卖,每个品命的库存都需要被保护的,即使其他的业务方法,想修改多个key中的其中的一个key,都不行,那么@Klock的注解实现方式就不可行。lockType:锁的类型,目前支持(可重入锁,公平锁,读写锁)。 阅读详情

前言

在Java中,关于锁我想大家都很熟悉。在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常,我们以synchronized 、Lock来使用它。
但是Java中的锁,只能保证在同一个JVM进程内中执行。如果在分布式集群环境下呢?

分布式锁

分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:

加锁
在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。

解锁
把脚印从沙滩上抹去,就是解锁的过程。

锁超时
为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。

分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。

安全和可靠性保证

  • 安全属性:互斥,不管任何时候,只有一个客户端能持有同一个锁。
  • 效率属性A:不会死锁,最终一定会得到锁,就算一个持有锁的客户端宕掉或者发生网络分区。
  • 效率属性B:容错,只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。

为什么基于故障切换的方案不够好

为了理解我们想要提高的到底是什么,我们先看下当前大多数基于Redis的分布式锁三方库的现状。 用Redis来实现分布式锁最简单的方式就是在实例里创建一个键值,创建出来的键值一般都是有一个超时时间的(这个是Redis自带的超时特性),所以每个锁最终都会释放(参见前文属性2)。而当一个客户端想要释放锁时,它只需要删除这个键值即可。 表面来看,这个方法似乎很管用,但是这里存在一个问题:在我们的系统架构里存在一个单点故障,如果Redis的master节点宕机了怎么办呢?有人可能会说:加一个slave节点!在master宕机时用slave就行了!但是其实这个方案明显是不可行的,因为这种方案无法保证第1个安全互斥属性,因为Redis的复制是异步的。 总的来说,这个方案里有一个明显的竞争条件(race condition),举例来说:

  • 客户端A在master节点拿到了锁。
  • master节点在把A创建的key写入slave之前宕机了。
  • slave变成了master节点 4.B也得到了和A还持有的相同的锁(因为原来的slave里还没有A持有锁的信息)

当然,在某些特殊场景下,前面提到的这个方案则完全没有问题,比如在宕机期间,多个客户端允许同时都持有锁,如果你可以容忍这个问题的话,那用这个基于复制的方案就完全没有问题,否则的话我们还是建议你采用这篇文章里接下来要描述的方案。

采用单实例的正确实现

在讲述如何用其他方案突破单实例方案的限制之前,让我们先看下是否有什么办法可以修复这个简单场景的问题,因为这个方案其实如果可以忍受竞争条件的话是有望可行的,而且单实例来实现分布式锁是我们后面要讲的算法的基础。 要获得锁,要用下面这个命令: SET resource_name my_random_value NX PX 30000 这个命令的作用是在只有这个key不存在的时候才会设置这个key的值(NX选项的作用),超时时间设为30000毫秒(PX选项的作用) 这个key的值设为“my_random_value”。这个值必须在所有获取锁请求的客户端里保持唯一。 基本上这个随机值就是用来保证能安全地释放锁,我们可以用下面这个Lua脚本来告诉Redis:删除这个key当且仅当这个key存在而且值是我期望的那个值。

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

这个很重要,因为这可以避免误删其他客户端得到的锁,举个例子,一个客户端拿到了锁,被某个操作阻塞了很长时间,过了超时时间后自动释放了这个锁,然后这个客户端之后又尝试删除这个其实已经被其他客户端拿到的锁。所以单纯的用DEL指令有可能造成一个客户端删除了其他客户端的锁,用上面这个脚本可以保证每个客户单都用一个随机字符串’签名’了,这样每个锁就只能被获得锁的客户端删除了。

这个随机字符串应该用什么生成呢?我假设这是从/dev/urandom生成的20字节大小的字符串,但是其实你可以有效率更高的方案来保证这个字符串足够唯一。比如你可以用RC4加密算法来从/dev/urandom生成一个伪随机流。还有更简单的方案,比如用毫秒的unix时间戳加上客户端id,这个也许不够安全,但是也许在大多数环境下已经够用了。

key值的超时时间,也叫做”锁有效时间”。这个是锁的自动释放时间,也是一个客户端在其他客户端能抢占锁之前可以执行任务的时间,这个时间从获取锁的时间点开始计算。 所以现在我们有很好的获取和释放锁的方式,在一个非分布式的、单点的、保证永不宕机的环境下这个方式没有任何问题。

如何实现分布式锁

本文基于注解来实现分布式锁

项目结构图下图
在这里插入图片描述

RedisLock

/**
 * 加锁的注解
 */

@Target(value = {ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {

    /**
     * 锁的名称
     */
    String name() default "";

    /**
     * 锁的value
     */
    String value() default "";

    /**
     * 过期时间(如果不设置默认60s)
     */
    int expireTime() default 60;


}

RedislockAspectHandler

/**
 * @FileName: KlockAspectHandler.java
 * @Description: 给添加@RedisLock切面加锁处理
 * @Author: kimwu
 * @Time: 2020-12-19 14:08:56
 */
@Aspect
@Component
@Order(0)
@Slf4j
public class RedislockAspectHandler {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Pointcut("@annotation(com.kimwu.kill.mall.redis.lock.annotation.RedisLock)")
    public void lockPointcut() {
        log.info("-------------------");
    }


    @Before("lockPointcut()")
    public Object around(JoinPoint joinPoint) throws Throwable {
        RedisLock redisLock = getKimLock(joinPoint);
        RedisLockModel redisLockModel = RedisLockModelProvider.get(joinPoint, redisLock);
        if (ObjectUtils.isAllFieldNull(redisLockModel)) {
            throw new RuntimeException("对象不可为空");
        }
        //执行加锁过程
        RedisDistributionLockPlus redisDistributionLockPlus = new RedisDistributionLockPlus(stringRedisTemplate);
        CurentMapUtils.currentThreadLock.put(CurentMapUtils.getCurrentLockId(), redisDistributionLockPlus);
        boolean lock = redisDistributionLockPlus.lock(redisLockModel.getName(), redisLockModel.getValue(), redisLockModel.getExpireTime());
        log.info("{} 获取锁结果 {}", redisLockModel, lock);
        return joinPoint;
    }

    @AfterReturning(pointcut = "lockPointcut()")
    public void afterReturning(JoinPoint joinPoint) throws Throwable {
        RedisLock redisLock = getKimLock(joinPoint);
        RedisLockModel redisLockModel = RedisLockModelProvider.get(joinPoint, redisLock);
        if (ObjectUtils.isAllFieldNull(redisLockModel)) {
            throw new RuntimeException("对象不可为空");
        }
        RedisDistributionLockPlus redisDistributionLockPlus = CurentMapUtils.currentThreadLock.get(CurentMapUtils.getCurrentLockId());

        boolean unlock = redisDistributionLockPlus.unlock(redisLockModel.getName(), redisLockModel.getValue());
        log.info("{} 释放锁结果 {}", redisLockModel, unlock);
    }

    @AfterThrowing(pointcut = "lockPointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) throws Throwable {
        RedisLock redisLock = getKimLock(joinPoint);
        RedisLockModel redisLockModel = RedisLockModelProvider.get(joinPoint, redisLock);
        if (ObjectUtils.isAllFieldNull(redisLockModel)) {
            throw new RuntimeException("对象不可为空");
        }
        RedisDistributionLockPlus redisDistributionLockPlus = CurentMapUtils.currentThreadLock.get(CurentMapUtils.getCurrentLockId());

        boolean unlock = redisDistributionLockPlus.unlock(redisLockModel.getName(), redisLockModel.getValue());
        log.info("{} 释放锁结果 {}", redisLockModel, unlock);
        throw e;
    }


    private RedisLock getKimLock(JoinPoint joinPoint) {
        RedisLock redisLock = null;
        //获得所在切点的该类的class对象,也就是UserController这个类的对象
        Class<?> aClass = joinPoint.getTarget().getClass();
        //获取该切点所在方法的名称
        String name = joinPoint.getSignature().getName();
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        try {
            //通过反射获得该方法
            Method method = aClass.getMethod(name, parameterTypes);
            //获得该注解
            redisLock = method.getAnnotation(RedisLock.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return redisLock;
    }
}

RedisLockModelProvider

/**
 * @FileName: KimLockModelProvider.java
 * @Description: 获取锁的信息
 * @Author: kimwu
 * @Time: 2020-12-19 14:15:22
 */
@Slf4j
public class RedisLockModelProvider {

    static ThreadLocal threadLocal = new ThreadLocal();

    public static RedisLockModel get(JoinPoint joinPoint, RedisLock redisLock) {
        if (threadLocal.get() == null) {
            if (StringUtils.isNotBlank(redisLock.value())) {
                threadLocal.set(redisLock.value());
            } else {
                threadLocal.set(UUID.randomUUID().toString());
            }
        }
        return new RedisLockModel(redisLock.name(), threadLocal.get().toString(), getExpiteTime(redisLock));
    }


    public static Integer getExpiteTime(RedisLock lock) {
        return lock.expireTime() == Integer.MIN_VALUE ?
                60 : lock.expireTime();
    }
}

RedisDistributionLockPlus

/**
 * redis分布式锁
 * redis分布式锁过期时间到了,但业务还没执行完,怎么办?
 * redisson给的答案是锁获取成功后,注册一个定时任务,每隔一定时间(this.internalLockLeaseTime / 3L)就去续约。
 * internalLockLeaseTime可配置,默认30s。
 * 这种方式每次获取一个锁,就会创建一个定时任务,有些浪费
 * 这里借鉴jvm对自旋锁优化的思想,将续约的key的耗时保存在redis,再次获取锁时,直接使用上次耗时 + 10s 作为锁的过期时间,
 * 不用每次都去续约。
 * 缺点:获取锁后->释放锁前,若每次执行耗时差距过大(业务方法的各条流程耗时差距过大),将得不到预期的效果
 */
@Slf4j
@Service
public class RedisDistributionLockPlus {

    /**
     * 加锁超时时间,单位毫秒, 即:加锁时间内执行完操作,如果未完成会有并发现象
     */
    private static final long DEFAULT_LOCK_TIMEOUT = 30;

    private static final long TIME_SECONDS_FIVE = 5;

    /**
     * 每个key的过期时间 {@link LockContent}
     */
    private Map<String, LockContent> lockContentMap = new ConcurrentHashMap<>(512);

    /**
     * redis执行成功的返回
     */
    private static final Long EXEC_SUCCESS = 1L;

    /**
     * 获取锁lua脚本, k1:获锁key, k2:续约耗时key, arg1:requestId,arg2:超时时间
     */
    private static final String LOCK_SCRIPT = "if redis.call('exists', KEYS[2]) == 1 then ARGV[2] = math.floor(redis.call('get', KEYS[2]) + 10) end " +
            "if redis.call('exists', KEYS[1]) == 0 then " +
            "local t = redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]) " +
            "for k, v in pairs(t) do " +
            "if v == 'OK' then return tonumber(ARGV[2]) end " +
            "end " +
            "return 0 end";

    /**
     * 释放锁lua脚本, k1:获锁key, k2:续约耗时key, arg1:requestId,arg2:业务耗时 arg3: 业务开始设置的timeout
     */
    private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "local ctime = tonumber(ARGV[2]) " +
            "local biz_timeout = tonumber(ARGV[3]) " +
            "if ctime > 0 then  " +
            "if redis.call('exists', KEYS[2]) == 1 then " +
            "local avg_time = redis.call('get', KEYS[2]) " +
            "avg_time = (tonumber(avg_time) * 8 + ctime * 2)/10 " +
            "if avg_time >= biz_timeout - 5 then redis.call('set', KEYS[2], avg_time, 'EX', 24*60*60) " +
            "else redis.call('del', KEYS[2]) end " +
            "elseif ctime > biz_timeout -5 then redis.call('set', KEYS[2], ARGV[2], 'EX', 24*60*60) end " +
            "end " +
            "return redis.call('del', KEYS[1]) " +
            "else return 0 end";
    /**
     * 续约lua脚本
     */
    private static final String RENEW_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";


    private final StringRedisTemplate redisTemplate;

    public RedisDistributionLockPlus(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        ScheduleTask task = new ScheduleTask(this, lockContentMap);
        // 启动定时任务
        ScheduleExecutor.schedule(task, 1, 1, TimeUnit.SECONDS);
    }

    /**
     * 加锁
     * 取到锁加锁,取不到锁一直等待知道获得锁
     *
     * @param lockKey
     * @param requestId 全局唯一
     * @param expire    锁过期时间, 单位秒
     * @return
     */
    public boolean lock(String lockKey, String requestId, long expire) {
        log.info("开始执行加锁, lockKey ={}, requestId={}", lockKey, requestId);
        for (; ; ) {
            // 判断是否已经有线程持有锁,减少redis的压力
            LockContent lockContentOld = lockContentMap.get(lockKey);
            boolean unLocked = null == lockContentOld;
            // 如果没有被锁,就获取锁
            if (unLocked) {
                long startTime = System.currentTimeMillis();
                // 计算超时时间
                long bizExpire = expire == 0L ? DEFAULT_LOCK_TIMEOUT : expire;
                String lockKeyRenew = lockKey + "_renew";

                RedisScript<Long> script = RedisScript.of(LOCK_SCRIPT, Long.class);
                List<String> keys = new ArrayList<>();
                keys.add("{redisLock}" + lockKey);
                keys.add("{redisLock}" + lockKeyRenew);
                Long lockExpire = redisTemplate.execute(script, keys, requestId, Long.toString(bizExpire));
                if (null != lockExpire && lockExpire > 0) {
                    // 将锁放入map
                    LockContent lockContent = new LockContent();
                    lockContent.setStartTime(startTime);
                    lockContent.setLockExpire(lockExpire);
                    lockContent.setExpireTime(startTime + lockExpire * 1000);
                    lockContent.setRequestId(requestId);
                    lockContent.setThread(Thread.currentThread());
                    lockContent.setBizExpire(bizExpire);
                    lockContent.setLockCount(1);
                    lockContentMap.put(lockKey, lockContent);
                    log.info("加锁成功, lockKey ={}, requestId={}", lockKey, requestId);
                    return true;
                }

            }
            if (lockContentOld != null) {
                // 重复获取锁,在线程池中由于线程复用,线程相等并不能确定是该线程的锁
                if (Thread.currentThread() == lockContentOld.getThread()
                        && requestId.equals(lockContentOld.getRequestId())) {
                    // 计数 +1
                    lockContentOld.setLockCount(lockContentOld.getLockCount() + 1);
                    return true;
                }
            }
            // 如果被锁或获取锁失败,则等待100毫秒
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                // 这里用lombok 有问题
                log.error("获取redis 锁失败, lockKey ={}, requestId={}", lockKey, requestId, e);
                return false;
            }
        }
    }


    /**
     * 解锁
     *
     * @param lockKey
     * @param lockValue
     */
    public boolean unlock(String lockKey, String lockValue) {
        String lockKeyRenew = lockKey + "_renew";
        LockContent lockContent = lockContentMap.get(lockKey);

        long consumeTime;
        if (null == lockContent) {
            consumeTime = 0L;
        } else if (lockValue.equals(lockContent.getRequestId())) {
            int lockCount = lockContent.getLockCount();
            // 每次释放锁, 计数 -1,减到0时删除redis上的key
            if (--lockCount > 0) {
                lockContent.setLockCount(lockCount);
                return false;
            }
            consumeTime = (System.currentTimeMillis() - lockContent.getStartTime()) / 1000;
        } else {
            log.info("释放锁失败,不是自己的锁。");
            return false;
        }

        // 删除已完成key,先删除本地缓存,减少redis压力, 分布式锁,只有一个,所以这里不加锁
        lockContentMap.remove(lockKey);

        RedisScript<Long> script = RedisScript.of(UNLOCK_SCRIPT, Long.class);
        List<String> keys = new ArrayList<>();
        keys.add("{redisLock}" + lockKey);
        keys.add("{redisLock}" + lockKeyRenew);

        Long result = redisTemplate.execute(script, keys, lockValue, Long.toString(consumeTime),
                Long.toString(lockContent.getBizExpire()));
        return EXEC_SUCCESS.equals(result);

    }

    /**
     * 多服务器集群,使用下面的方法,代替System.currentTimeMillis(),获取redis时间,避免多服务的时间不一致问题!!!
     *
     * @return
     */
    public long currtTimeForRedis() {
        return redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.time());
    }

    /**
     * 续约
     *
     * @param lockKey
     * @param lockContent
     * @return true:续约成功,false:续约失败(1、续约期间执行完成,锁被释放 2、不是自己的锁,3、续约期间锁过期了(未解决))
     */
    public boolean renew(String lockKey, LockContent lockContent) {

        // 检测执行业务线程的状态
        Thread.State state = lockContent.getThread().getState();
        if (Thread.State.TERMINATED == state) {
            log.info("执行业务的线程已终止,不再续约 lockKey ={}, lockContent={}", lockKey, lockContent);
            return false;
        }

        String requestId = lockContent.getRequestId();
        long timeOut = (lockContent.getExpireTime() - lockContent.getStartTime()) / 1000;

        RedisScript<Long> script = RedisScript.of(RENEW_SCRIPT, Long.class);
        List<String> keys = new ArrayList<>();
        keys.add("{redisLock}" + lockKey);

        Long result = redisTemplate.execute(script, keys, requestId, Long.toString(timeOut));
        log.info("续约结果,True成功,False失败 lockKey ={}, result={}", lockKey, EXEC_SUCCESS.equals(result));
        return EXEC_SUCCESS.equals(result);
    }


    static class ScheduleExecutor {

        public static void schedule(ScheduleTask task, long initialDelay, long period, TimeUnit unit) {
            long delay = unit.toMillis(initialDelay);
            long period_ = unit.toMillis(period);
            // 定时执行
            new Timer("Lock-Renew-Task").schedule(task, delay, period_);
        }
    }

    static class ScheduleTask extends TimerTask {

        private final RedisDistributionLockPlus redisDistributionLock;
        private final Map<String, LockContent> lockContentMap;

        public ScheduleTask(RedisDistributionLockPlus redisDistributionLock, Map<String, LockContent> lockContentMap) {
            this.redisDistributionLock = redisDistributionLock;
            this.lockContentMap = lockContentMap;
        }

        @Override
        public void run() {
            if (lockContentMap.isEmpty()) {
                return;
            }
            Set<Map.Entry<String, LockContent>> entries = lockContentMap.entrySet();
            for (Map.Entry<String, LockContent> entry : entries) {
                String lockKey = entry.getKey();
                LockContent lockContent = entry.getValue();
                long expireTime = lockContent.getExpireTime();
                // 减少线程池中任务数量
                if ((expireTime - System.currentTimeMillis()) / 1000 < TIME_SECONDS_FIVE) {
                    //线程池异步续约
                    ThreadPool.submit(() -> {
                        boolean renew = redisDistributionLock.renew(lockKey, lockContent);
                        if (renew) {
                            long expireTimeNew = lockContent.getStartTime() + (expireTime - lockContent.getStartTime()) * 2 - TIME_SECONDS_FIVE * 1000;
                            lockContent.setExpireTime(expireTimeNew);
                        } else {
                            // 续约失败,说明已经执行完 OR com.kimwu.kill.mall.redis 出现问题
                            lockContentMap.remove(lockKey);
                        }
                    });
                }
            }
        }
    }


    @Data
    @ToString
    public static class LockContent implements Serializable {

        /**
         * 锁过期时间,单位秒
         */
        private volatile long lockExpire;

        /**
         * 锁过期时间,单位毫秒
         */
        private volatile long expireTime;

        /**
         * 获取锁的开始时间,单位毫秒
         */
        private volatile long startTime;

        /**
         * 用于防止锁的误删,全局唯一
         */
        private String requestId;

        /**
         * 执行业务的线程
         */
        private volatile Thread thread;

        /**
         * 业务调用设置的锁过期时间,单位秒
         */
        private long bizExpire;

        /**
         * 重入次数
         */
        private int lockCount = 0;

    }


    static class ThreadPool {

        private static final int CORESIZE = Runtime.getRuntime().availableProcessors();
        private static final int MAXSIZE = 200;
        private static final int KEEPALIVETIME = 60;
        private static final int CAPACITY = 2000;

        private static ThreadPoolExecutor threadPool;

        static {
            init();
        }

        private ThreadPool() {
        }

        /**
         * 初始化所有线程池。
         */
        private static void init() {
            threadPool = new ThreadPoolExecutor(CORESIZE, MAXSIZE, KEEPALIVETIME,
                    TimeUnit.SECONDS, new LinkedBlockingQueue<>(CAPACITY));
            log.info("初始化线程池成功。");
        }

        public static Future<?> submit(Runnable task) {
            if (null == task) {
                throw new IllegalArgumentException("任务不能为空");
            }
            return threadPool.submit(task);
        }
    }


}

RedisLockModel

/**
 * @Project: kim-com.kimwu.kill.mall.redis
 * @PackageName: com.kim.com.kimwu.kill.mall.redis.common.lock.model
 * @FileName: KimLockModel.java
 * @Description: The KimLockModel is...
 * @Author: kimwu
 * @Time: 2020-12-19 14:13:32
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RedisLockModel {

    /**
     * 锁的名称
     */
    private String name;

    /**
     * 锁的value
     */
    private String value;

    /**
     * 过期时间(如果不设置默认60s)
     */
    private Integer expireTime;

}

CurentMapUtils

/**
 * @Project: kim-com.kimwu.kill.mall.redis
 * @PackageName: com.kim.com.kimwu.kill.mall.redis.common.lock.utils
 * @FileName: CurentMapUtils.java
 * @Description: The CurentMapUtils is...
 * @Author: kimwu
 * @Time: 2020-12-19 14:31:37
 */
public class CurentMapUtils {


    public static final Map<String, RedisDistributionLockPlus> currentThreadLock = new ConcurrentHashMap<>();

    /**
     * 获取当前锁在map中的key
     *
     * @param joinPoint
     * @param klock
     * @return
     */
    public static String getCurrentLockId() {
        Long curentLock = Thread.currentThread().getId();
        return curentLock.toString();
    }
}

ObjectUtils

/**
 * @Project: kim-com.kimwu.kill.mall.redis
 * @PackageName: com.kim.com.kimwu.kill.mall.redis.common.lock.utils
 * @FileName: ObjectUtils.java
 * @Description: The ObjectUtils is...
 * @Author: kimwu
 * @Time: 2020-12-19 14:23:40
 */
@Slf4j
public class ObjectUtils {


    /**
     * 判断该对象是否: 返回ture表示所有属性为null  返回false表示不是所有属性都是null
     * @param obj
     * @return
     * @throws Exception
     */
    public static boolean isAllFieldNull(Object obj) throws Exception {
        // 得到类对象
        Class stuCla = (Class) obj.getClass();
        //得到属性集合
        Field[] fs = stuCla.getDeclaredFields();
        boolean flag = true;
        //遍历属性
        for (Field f : fs) {
            // 设置属性是可以访问的(私有的也可以)
            f.setAccessible(true);
            // 得到此属性的值
            Object val = f.get(obj);
            //只要有1个属性不为空,那么就不是所有的属性值都为空
            if (val != null) {
                flag = false;
                break;
            }
        }
        return flag;
    }


}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.kimwu.kill.mall.redis.lock.aspect.RedislockAspectHandler
后端领域的分布式锁:Redis 实现方案 分布式锁需满足以下四大核心属性互斥性:同一时间只有一个客户端持有锁;防止死锁:客户端崩溃后,锁需自动释放(如过期时间);高可用性:锁服务需容忍节点故障(如Redis集群);性能:获取/释放锁的延迟低(如Redis的亚毫秒级响应)。可选属性公平性:按请求顺序分配锁(避免饥饿);可重入性:同一客户端可重复获取已持有的锁;容错性:部分节点故障不影响锁服务。适用场景:高并发、弱一致的场景(如电商库存、分布式任务);最佳实践。 阅读详情

相关推荐

Spring Boot开发脚手架,快速搭建项目,集成tk-mybatis、日志记录和统一RESTful API。.zip

Spring Boot开发脚手架,快速搭建项目,集成tk-mybatis、日志记录和统一RESTful API。

redis分布式锁

实现redis分布式锁

qq_41483728的博客 379

DEM数据下载,拼接,裁剪

目录: 1、数字高程模型(DEM)的数据下载 2、在ENVI里面进行拼接,生成SARscape标准格式的DEM数据3、在ArcGIS里面安装已有的矢量范文,进行掩膜裁剪 4、注意事项 数字高程模型的数据下载: 网址:http://dwtkns.com/srtm30m/ 1.对研究区域进行经纬度选择,然后进行选择下载即可 2.下载的数据是.hgt格式的,按照不同的需求进行转换,这里我是用于ENVI处理,且下载了四个,所有进行了拼接,拼接、格式转换如下 注:可能有时候需要登陆注册,点击下...

weixin_40874151的博客 2万+

Redis分布式代理锁的两种实现

基于Redission通过两种方式实现代理分布式锁: 1、ThreadLocal线程缓存 + AOP切面 2、AOP切面 + 入参固定

m0_69270256的博客 2万+

suse ha for sap scale-up性能优化场景安装配置

suse ha for SAP安装配置

jetliu05的博客 2134

基于STM32的智能垃圾分类机器人

垃圾分类是城市管理的重要环节,传统人工分拣效率低、成本高。本文设计了一款基于STM32的智能垃圾分类机器人,通过视觉识别与机械臂协同作业,实现垃圾自动分类与投放,适用于社区、公共场所等场景,可显著提升垃圾分类效率。:STM32H743(480MHz Cortex-M7,支持AI加速)本系统实现了垃圾分拣的自动化,显著提升了分类效率。OV5640摄像头(500万像素,支持JPEG输出)支持4类垃圾识别(可回收/厨余/有害/其他)问题讨论,stm32的资料领取可以私信!超声波避障(探测距离0.1-4m)

stm32d1219的博客 1819

微服务Redis分布式锁配置成注解使用(AOP)

在分布式系统中,因为每个进程或机器都是独立的,它们之间并不能共享同一个锁,因此需要通过一些方法来实现锁的协调。而 Redis 分布式锁就是一种基于 Redis 实现的分布式锁算法,它可以让多个进程或机器安全地共享同一个锁,从而协调它们的执行顺序和时间。它的主要作用是在多个进程或多台机器之间协调执行某个任务,以保证任务的正确性和一致性。

weixin_47390965的博客 1486

FANUC机器人S523 新款PN板卡与西门子PLC通信配置介绍

FANUC机器人S523 新款PN板卡与西门子PLC通信配置介绍

Robot_PLC_HMI_Automation 349

Redis实现分布式锁的7种方案

https://www.cnblogs.com/wangyingshuo/p/14510524.html 七种方案前言 日常开发中,秒杀下单、抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式。如果有不正确的地方,欢迎大家指出哈,一起学习一起进步。 什么是分布式锁 方案一:SETNX + EXPIRE 方案二:SETNX + value值是(系统时间+过期时间) 方案三:使用Lua脚本(包含SET

yygr的博客 2万+

YOLOv13结合Sobel边缘检测算子,实现EIEM(Edge Information Enhancement Module,边缘信息增强模块),强化空间特征提取

EIEM模块通过巧妙地结合Sobel边缘检测算子和现代卷积网络架构,实现了显式边缘信息增强的创新设计。这种方法不仅提高了网络对边缘特征的敏感性,还保持了良好的计算效率和模块化特性。 从技术实现角度看,EIEM展示了如何将经典计算机视觉算法无缝集成到深度学习框架中,为构建更强大、更可解释的视觉模型提供了新的思路。随着对边缘信息重要性认识的不断深入,以及对网络可解释性需求的增长,EIEM这样的显式特征增强模块将在未来的计算机视觉研究中发挥越来越重要的作用。

FJN110的博客 906

2026终极反反爬:Canvas/WebGL/字体指纹全维度伪装,应对浏览器检测实战指南

本文分享了2026年浏览器指纹检测现状及全维度伪装方案。当前反爬系统采用多维度联合指纹检测,重点针对硬件级(WebGL、Canvas)、系统级(字体、CPU)和行为级(鼠标移动)指纹。作者设计了一个独立的指纹伪装引擎,包含真实设备指纹库、指纹生成器和统一注入层,通过JS沙箱隔离实现伪装。核心方案包括:Canvas指纹通过真实设备渲染差异添加微偏移;WebGL指纹完整劫持上下文返回真实显卡参数;字体指纹劫持measureText方法模拟不同系统字体宽度。该方案实测可连续运行一个月零封号,解决了传统方法易被检测

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 923

基于Redis的分布式锁实现方案

一.Redis分布式锁实现分析 存在的问题:   如果某个线程执行的太慢,导致在有效期内还没有执行完,那么因为设置了锁超时自动释放机制,此时锁被自动释放,另一个线程进来拿到锁开始执行代码,就会出现同一时间有两个线程在执行互斥资源代码,可能出现数据不一致。 如何解决: 设置合理的超时时间 + 监控代码执行情况 自动续期,起一个定时任务,周期性扫描超距离时时间还剩多少时仍没有执行完的线程,自动延...

G0_hw 3066

从点击图标到Activity亮屏:AMS、Zygote、ActivityThread的Binder协作之旅

Launcher通过Context调用 startActivity(),最终会通过 Activity.startActivity() 传递到 ActivityManager.getService(),获取AMS的Binder代理对象(IActivityManager)。这里的 attach() 方法通过Binder调用AMS的 attachApplication(),将新进程的 IApplicationThread 代理对象传递给AMS。这一步的Binder通信,是整个启动流程的关键转折点,值得细细品味。

大模型大数据攻城狮的专栏 1070

Redis实现分布式锁有哪些方案?

如果锁过期的时候,并发多个客户端同时请求过来,都执行jedis.get()和set(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。方案四还是可能存在「锁过期释放,业务没执行完」的问题。设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。只要线程一加锁成功,就会启动一个watchdog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。

weixin_66137383的博客 920

9篇1章1节:中国居民健康与营养调查(CHNS)数据库的官网解析和数据下载(2026年版)

在探讨中国社会经济转型与人口健康变迁的学术领域中,中国健康与营养调查(CHNS) 是一个无法绕过的权威数据库。本文将带你深入了解CHNS的项目背景、庞大的数据体系,以及如何通过这两个核心网站获取关键的家庭、个人及社区层面的研究数据。

MD Code 1690

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

redis分布式锁的简单实现方案

package com.mqc.lock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class ...

maqinchaoTnT的博客 312

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Redis实现分布式锁的7种方案,及正确使用方式!

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。互斥性: 任意时刻,只有一个客户端能持有锁。锁超时释放:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。可重入性:一个线程如果获取了锁之后,可以再次对其请求加锁。高性能和高可用:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。安全性:锁只能被持有的客户端删除,不能被其他客户端删除。

qq_40823822的博客 1133

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

【分布式锁的实现方案之Redis】

【分布式锁的实现方案之Redis】分布式锁就是控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

Coder_ljw的博客 284

Redis实现分布式锁的几种方案

1.前言 对于Redis实现分布式锁的几种方案这个话题,展开之前我想先简单聊聊什么是分布式锁,分布式锁的使用场景,除了Redis外还有什么技术实现分布式锁等一系列内容。 1.1分布式锁 说大一点,就是在现在发展越来越迅速的大背景下,去中心化分布式系统越来越普及,在我们实际的生产开发当中,有一种不可避免的场景就是多个进程互斥的对其资源的使用,为了保证数据不重复,要求在同一时刻,同一任务只在一个节点上运行,且保证在多进程下的数据安全,分布式锁就十分重要了。 1.2分布式锁的几种方案 方式有很多种,根据技术角度的

GreatSQL2021的博客 257

Redis分布式锁实现方案

本地锁: 解决同一进程内多个线程操作同一资源的问题。分布式锁: 解决多个进程同时操作同一资源的问题。

bocai_xiaodaidai的博客 622

redis分布式锁的实现问题与解决方案详解

分布式锁是一种在分布式系统中用于控制多个节点对共享资源的并发访问的机制。它可以确保在分布式环境下,多个服务实例或进程在访问关键资源时不会发生竞争。分布式锁的目标是保证每次只有一个节点可以持有锁,并在操作完成后及时释放锁,以便其他节点可以继续操作资源。在分布式环境下,传统的单机锁(如Java中的或Lock)不再适用,因为这些锁无法在多个进程或服务器之间共享。因此,分布式锁的机制通常基于共享的存储系统(如Redis、Zookeeper等)来实现。

qq_38917476的博客 2171

Redis分布式锁实现:原理、实现与坑点,生产级方案详解

Redis分布式锁实现要点 本文系统介绍了Redis分布式锁的实现方案,主要包括以下核心内容: 分布式锁概述 用于控制分布式系统中共享资源访问的同步机制 典型应用场景:库存扣减、唯一性约束、定时任务等 关键特性要求:互斥性、防死锁、高可用和可重入性 Redis实现原理 基于SET命令的原子性操作实现锁获取 通过Lua脚本保证锁释放的原子性 核心要素包括唯一标识、过期时间设置和可重入设计 实现方案 提供简单分布式锁的基础实现代码示例 包含阻塞式锁实现,支持超时等待机制 强调了锁释放的安全性和异常处理的重要性

m0_75126572的博客 2024

基于Redis方案的分布式锁的Java实现

本文介绍了基于Redis实现分布式锁的方案。核心原理是利用Redis的原子命令SET key value NX PX timeout实现互斥锁,并通过Lua脚本保证解锁原子性。Redis分布式锁具有高性能、高可用等优点,但也存在主从复制延迟等问题。文中详细说明了实现要点,包括可重入性、锁续期机制、异常处理等,并提供了Java代码实现示例。适用于高并发场景如秒杀、库存扣减等业务,使用时需注意锁粒度、异常处理和监控告警等关键事项。

一名80后IT人的成长记录,记录自己在工作、学习中的点点滴滴,在成长的喜悦与忧心的故事里,铭记反脆弱的勇气。 1797

5种方案,轻松实现Redis 分布式锁

基于上面本地锁的问题,我们需要一种支持分布式集群环境下的锁:查询 DB 时,只有一个线程能访问,其他线程都需要等待第一个线程释放锁资源后,才能继续执行。生活中的案例:可以把锁看成房门外的一把锁,所有并发线程比作人,他们都想进入房间,房间内只能有一个人进入。当有人进入后,将门反锁,其他人必须等待,直到进去的人出来。我们来看下分布式锁的基本原理,如下图所示:我们来分析下上图的分布式锁:1.前端将 10W 的高并发请求转发给四个题目微服务。2.每个微服务处理 2.5 W 个请求。

IT_1024的博客 303
上一篇: Redis过期键通知(使用redis来实现延迟通知)
下一篇: MySQL(一)锁 ----- 全局锁
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值