redis-分布式锁

270 阅读4分钟

背景

redis实现分布式锁,只需要看黄建宏的书就够了。里面讲解了怎么实现,有什么问题,问题的解决方案。

解决方法

set nx命令。

问题

用户校验的问题

就是不能由别人来释放锁,只能由自己来释放锁。即自己的锁,必须只能自己释放。但是redis服务器是没有办法来帮你做到这一点的。怎么办?只能程序员自己解决。怎么解决?校验输入数据和数据库里的数据是否一致。一致,就释放锁,因为一致就是同一个人。不一致,就不是同一个人。一致,就释放锁。不一致,就不释放锁。

代码

主要是释放锁的代码

//伪代码

//1.获取锁
if(set(lock/userid)){
    //2.执行业务
    ...

    //3.释放锁
    //从redis获取userid
    userid = get(lock);

   //校验输入数据和数据库redis里的数据是否一致
   if(输入数据 == userid){ //1.一致 //true,释放锁
      del;
   }else{ //2.不一致 //false,因为不是你持有锁
      return false;
   }
}

上面的代码,解决了校验用户的问题。但是还有一个问题,就是上面有多个命令,get和del命令。假如get命令之后,key到期了(可能因为某些异常原因导致key过期),就自己自动删除了,别的线程就可以获取到锁了。这个时候,锁被别的线程获取了,但是当前线程又把它删除了,即释放了锁。这里有问题,就乱套了。所以怎么解决?事务。

事务

redis本身有事务。

但是光靠事务还不能解决,因为你最终要达到的效果是, 1.数据没变 //提交事务 2.数据变了 //不提交事务

事务只能确保1.开启2.提交。但是不知道数据是否变了。怎么知道数据变了?watch命令。

所以,事务+命令,解决了问题。


watch命令

作用就是和事务命令结合,1.数据没变,就执行事务2.数据变了,就不执行事务。


代码

1.获取锁

2.执行业务

3.释放锁
watch; //监听key

开启事务;
释放锁; //包含多个redis命令1.get(lock)=userid 2.del(lock)
提交事务;

总结一下

事务+watch,本质要做的是,监控key(即lock)是否改变。这是第一个核心问题。即监听的是哪个数据?

其次,事务要保护的是什么东西?只保护释放锁的两个命令。即只确保释放锁的方法是原子性的。所以,其实这里的事务+watch,不能确保获取锁和执行业务是原子的,也没有必要确保。因为本来就是为了解决释放锁的原子问题。

乐观锁

上面的解决方法,实际上是实现了乐观锁。乐观锁到底是指什么?

如果是乐观锁,那么可以读共享数据。如果是悲观锁,不能读共享数据。能不能读是乐观锁的本质,也是和悲观锁的唯一区别。怎么实现乐观锁?就是基于AQS的cas。比较,然后才设置。具体细节是?输入数据和数据库数据比较,如果一样,就设置新的值。完了,就这一句话。这是本质,最核心的一句话。其他的什么加版本号,什么各种解决方法,都是基于不同的工具去实现这句话。

memcache

add命令也和redis的命令一样,可以实现分布式锁。


工作使用-支付

幂等处理,防止订单重复提交。怎么解决?使用add。token/order对象。但是这个只是使用缓存而已,跟分布式锁没有关系。而且,add命令只是一个命令,是线程安全的,单个命令不存在线程安全问题。命令的组合,才会有线程安全问题。

总结

上面的是解决方法。除了redis,还有其他的解决方法,mysql,zk,只要能存储数据,基本上都可以解决分布式锁的问题。

mysql

实现思想是版本号。基于列的唯一性实现。

zk

参考

黄建宏最新出的书

juejin.cn/post/684490…

官方文档 redis.io/topics/dist…