注:部分内容来源于广大网友。
1.加锁
jedis.set(key, value, "NX","PX",10000);
这个操作是原子性的,同一时刻只能有一个线程操作成功。
key: 分布式下所有机器使用相同的key;
value:有讲究,下面说;
10000:为什么设置过期时间,下面说;
2. 解锁
jedis.del(key);
现在考虑下可用性:假如现在加锁的这台机器挂掉了,那么锁就永远无法释放。假如是以某件商品id为key,那么这件商品就永远没有用武之地,如果是秒杀,所有人都抢不到。因此,需要加过期时间。
加了过期时间又会涉及到另一个问题:现在假设过期时间设置30s, 有个线程处理了35s, 那么就会出现有两个线程同时进入业务逻辑,会产生数据一致性问题。根本原因是两个线程同时操作数据库,想进行库存减1操作,那保证只有一个可以提交就可以了。
事务 + lua脚本保证数据一致性
删除key的时候,只有删除成功的才可以提交事务,否则可以返回个错误页面;
如果只是使用 jedis.del(key);那么所有线程都可以提交成功了。
线程A过期了,线程B拿到了锁,此时redis中的值是线程B设置的;我们就是要根据不同线程的value来区分,用ThreadLocal存放当前线程设置的value;此时线程A判断自己设置的值redis中已经没有了,那就不能提交;
先判断再删除,两个操作的原子性使用lua脚本,根据返回值判断是否删除成功;成功的话,提交事务。
String script =
"if redis.call('get',KEYS[1]) == ARGV[1] then" +
" return redis.call('del',KEYS[1]) " +
"else" +
" return 0 " +
"end";
Object result = jedis.eval(script, Collections.singletonList(key),
Collections.singletonList(value));
总结:个人认为,如果保证过期时间内能处理完业务,那就不用涉及value、事务、lua了,直接jedis.del(key)就行了。