分布式锁(redis)的应用的坑和解决办法

分布式锁(redis)的应用的坑和解决办法

使用场景:两台服务器集群部署中台对接工程(数据同步工程),每台项目使用spring的定时任务执行,1分钟执行1次扫描。
,扫描推送数据中间表,进行处理。

问题描述:随机会出现部分数据被扫描两次的现象。

问题背景:设计目标:定时扫描执行的代码增加了分布式锁,获取锁之后,将查询到的数据中间表数据的状态由1改成0,此时标识已经在处理了,另一台服务器就不会再去扫描了。

问题分析:分布式锁不生效导致的两台服务器同时执行同一条数据。
关键点:1.待扫描数据转成处理中的过程,是否是独立的
2.分布式锁是否持续有效

经过分析发现如下的写法:

 if(CacheUtil.getLock(key,second)){
 	 try{
 	 	//扫描待处理数据,并且将数据改成处理中,避免被下个进程扫描到
 	 	//处理数据逻辑
 	 }catch(Throwable e){
                logger.error(e.getMessage(),e);
     }finally{
		CacheUtil.unlock(key);
	}
 }
public static boolean getLock(String key, int expires) {
        try {
            logger.info("获取锁开始:" + key + " timestamp:" + System.currentTimeMillis());
            if (getCache().setnx(key, System.currentTimeMillis())) {
                logger.info("获取锁成功:key ==> " + key + " 值为:" + getCache().get(key));
                return true;
            } else {
                long currentValueL = 0L;

                try {
                    String currentValue = getCache().get(key) == null ? "0" : String.valueOf(getCache().get(key));
                    currentValueL = Long.parseLong(currentValue);
                } catch (Exception var5) {
                    currentValueL = 0L;
                }

                if (currentValueL < System.currentTimeMillis() - (long)expires) {
                    logger.info("锁过期,删除旧锁:key ==> " + key + " timestamp:" + System.currentTimeMillis());
                    unlock(key);//*此处和上面的CacheUtil.unlock(key);逻辑是一样的*
                    return getCache().setnx(key, System.currentTimeMillis());
                } else {
                    logger.info("获取锁结束:" + key + " timestamp:" + System.currentTimeMillis());
                    return false;
                }
            }
        } catch (Exception var6) {
            logger.error("OH,MY GOD! SOME ERRORS OCCURED! AS FOLLOWS :获取锁异常key ==> " + key, var6);
            return false;
        }
    }

 public static void unlock(String key) {
        try {
            cacheManager.unlock(key);
            logger.info("解锁成功  key ==> " + key + " timestamp:" + System.currentTimeMillis());
        } catch (Exception var2) {
            logger.info("解锁失败  key ==> " + key + " 不存在,或已解锁! timestamp:" + System.currentTimeMillis());
        }

    }

  public void unlock(String key) {
        this.del(key);//未做任何处理,直接删除了,所以如果无条件调用它,就会出现暴力删除的动作
    }
    

生产上出现的锁不住场景的流程分析图:
被暴力解锁的大致流程分析图,两边都有unlock
结论:
1.进行定时任务扫描时,最好使用按时间分段扫描的逻辑,起始时间可以使用队列生成,先进先出,保障分布式环境下的取数据唯一性。

2.加锁可以使用数据库枷锁和redis加锁,
若用数据库加锁,可以使用类似select * from xxx where id=xxxx for update来锁住这条数据,那么谁获取到这条数据的锁,那么谁就获得了锁,可以往下走。但是这个的效率很差。

若使用redis加锁,就是用setnx的api进行加锁,即若不存在就设置值,存在就不设置值。谁设置到值,谁就拿到锁。

3.对于解锁的逻辑,不能暴力解锁,必须要判断这个锁是否有效,若无效才可以删除掉,否则就会出现锁失效的情况。

猜你喜欢

转载自blog.csdn.net/weixin_40821669/article/details/91411869