设置锁过期时间,访问数据库后删除锁。涉及的两种问题都是毫秒级别的问题,为了减轻服务器的压力,一般在大厂里要完善的代码。
而小公司里,则不需要这么麻烦,不设计删除锁,直接等待过期时间过去自动删除锁。这样就省去了因为删除锁产生的一系列问题。
分布式锁的两种问题:
第一种问题:锁过期,删除锁,把别人的删了
第1条线程拿锁进去访问数据库,突然该线程发生了意外,等待时间较长,直到过期时间到了(锁释放,第2条线程拿锁进入访问)!突然:第1条线程复活,访问数据库出来,删除锁(此时把第2条线程的锁给删除了)
请设计方案解决这个问题,怎么防止误删别人的锁?
UUID 的概念
UUID(Universally Unique Identifier):通用唯一识别码,是一种软件建构的标准。
UUID 目的是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。
UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。
虽然设置这把锁key是唯一的(sku:skuId:lock),但是可以设置value不唯一:
采用UUID.randomUUID().tostring()生成随机数。
每设置一把锁都是不同的value,根据不同的value识别唯一的锁
set一把锁,再get这一把锁,就可以获得value值(相当于这把锁的密码,用UUID随机生成独一无二的密码)
答:第1条线程如果要删除锁,get一下key的锁(第2条线程设置的锁),虽然它是同一个key,但是value却是唯一的。
第1条线程get到的value值,和开头设置的value值不同,不给删除!!!
这样就实现了避免误删别人的锁了。
String token = UUID.randomUUID().toString();
//每设置一把锁都是不同的value,根据不同的value识别唯一的锁
String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10*1000);
拿锁访问数据库,省略。。。
String lockToken = jedis.get("sku:" + skuId + ":lock");
//get锁的value值,自己设置的则删除。get别人锁的value值,则不删除!
if(StringUtils.isNotBlank(lockToken)&&lockToken.equals(token)){
jedis.del("sku:" + skuId + ":lock");
//访问数据库结束后,删除锁。下一个用户继续拿锁访问!
}
第二种问题:get到key锁相等UUID的value(允许删锁),锁过期,删除锁,把别人的删了。
在一个问题的基础上,使用UUID虽然可以解决问题,但是在删除锁的时候!
假设第1条线程get到value值是自己的(可以删除锁),但正在做判断if的时候,锁刚好过期,锁释放!锁一释放,第2条线程就拿到锁了。
这时第1条线程做完if判断了,进入删除锁,又把第2条线程的锁删了!!!
怎样解决这个问题?
使用lua脚本,保证执行代码段的原子性,get到锁相等的value就删除,就是一个杀手,逮到就删,否则不删。
使用lua脚本替代第一个问题删除锁的三个步骤(get查询,if判断,del删除)
是第一个问题的进阶版本
String token = UUID.randomUUID().toString();
//每设置一把锁都是不同的value,根据不同的value识别唯一的锁
String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10*1000);
拿锁访问数据库,省略。。。
//使用lua脚本删除锁
//KEYS[1]对应第一个singletonList查锁的value,ARGV[1]对应第二个singletonList的token-UUID值。
//如果value==token,则删除!否则不删!
String script ="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//访问数据库结束后,删除锁。下一个用户继续拿锁访问!
jedis.eval(script, Collections.singletonList("sku:" + skuId + ":lock"),Collections.singletonList(token));
这里就不测试了,这里取最优lua脚本删除锁的第二种方法写进代码