分布式锁

分布式锁的使用场景是什么呢?

之前红包需求的时候,有一个场景要用到分布式锁。同一个openid,最多只能抢到一个子红包。

1、先判断这个openid在不在该红包对应的已抢openid hash中

String hget(String key, String field);

2、如果在,就返回。如果不在,则加分布式锁,保证同一个openid,最多只有一个请求能抢到红包。这里是怕有人用自己的openid刷接口,从而导致他自己抢了很多个红包。

SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]

只有这个命令,能一边设不存在时才set,一边设置过期时间。

SETNX key value,只能在不存在时才set,不能设置过期时间。

key是什么?key是openid+redPacketId。

value是什么?value是一个uuid即可,后面还要依靠这个uuid去解锁呢。对的,分布式锁加上之后,需要我们在业务完成之后,手动解锁。即del key,但是不能乱删,不能是没有获取到锁的客户端把锁解了,只能是获取到锁的客户端才能解锁。这就是uuid的好处,每个请求生成的uuid是不一样的,只有uuid是key的值时,才能解锁。

伪代码如下:

    public Object getChildRedPacket() {
        JedisCommands jedisCommands = null;
        Jedis jedis = null;
        JedisCluster jedisCluster = null;

        String openid = "d";
        String redPacketId = "ss";
        String lockKey = openid + redPacketId;
        SetParams setParams = new SetParams();
        setParams.nx();
        setParams.ex(60 * 60);

        String uuid = UUID.randomUUID().toString();
        String money = jedisCommands.hget(redPacketId, openid);
        if (StringUtils.isNotBlank(money)) {
            return ImmutableMap.of("money", money);
        } else {
            try {
                // 获得锁
                if ("ok".equalsIgnoreCase(jedisCommands.set(lockKey, uuid, setParams))) {
                    money = jedisCommands.lpop(redPacketId);
                    if (StringUtils.isNotBlank(money)) {
                        jedisCommands.hset(redPacketId, openid, money);
                        return ImmutableMap.of("money", money);
                    } else {
                        return ImmutableMap.of("msg", "已抢完");
                    }
                }
            } finally {
                // 释放锁
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return end";
//                jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(uuid));
                jedisCluster.eval(script, Collections.singletonList(lockKey), Collections.singletonList(uuid));
            }
        }
        return null;
    }

Jedis常规的set、get、hset、hget方法都是从JedisCommands接口继承的,JedisCluster常规的set、get、hset、hget方法都是从JedisClusterCommands接口继承的。

Jedis的eval方法是从ScriptingCommands接口继承的,JedisCluster的eval方法是从JedisClusterScriptingCommands接口继承的。

注意以上代码,在lua脚本中用了uuid的值,这就是约束了只有获得锁的请求能解锁,没有获得锁的请求不能解锁,且释放锁代码放在finally块中。

加锁时的过期时间怎么定呢?只要是比业务代码执行所需时间长就好了,长点无所谓。为啥无所谓呢?因为锁是一个openid对应一个redPacketId,锁不释放不会影响另一个openid抢这个红包,也不会影响这个openid抢另一个红包,只会影响这个openid再一次抢这个红包。参考https://mp.weixin.qq.com/s/ceRwZdwOgmzRGqF9HZztKg

posted on 2019-06-18 23:14  koushr  阅读(664)  评论(0编辑  收藏  举报

导航