redis分布式锁

在大型项目中,单机服务往往是不可行的。所以分布式环境的出现,但是分布式环境中如何保持数据的一致性,在这里我们只讨论下redis分布式锁这个解决方案:

使用redis分布式锁需要了解几个命令


redis命令介绍


SETNX命令

当且仅当 key 不存在,将 key 的值设为 value,并返回1 。

若给定的 key 已经存在,则SETNX不做任何动作,并返回0。

GET命令

返回 key 所关联的字符串值。

如果 key 不存在那么返回特殊值 nil 。

假如 key 储存的值不是字符串类型,返回一个错误,因为 GET 只能用于处理字符串值。

DEL命令

删除给定的一个或多个 key 。

不存在的 key 会被忽略。

EXPIRE命令

为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。


redis分布所实现


获得锁

使用SETNX命令可以直接加锁,客户端可以直接使用命令,假如返回值为1,表示客户端获得锁了,可以继续操作,最后使用DEL命令释放锁。
如果返回0,就意味着别的客户端获得锁了。这种想法是美好的,但是有许多不足的地方,接着往下看:
127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 1
127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 0
127.0.0.1:6379> DEL MY.LOCK
(integer) 1


解决死锁

在上面获得锁,处理业务,但是如果业务卡死,客户端没释放锁将需要怎么办?我们可以使用EXPIRE命令来设置锁过期。

假如客户端获得锁时间只有10S,超过这个时间锁就自动释放了,这么一看貌似解决了问题。我们接着往下看:

127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 1
127.0.0.1:6379> SETNX MY.LOCK 123
(integer) 0
127.0.0.1:6379> EXPIRE MY.LOCK 10
(integer) 1

释放锁优化

上面解决了死锁,那么还有什么问题了。现在思考下,客户端获得锁了,10S后锁自动释放,这个时候某一个客户端业务处理超过10S,然后又把锁释放了,注意释放的这个锁并不是自己的锁,释放了别的客户端的锁,这个时候又如何解决了?现在我们在获得锁的时候,把VALUS的值设置为UUID,客户端去释放锁的时候,首先GET锁的值,如果获得了值去跟自己的UUID比对,做相对应的处理。这下看来就完美了。

客户端获得锁等待

如果同一个时间有多个客户端进来,只有一个客户端获得锁,那么其余的客户端要在等待中,一直去尝试获得锁。下面我们结合代码看下:

客户端代码:

@Service
public class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    //请求次数
    private final int waitCount=100;
    /**
     * 获得锁
     * zwz 2016-07-12 17:05:32
     * @param key 
     * @param uuid
     * @return
     */
    public boolean getNxLock(final String key,final String uuid){
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {  
            public Boolean doInRedis(RedisConnection connection)throws DataAccessException {  
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();  
                byte[] keyByte = serializer.serialize(key+".lock");  
                byte[] valueByte = serializer.serialize("locked:"+uuid);  
                boolean isOk = false;
                for(int i=1;i<=waitCount;i++){
                    isOk = connection.setNX(keyByte, valueByte);
                    if(isOk){
                        connection.expire(keyByte, 10);
                        break;
                    }else{
                        try{
                            Thread.sleep(100);//休眠100毫秒
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }
                return isOk;  
            }
        });  
        return result;
    }
    /**
     * 释放锁
     * zwz 2016-07-12 17:06:32
     * @param key
     * @param uuid
     */
    public void releaseNxLock(final String key,final String uuid){
        redisTemplate.execute(new RedisCallback<Boolean>() {
            public Boolean doInRedis(RedisConnection connection)throws DataAccessException {  
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] keyByte = serializer.serialize(key+".lock");
                byte[] bs = connection.get(keyByte);
                if(bs==null){
                    return false;
                }
                if(("locked:"+uuid).equals(new String(bs))){
                    return connection.del(keyByte) == 1;
                }else{
                    return false;
                }
            }  
        });  
    }
}


  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值