基于Redis实现分布式锁
获取锁的Redis命令
set resource_name my_random_value NX PX 30000
resource_name: 资源名称,可根据不同业务区分不同的锁
my_random_value: 随机值,每个线程的随机值都不同,用于释放锁时的校验
NX: key不存在时设置成功,key存在则设置不成功
PX: 自动失效时间,出现异常情况,锁可以过期失效
30000: 有效时间
原理:
利用NX的原子性,多个线程并发时,只有一个线程可以设置成功
设置成功即获得锁,可以执行后续的业务处理
如果出现异常,过了锁的有效期,锁自动释放
释放锁采用Redis的delete命令
释放锁时校验之前设置的随机数,相同才能释放锁
释放锁的LUA脚本:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
案例:
@RestController @Slf4j public class RedisLockController { @Autowired private RedisTemplate redisTemplate; @RequestMapping("/redisLock") public String redisLock(){ log.info("我进入了方法!"); String key = "redisKey" ; String value = UUID.randomUUID().toString(); RedisCallback<Boolean> redisCallback = connection ->{ //设置NX RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent(); //设置过期时间 Expiration expiration = Expiration.seconds(30); //设置序列化key byte[] redisKey = redisTemplate.getKeySerializer().serialize(key); //序列化value byte[] redisValue = redisTemplate.getKeySerializer().serialize(value); //执行setnx操作 Boolean result = connection.set(redisKey, redisValue, expiration, setOption); return result; }; //获取分布式锁 Boolean lock = (Boolean)redisTemplate.execute(redisCallback); if (lock){ log.info("我进入了锁!"); try { Thread.sleep(15000); } catch (InterruptedException e) { e.printStackTrace(); }finally{ String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class); List<String> keys = Arrays.asList(key); Boolean result = (Boolean) redisTemplate.execute(redisScript, keys, value); log.info("释放锁的结果: " + result); } } return "方法执行完成!"; } }
redisson实现:
@RequestMapping("redissonLock") public String redissonLock(){ log.info("我进入了方法!"); Config config = new Config(); /** * <dependency> * <groupId>org.redisson</groupId> * <artifactId>redisson</artifactId> * <version>3.0.1</version> * </dependency> */ //注意redisson版本设置setAddress config.useSingleServer().setAddress("localhost:6379"); RedissonClient redisson = Redisson.create(config); RLock rLock = redisson.getLock("order"); try { rLock.lock(30, TimeUnit.SECONDS); log.info("我获得了锁"); Thread.sleep(10000); }catch (Exception e){ e.printStackTrace(); }finally { log.info("我释放了锁!"); rLock.unlock(); } log.info("方法执行结束!"); return "方法执行完成"; }
Zookeeper:
@Slf4j public class ZkLock implements AutoCloseable, Watcher { private ZooKeeper zooKeeper; private String znode; public ZkLock() throws Exception{ zooKeeper = new ZooKeeper("localhost:2181",10000,this); } public Boolean getLock(String bussinessCode){ try { Stat stat = zooKeeper.exists("/" + bussinessCode, false); if (stat == null){ zooKeeper.create("/" + bussinessCode,bussinessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } //创建瞬时有序节点 znode = zooKeeper.create("/" + bussinessCode + "/" + bussinessCode + "_", bussinessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); //获取业务节点下的所有子节点 List<String> childrenNodes = zooKeeper.getChildren("/" + bussinessCode, false); //子节点排序 Collections.sort(childrenNodes); String firstNode = childrenNodes.get(0); if (znode.endsWith(firstNode)){ return true; } //不是第一个子节点,则监听前一个节点 String lastNode = firstNode; for (String node:childrenNodes){ if (znode.endsWith(node)){ zooKeeper.exists("/" + bussinessCode + "/" + lastNode, true); break; }else { lastNode = node; } } synchronized (this){ wait(); } return true; } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return false; } @Override public void close() throws Exception { zooKeeper.delete(znode,-1); zooKeeper.close(); log.info("我已经释放了锁."); } @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.NodeDeleted){ synchronized (this){ notify(); } } } }