分布式锁实现要素
分布式锁实现注意几个要素:
- 加锁过程原子性:加锁时首先判断key是否存在、是否有值,没有值再设置,这3个步骤需要是原子操作;
- 锁正常释放:出现超时、网络等问题时,保证加的锁可以正常释放;
- 锁正确释放:锁A释放时,需要保证只能由加A锁的客户端释放,否则可能就会出现误删锁;
- 锁高可用保证:分布式锁如Redis或ZK出现宕机时,如何保证加锁功能不被影响,需要根据业务考虑到CP、CA抉择选择合适的实现方式;同时可以添加兜底逻辑,如使用CA模型的ZK分布式锁时,当出现分布式不可用时,可以退化成本地锁或MySQL实现的分布式锁等。
Redis分布式锁代码示例
下面代码实现了Redis分布式锁加锁、解锁的过程:
public interface DistributedLock {
Lock obtainLock(String key);
}
public class RedisDistributedLock implements DistributedLock {
private static final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
/**
* Get redis lock script.
* param1 - KEYS[1]: redisKey
* params2 - ARGV[1]: clientId
* params3 - ARGV[2]: expire time
*
*/
private static final String OBTAIN_LOCK_SCRIPT =
"local lockClientId = redis.call('GET', KEYS[1])\n" +
"if lockClientId == ARGV[1] then\n" +
" redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
" return true\n" +
"elseif not lockClientId then\n" +
" redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\n" +
" return true\n" +
"end\n" +
"return false";
private final String clientId = UUID.randomUUID().toString();
private final long expireTime;
private final Map<String, RedisLocalLock> locks = new ConcurrentHashMap<>();
private final StringRedisTemplate redisTemplate;
private final RedisScript<Boolean> redisScript;
private Executor executorService = Executors.newFixedThreadPool(2, new CustomerThreadFactory("Redis-Lock-Executor"));
static class CustomerThreadFactory implements ThreadFactory {
private String name;
public CustomerThreadFactory(String name) {
this.name = name;
}
@Override
public Thread newThread(Runnable r) {
return new Thread();
}
}
/**
* Prefix path of a redis key, usually use project name.
* eg: in a order service(driver-order),
* order pay redis key like this: driver-order:order:pay:13932032323288
* order cancel redis key like this: driver-order:order:cance