批量分布式锁 Redis 批量命令执行 lua&pipeline

场景:分布式锁是使用非常频繁的,批量跑逻辑比如导入、多选提交业务,也会用到,但是由于单个锁频繁网络请求非常耗时,于是就有了批量锁;

批量上锁要原子性用了lua脚本实现,批量解锁用的pipeline,废话不说上代码

public class RedisBatchLock {
    static final Integer MAX_SIZE = 100;

    /**
     * 获取lua脚本
     *
     * @return
     */
    static final String getScriptLua() {
        StringBuffer str = new StringBuffer();
        str.append(" local seccess = {}; ");
        str.append(" for k, v in pairs(KEYS) do ");
        str.append(" local value = ARGV[k] ; ");
        str.append(" local ok = redis.call('setnx', v, value); ");
        str.append("   if ok == 1 then ");
        str.append("   redis.call('expire', v, ARGV[#ARGV]); ");
        str.append("   seccess[k]=v; ");
        str.append("   else ");
        str.append("       if #seccess>0 then ");
        str.append("           for x, y in pairs(seccess) do ");
        str.append("           redis.call('del', y); ");
        str.append("           end ");
        str.append("       end ");
        str.append("   return false; ");
        str.append("   end ");
        str.append(" end ");
        str.append(" return true; ");
        return str.toString();
    }

    /**
     * 批量上锁
     *
     * @param keys
     * @param timeOut 单位毫秒
     * @return
     */
    public static Boolean batchLock(List<String> keys, Long timeOut) {
        if (CollectionUtil.isEmpty(keys) ||
                timeOut < 1000L ||
                keys.size() > MAX_SIZE) {
            return false;
        }
        List values = new ArrayList();
        List<String> finalKeys = new ArrayList();
        for (String key : keys) {
            LockInfo lockInfo = new LockInfo(key, Thread.currentThread().getName(), Thread.currentThread().getId());
            lockInfo.setStatus(1);
            finalKeys.add(RedisLockKeyGenerator.generateRedisLockKey(key));
            values.add(JSON.toJSONString(lockInfo));
        }
        values.add(timeOut / 1000 + "");
        RedisTemplate<String, String> redisMasterTemplate = RedisHelper.getRedisTemplate();
        RedisScript<Boolean> redisScript = new DefaultRedisScript(getScriptLua(), Boolean.class);
        Boolean flag = redisMasterTemplate.execute(redisScript, finalKeys, values.toArray(new String[0]));
        return flag;
    }

    /**
     * 批量解锁
     *
     * @param keys
     * @return
     */
    public static boolean batchUnLock(List<String> keys) {
        if (CollectionUtil.isEmpty(keys)) {
            return false;
        }
        RedisTemplate<String, String> redisTemplate = RedisHelper.getRedisTemplate();
        List<Object> list = redisTemplate.executePipelined(new SessionCallback<Boolean>() {
            @Override
            public Boolean execute(RedisOperations operations) throws DataAccessException {
                for (String key : keys) {
                    key = RedisLockKeyGenerator.generateRedisLockKey(key);
                    operations.delete(key);
                }
                return null;
            }
        });
        return true;
    }

}

Redis与spring集成就不多说了,主要逻辑在此,个别封装代码可以忽略,稍微动一下就可以运行啦;

经测试,批量处理10把锁性能提示4倍。

lua脚本和java调用注意返回值类型要一致(这里是Boolean),参数集合类型要一致(这里是String),注意这些坑即可。

另外,这里未支持重入锁的场景,批量处理一般也不会有这种场景;解锁处理要小心,没有做约束本线程可解锁,这需要改进。

是的,Redis可以用来实现分布式锁,实现方式有多种,其中常见的两种分别是基于SETNX和基于Lua脚本的实现。 1. 基于SETNX实现分布式锁 SETNX命令可以将一个key设置为一个value,但仅在key不存在时才能设置成功,如果key已经存在,则设置失败。利用这个特性,可以通过以下步骤来实现分布式锁: - 客户端使用SETNX命令尝试获取锁,如果返回1,则表示获取锁成功;如果返回0,则表示获取锁失败,需要重新尝试获取; - 获取锁成功后,设置锁的过期时间,防止锁永远不释放; - 执行业务代码; - 释放锁,即删除锁对应的key。 代码示例: ```python def acquire_lock(conn, lockname, acquire_timeout=10, lock_timeout=10): identifier = str(uuid.uuid4()) lockname = 'lock:' + lockname end = time.time() + acquire_timeout while time.time() < end: if conn.setnx(lockname, identifier): conn.expire(lockname, lock_timeout) return identifier elif not conn.ttl(lockname): conn.expire(lockname, lock_timeout) time.sleep(0.001) return False def release_lock(conn, lockname, identifier): lockname = 'lock:' + lockname with conn.pipeline() as pipe: while True: try: pipe.watch(lockname) if pipe.get(lockname) == identifier: pipe.multi() pipe.delete(lockname) pipe.execute() return True pipe.unwatch() break except redis.exceptions.WatchError: pass return False ``` 2. 基于Lua脚本实现分布式锁 基于Lua脚本的实现方式可以通过一次Redis操作完成获取锁和设置锁过期时间两个操作,避免了基于SETNX方式需要两次Redis操作的问题,具有更好的性能和安全性。 代码示例: ```lua local lockname = KEYS[1] local identifier = ARGV[1] local lock_timeout = ARGV[2] if redis.call('setnx', lockname, identifier) == 1 then redis.call('expire', lockname, lock_timeout) return identifier elseif redis.call('ttl', lockname) == -1 then redis.call('expire', lockname, lock_timeout) end return false ``` 具体实现时,客户端需要将上述Lua脚本传递给Redis服务器执行,获取返回结果即可。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值