分布式锁 分布式锁的实现一般有redis或zookeeper临时顺序节点以及基于数据库行锁来实现
redis分布式锁 利用redis的setnx命令,只有在key不存在的情况下,才能set成功
1 2 3 4 5 6 7 8 9 10 11 12 13 jedis.setnx(key,value) jedis.del(key) jedis.expire(key, 30 ) if (jedis.setnx(lockKey, val) == 1 ) { jedis.expire(lockKey, timeout); }
但是setnx+expire是两个操作,不是原子性的,可能会出现setnx刚执行成功,还没来得及执行expire命令,服务挂掉了,这样就会使得该key没有设置过期时间,该key会一直存在,导致别的服务永远无法获取到锁
1 2 3 4 String set (String key, String value, String nxxx, String expx, long time) ;
RedisTemplate中没有这种方法,不过可以使用execute来调用原生的jedis来操作set命令
1 2 3 4 5 6 7 String result = redisTemplate.execute(new RedisCallback<String>() { @Override public String doInRedis (RedisConnection connection) throws DataAccessException { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.set(key, "锁定的资源" , "NX" , "PX" , expire); } });
但是有可能会出现另一种情况,客户端A获取到锁之后,开始执行业务,此时key过期删除了,客户端B同样获取到锁,之后客户端A执行完之后就会删除锁,此时删除的是客户端B的锁,所以在加锁的时候可以使用随机字符串requestId作为value,删除之前进行验证key对应的value是不是自己的requestId
1 2 3 4 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" ; redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(requestId));
redission框架 redission中已经实现了redis分布式锁
1 2 3 4 5 <dependency > <groupId > org.redisson</groupId > <artifactId > redisson</artifactId > <version > 3.20.0</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 Config config = new Config(); config.useClusterServers() .addNodeAddress("redis://127.0.0.1:6379" ); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("myLock" ); lock.lock(); lock.unlock();
redission中的指令都是使用lua脚本执行的
zookeeper分布式锁 利用zookeeper的顺序临时节点,在zookeeper中创建一个临时顺序节点locks
三个系统A/B/C都去访问locks节点,访问的时候会创建带顺序号的临时/短暂节点,(例如,A创建了id_0000节点,B创建了id_0001节点,C创建了id_0002节点)
拿到/locks节点下的所有子节点(id_0000,id_0001,id_0002),getChildren方法,判断自己创建的是不是最小的节点
如果是,则拿到锁,执行完操作之后,把创建的节点删掉,即为释放锁
如果不是,找到比自己小1的节点,exists方法,并进行监听,发现比自己小1的节点删掉之后,发现自己已经是最小的节点了,拿到锁
如果在操作过程中,应用服务挂掉了,那么由于连接断开,此时的顺序临时节点也会自动删除
zookeeper的分布式锁由于需要创建、销毁临时节点来实现锁功能,所以性能不太高。在并发量很大的情况下不建议使用
Curator框架 Curator实现了zookeeper的分布式锁实现
1 2 3 4 5 <dependency > <groupId > org.apache.curator</groupId > <artifactId > curator-recipes</artifactId > <version > 5.4.0</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000 , 3 ) CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy); client.start(); InterProcessMutex lock = new InterProcessMutex(client, lockPath); if ( lock.acquire(maxWait, waitUnit) ) { try { } finally { lock.release(); } }
基于数据库 mysql数据库的innodb本身是支持行锁的,使用select xxx for update
可以来支持分布式锁
也可以创建一个lock表,来设置锁的名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 加锁 INSERT INTO shedlock(name , lock_until, locked_at, locked_by) VALUES (锁名字, 当前时间+最多锁多久, 当前时间, 主机名) // 续约 UPDATE shedlockSET lock_until = 当前时间+最多锁多久,locked_at = 当前时间, locked_by = 主机名 WHERE name = 锁名字 AND lock_until <= 当前时间 // 释放锁 UPDATE shedlockSET lock_until = lockTime WHERE name = 锁名字
使用mysql不适合于高并发场景