利用Redis对批量数据实现分布式锁

利用Redis对批量数据实现分布式锁

需求背景

在开发的收入结转平台界面上有一个归集按钮,可以实现抓取结转表里面的多条数据进行归集操作。为了防止多人多电脑同时操作一条数据,我们自己开发了一个简单的基于Redis实现的分布式锁。

代码实现

逻辑代码中的使用案例

参数说明:

scIds:结转数据的ID主键集合。

timeOutToDeleteRedisKey:最大锁超时时间(用于自动解锁)

organizationId:租户ID(这个参数根据情况选择是否需要)

ReturnLock returnLock = RedisLock.applyByIds(scIds, redisLockTime, organizationId, () -> {
    // dothing 具体的代码业务逻辑
    return 123;
});

if (!returnLock.getFlag()) {
    if(returnLock.getLock()){
        throw new CommonException("归集数据有程序正在运行,请稍候刷新页面重试");
    }else {
        throw new CommonException(returnLock.getErrorMsg());
    }
}
// 返回对象
System.out.println(returnLock.getResObj()) // 123

Redis加锁方法封装

public static ReturnLock applyByIds(List<Long> scIds, Long timeOutToDeleteRedisKey, Long tenantId,
                                    Supplier<Object> supplier) {
    // 获得锁
    Map<String, String> keyMap = getLockByIds(scIds, timeOutToDeleteRedisKey, tenantId);
    ReturnLock returnLock = new ReturnLock();
    returnLock.setFlag(true);
    returnLock.setLock(false);
    try {
        // 判断主键ID数量和加锁的数量是否一致,不一致说明有加锁失败的数据,返回失败锁信息
        if(scIds.size() > keyMap.size()){
            returnLock.setFlag(false);
            returnLock.setLock(true);
            returnLock.setKeyMap(keyMap);
            return returnLock;
        }
        // 应用代码执行后的返回结果 supplier:java8四大内置函数的供给型接口
        // Supplier<T>(供给型接口)无参数,返回类型为T的对象:T get()
        returnLock.setResObj(supplier.get());
    }catch (Exception e) {
        returnLock.setFlag(false);
        returnLock.setErrorMsg(e.getMessage());
        e.printStackTrace();
    }finally {
        // 应用代码执行报错,解锁
        unLockByIds(keyMap);
    }
    return returnLock;
}

getLockByIds

批量获取每一条数据的Redis锁

private static Map<String, String> getLockByIds(List<Long> scIds, Long timeOutToDeleteRedisKey, Long tenantId) {
    Map<String, String> keyMap = new HashMap<>();
    // 从spring上下文中获取Redis的操作对象,因为这个代码是写在util中,所以通过上下文方式获取bean对象
    // RedisHelper:Redis的操作对象,我们自己公司基于redisTemplate封装的
    RedisHelper redisHelper = SpringUtil.getBean(RedisHelper.class);
    try {
        if (CollectionUtil.isNotEmpty(scIds)) {
            for (int i = 0; i < scIds.size(); i++) {
                String item = "L_" + scIds.get(i);
                String UUID = UUIDUtils.generateTenantUUID(tenantId);
                // 判断key值是否被锁,如果没有锁则加锁并设置过期时间, 如果有锁, 则报错并立即释放已加的锁
                // strSetIfAbsent会返回true/false,底层是封装了java的setIfAbsent方法和Redis的setnx方法
                // 如果设置成功返回true否则false
                if (redisHelper.strSetIfAbsent(item, UUID)) {
                    // 设置过期时间
                    redisHelper.setExpire(item, timeOutToDeleteRedisKey, TimeUnit.SECONDS);
                    // 保存设置的锁信息
                    keyMap.put(item, UUID);
                } else {
                    // 锁设置异常,表示有数据被锁住了,解锁之间加锁的数据
                    if (MapUtil.isNotEmpty(keyMap)) {
                        if (unLockByIds(keyMap)) {
                            // 解锁失败再次解锁
                            unLockByIds(keyMap);
                        }
                    }
                    // 返回锁信息
                    return keyMap;
                }
            }
        }
        return keyMap;
    }catch (Exception e) {
        e.printStackTrace();
        return keyMap;
    }
}

解锁

private static Boolean unLockByIds(Map<String, String> keyMap) {
    RedisHelper redisHelper = SpringUtil.getBean(RedisHelper.class);
    try {
        if (MapUtil.isEmpty(keyMap)) {
            return false;
        }
        // 判断是否是自己的锁
        for (String key : keyMap.keySet()) {
            String uuid = keyMap.get(key);
            if (StringUtils.equals(uuid, redisHelper.strGet(key))) {
                // 封装了redisTemplate.delete(key);
                redisHelper.delKey(key);
            }
        }
        return true;
    }catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}
实现效果

当对数据进行批量加锁的时候,若在加锁的过程中出现加锁失败,则回滚直接加的锁,并提示”归集数据有程序正在运行,请稍候刷新页面重试”。

若业务代码逻辑执行成功或者执行报错都会自动的解锁当前加锁的数据。

hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 利用Redis对批量数据实现分布式锁