最近在项目中需要编写更新接口库存的方法,由于更新库存会存在竞争关系,高并发情况下会导致出现库存
负数的情况,所有需要进行加锁,但是传统的synchronized关键字无法满足多台服务器之前的锁一致问题。
项目是使用了spring-data-redis做缓存,同时部署了redis集群。所有可以使用分布式锁来解决问题。
通俗的来说分布式锁我的理解是:在redis中存入一个值(key-value),谁拿到这个值谁就可以执行代码。
由于reids中的数据是所有服务器都可以共享的,所有就可以解决锁一致的问题。
实际的设计中思考的问题:
1.获取锁应该是在redis中保存(key-value),释放锁应该是删除(key-value)
2.产品A的库存更新和产品B的库存更新是没影响的,如果只有一个锁,那么肯定会导致性能很差。所有
应该针对每个产品都要一个自己的锁,这样就可以提高效率。锁可以使用产品的Id这些唯一的字段标志。
3.对于一个线程去获取锁,如果锁已经被别人获取了,就采用轮询的方法在指定时间内获取锁。
4.对于一个线程获取了锁,由于其他原因导致锁无法释放,我采用了对锁设置有效时间,
负数的情况,所有需要进行加锁,但是传统的synchronized关键字无法满足多台服务器之前的锁一致问题。
项目是使用了spring-data-redis做缓存,同时部署了redis集群。所有可以使用分布式锁来解决问题。
通俗的来说分布式锁我的理解是:在redis中存入一个值(key-value),谁拿到这个值谁就可以执行代码。
由于reids中的数据是所有服务器都可以共享的,所有就可以解决锁一致的问题。
实际的设计中思考的问题:
1.获取锁应该是在redis中保存(key-value),释放锁应该是删除(key-value)
2.产品A的库存更新和产品B的库存更新是没影响的,如果只有一个锁,那么肯定会导致性能很差。所有
应该针对每个产品都要一个自己的锁,这样就可以提高效率。锁可以使用产品的Id这些唯一的字段标志。
3.对于一个线程去获取锁,如果锁已经被别人获取了,就采用轮询的方法在指定时间内获取锁。
4.对于一个线程获取了锁,由于其他原因导致锁无法释放,我采用了对锁设置有效时间,
这样即使锁无法释放,在有效时间后其他线程也可以继续获取。
下面是工具类,如果你用的是jedis可以参考修改:
public class RedisLock {
private RedisTemplate redisTemplate;
/**
* 重试时间
*/
private static final int DEFAULT_ACQUIRY_RETRY_MILLIS = 100;
/**
* 锁的后缀
*/
private static final String LOCK_SUFFIX = "_redis_lock";
/**
* 锁的key
*/
private String lockKey;
/**
* 锁超时时间,防止线程在入锁以后,防止阻塞后面的线程无法获取锁
*/
private int expireMsecs = 60 * 1000;
/**
* 线程获取锁的等待时间
*/
private int timeoutMsecs = 10 * 1000;
/**
* 是否锁定标志
*/
private volatile boolean locked = false;
/**
* 构造器
* @param redisTemplate
* @param lockKey 锁的key
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + LOCK_SUFFIX;
}
/**
* 构造器
* @param redisTemplate
* @param lockKey 锁的key
* @param timeoutMsecs 获取锁的超时时间
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs) {
this(redisTemplate, lockKey);
this.timeoutMsecs = timeoutMsecs;
}
/**
* 构造器
* @param redisTemplate
* @param lockKey 锁的key
* @param timeoutMsecs 获取锁的超时时间
* @param expireMsecs 锁的有效期
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
this(redisTemplate, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
public String getLockKey() {
return lockKey;
}
/**
* 封装和jedis方法
* @param key
* @return
*/
private String get(final String key) {
Object obj = redisTemplate.opsForValue().get(key);
return obj != null ? obj.toString() : null;
}
/**
* 封装和jedis方法
* @param key
* @param value
* @return
*/
private boolean setNX(final String key, final String value) {
return redisTemplate.opsForValue().setIfAbsent(key,value);
}
/**
* 封装和jedis方法
* @param key
* @param value
* @return
*/
private String getSet(final String key, final String value) {
Object obj = redisTemplate.opsForValue().getAndSet(key,value);
return obj != null ? (String) obj : null;
}
/**
* 获取锁
* @return 获取锁成功返回ture,超时返回false
* @throws InterruptedException
*/
public synchronized boolean lock() throws InterruptedException {
int timeout = timeoutMsecs;
while (timeout >= 0) {
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); //锁到期时间
if (this.setNX(lockKey, expiresStr)) {
locked = true;
return true;
}
//redis里key的时间
String currentValue = this.get(lockKey);
//判断锁是否已经过期,过期则重新设置并获取
if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//设置锁并返回旧值
String oldValue = this.getSet(lockKey, expiresStr);
//比较锁的时间,如果不一致则可能是其他锁已经修改了值并获取
if (oldValue != null && oldValue.equals(currentValue)) {
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RETRY_MILLIS;
//延时
Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
}
return false;
}
/**
* 释放获取到的锁
*/
public synchronized void unlock() {
if (locked) {
redisTemplate.delete(lockKey);
locked = false;
}
}
}
//使用方法,创建RedisLock对象
RedisLock lock = new RedisLock(redisTemplate, "lock_" + product.getId());
try {
if (lock.lock()) {}
} finally {
lock.unlock();
}