背景
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
略
参考
黄建宏最新出的书