問題:多個線程同時進行購買商品操作,減庫存,增加記錄數據操作會出現庫存爲-1的情況。
1.使用同步鎖(synchronized 或 Lock),在單線程同步進行情況下不會出現庫存-1的情況
2.分佈式多個節點多線程同步進行請求會出現同步鎖無法解決的問題,依舊出現多減庫存的情況。
解決思路(一):redis分佈式事物鎖
思路:主要用到的redis函數是setnx(),這個應該是實現分佈式鎖最主要的函數。首先是將某一任務標識名(這裏用Lock:order作爲標識名的例子)作爲鍵存到redis裏,併爲其設個過期時間,如果是還有Lock:order請求過來,先是通過setnx()看看是否能將Lock:order插入到redis裏,可以的話就返回true,不可以就返回false
代碼:
package com.swxc.core.lock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException;
import java.util.List;
import java.util.UUID;
/**
* @作者:huangliang
* @時間:2019-9-27 19:25
* @註釋:DistributedLock
*/
@Configuration
public class DistributedLock {
private static JedisPool jedisPool;
@Bean
public DistributedLock jpushServiceForStaff() {
JedisPoolConfig config = new JedisPoolConfig();
// 設置最大連接數
config.setMaxTotal(200);
// 設置最大空閒數
config.setMaxIdle(8);
// 設置最大等待時間
config.setMaxWaitMillis(1000 * 100);
// 在borrow一個jedis實例時,是否需要驗證,若爲true,則所有jedis實例均是可用的
config.setTestOnBorrow(true);
jedisPool = new JedisPool(config, "127.0.0.1", 6789, 3000,",./6789");
return new DistributedLock(jedisPool);
}
public DistributedLock() {
}
public DistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 加鎖
* @param locaName 鎖的key
* @param acquireTimeout 獲取超時時間
* @param timeout 鎖的超時時間
* @return 鎖標識
*/
public String lockWithTimeout(String locaName,
long acquireTimeout, long timeout) {
Jedis conn = null;
String retIdentifier = null;
try {
// 獲取連接
conn = jedisPool.getResource();
// 隨機生成一個value
String identifier = UUID.randomUUID().toString();
// 鎖名,即key值
String lockKey = "lock:" + locaName;
// 超時時間,上鎖後超過此時間則自動釋放鎖
int lockExpire = (int)(timeout / 1000);
// 獲取鎖的超時時間,超過這個時間則放棄獲取鎖
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (conn.setnx(lockKey, identifier) == 1) {
conn.expire(lockKey, lockExpire);
// 返回value值,用於釋放鎖時間確認
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key沒有設置超時時間,爲key設置一個超時時間
if (conn.ttl(lockKey) == -1) {
conn.expire(lockKey, lockExpire);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retIdentifier;
}
/**
* 釋放鎖
* @param lockName 鎖的key
* @param identifier 釋放鎖的標識
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
Jedis conn = null;
String lockKey = "lock:" + lockName;
boolean retFlag = false;
try {
conn = jedisPool.getResource();
while (true) {
// 監視lock,準備開始事務
conn.watch(lockKey);
// 通過前面返回的value值判斷是不是該鎖,若是該鎖,則刪除,釋放鎖
if (identifier.equals(conn.get(lockKey))) {
Transaction transaction = conn.multi();
transaction.del(lockKey);
List<Object> results = transaction.exec();
if (results == null) {
continue;
}
retFlag = true;
}
conn.unwatch();
break;
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retFlag;
}
}
邏輯service調用:
@Resource
private DistributedLock distributedLock;
@Override
public Map test() {
String indentifier = distributedLock.lockWithTimeout("orderLock", 5000, 10000);
System.out.println("獲得了鎖");
System.out.println("業務操作");
distributedLock.releaseLock("resource", indentifier);
return null;
}