分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
我们来假设一个最简单的秒杀场景:数据库里有一张表,column分别是商品ID,和商品ID对应的库存量,秒杀成功就将此商品库存量-1。现在假设有1000个线程来秒杀两件商品,500个线程秒杀第一个商品,500个线程秒杀第二个商品。我们来根据这个简单的业务场景来解释一下分布式锁。
通常具有秒杀场景的业务系统都比较复杂,承载的业务量非常巨大,并发量也很高。这样的系统往往采用分布式的架构来均衡负载。那么这1000个并发就会是从不同的地方过来,商品库存就是共享的资源,也是这1000个并发争抢的资源,这个时候我们需要将并发互斥管理起来。这就是分布式锁的应用。
实现分布式锁的几种方案
1.Redis实现 (推荐)
2.Zookeeper实现
3.数据库实现
Redis实现代码:
/**
* 尝试获取分布式锁
* @param key 锁
* @param requestId 请求标识,释放锁时必须具有相同的requestId,一般使用UUID
* @param expire 超时时间,即最大锁定时间,可以防止加锁者出错导致死锁
* @param unit 超时时间的单位,毫秒:TimeUnit.MILLISECONDS, 秒:TimeUnit.SECONDS
* @return 是否获取成功
* @see https://www.cnblogs.com/linjiqin/p/8003838.html
* @see https://blog.csdn.net/qq_28397259/article/details/80839072
/
public boolean getLock(final String key, final String requestId, final long expire, final TimeUnit unit){
try {
boolean result = redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
/
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
String retStatusCode = commands.set(key, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expire);
*/
Object nativeConnection = connection.getNativeConnection();
String retStatusCode = null;
String expx = TimeUnit.MILLISECONDS==unit ? "PX" : "EX";//EX = seconds; PX = milliseconds
if(nativeConnection instanceof JedisCluster) {// 集群模式
JedisCluster jedisCluster = (JedisCluster) nativeConnection;
retStatusCode = jedisCluster.set(key, requestId, SET_IF_NOT_EXIST, expx, expire);
} else if(nativeConnection instanceof Jedis) {// 单机模式
Jedis jedis = (Jedis) nativeConnection;
retStatusCode = jedis.set(key, requestId, SET_IF_NOT_EXIST, expx, expire);
}
if (LOCK_SUCCESS.equals(retStatusCode)) {
return true;
}
return false;
}
});
return result;
} catch (Exception e){
e.printStackTrace();
}
return false;
}
/**
* 尝试释放分布式锁
* @param key 锁
* @param requestId 请求标识,该值必须与加锁时的requestId相同
* @return 是否释放成功
* @see https://www.cnblogs.com/linjiqin/p/8003838.html
* @see https://blog.csdn.net/qq_28397259/article/details/80839072
*/
public boolean releaseLock(final String key, final String requestId){
try {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
List<String> keys = Collections.singletonList(key);
List<String> args = Collections.singletonList(requestId);
Object nativeConnection = connection.getNativeConnection();
Object retStatusCode = null;
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
if (nativeConnection instanceof JedisCluster) {// 集群模式
JedisCluster jedisCluster = (JedisCluster) nativeConnection;
retStatusCode = jedisCluster.eval(UNLOCK_LUA_SCRIPT, keys, args);
} else if (nativeConnection instanceof Jedis) {// 单机模式
Jedis jedis = (Jedis) nativeConnection;
retStatusCode = jedis.eval(UNLOCK_LUA_SCRIPT, keys, args);
}
if (RELEASE_SUCCESS.equals(retStatusCode)) {
return true;
}
return false;
}
});
return result;
} catch (Exception e){
e.printStackTrace();
}
return false;
}