一、一些業務背景下,業務要求單號需要按照不同的業務進行生成不同前綴單號。那麼在分佈式的架構下如何自定義單號而且還能保證唯一呢?
二、當我們在設計分佈式鎖的時候,我們應該考慮分佈式鎖至少要滿足的一些條件,同時考慮如何高效的設計分佈式鎖,這裏我認爲以下幾點是必須要考慮的。
1、互斥
在分佈式高併發的條件下,我們最需要保證,同一時刻只能有一個線程獲得鎖,這是最基本的一點。
2、防止死鎖
在分佈式高併發的條件下,比如有個線程獲得鎖的同時,還沒有來得及去釋放鎖,就因爲系統故障或者其它原因使它無法執行釋放鎖的命令,導致其它線程都無法獲得鎖,造成死鎖。
所以分佈式非常有必要設置鎖的有效時間
,確保系統出現故障後,在一定時間內能夠主動去釋放鎖,避免造成死鎖的情況。
3、性能
對於訪問量大的共享資源,需要考慮減少鎖等待的時間,避免導致大量線程阻塞。
所以在鎖的設計時,需要考慮兩點。
1、鎖的顆粒度要儘量小
。比如你要通過鎖來減庫存,那這個鎖的名稱你可以設置成是商品的ID,而不是任取名稱。這樣這個鎖只對當前商品有效,鎖的顆粒度小。
2、鎖的範圍儘量要小
。比如只要鎖2行代碼就可以解決問題的,那就不要去鎖10行代碼了。
4、重入
我們知道ReentrantLock是可重入鎖,那它的特點就是:同一個線程可以重複拿到同一個資源的鎖。重入鎖非常有利於資源的高效利用。關於這點之後會做演示。
針對以上Redisson都能很好的滿足。下面就代碼來實現。
三、我這邊業務是按照 字母+數字 比如W0000001 按照這個順序自增。
代碼實例
1、maven座標
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.1</version>
</dependency>
2、、創建FormNoTypeEnum 枚舉類,業務不同前綴枚舉類,用戶編號和供應商編號,可以自行擴展。以用戶舉例
/**
* 生成規則 = 1位字母+7位數字流水碼,1開始
*/
public enum FormNoTypeEnum {
/**
* 用戶單號:
* 固定前綴:S
*/
USER_ORDER("S" ),
/**
* 供應商單號:
* 固定前綴:P
*/
SUPPLIER_ORDER("P"),
;
/**
* 單號前綴
* 爲空時填""
*/
private String prefix;
FormNoTypeEnum(String prefix ) {
this.prefix = prefix;
}
public String getPrefix() {
return prefix;
}
}
3、在application.yml 配置:
system:
user:
key: CURRENT_MAX_USER_CODE_KEY # 用戶編號鎖存在redis中 key
lock: USER_CODE_INC_LOCK # 獲取用戶編號鎖
supplier:
key: CURRENT_MAX_SUPPLIER_CODE_KEY # 供應商編碼存在redis中 key
lock: SUPPLIER_CODE_INC_LOCK # 獲取供應商編號鎖
4、在service層注入:
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisCache redisCache;
// 用戶編號存在redis中key
@Value("${system.user.key}")
private String key;
// 用戶編號鎖
@Value("${system.user.lock}")
private String userLock;
@Transactional(propagation = Propagation.REQUIRED)
public void addUserBO ( UserBO userBO,String supplierId){
BUser buser = new BUser();
BeanUtils.copyProperties(userBO,buser);
buser.setsNumber(supplierId);
buser.setCreateTime(new Date());
buser.setUserId(sid.nextShort());
String newMaxValue = null;
String PRE_USER_CODE = FormNoTypeEnum.USER_ORDER.getPrefix(); //生成編號前綴
RLock lock = redissonClient.getLock(userLock);
try {
lock.lock(10, TimeUnit.SECONDS);
String MaxValue = redisCache.getCacheObject(key); //獲取當前最大編碼值
if(StringUtils.isNull(MaxValue)){
//從數據庫獲取
MaxValue = bUserMapper.getUserNumberMax();
if(StringUtils.isNull(MaxValue)){ //沒有查詢到則初始化編碼
MaxValue = PRE_USER_CODE + "0000000";
}
}
int currentMaxNum = Integer.parseInt(MaxValue.substring(MaxValue.indexOf(PRE_USER_CODE)+1));
currentMaxNum = currentMaxNum + 1;
newMaxValue = PRE_USER_CODE + String.format("%07d", currentMaxNum);
//4、將新的最大值同步到redis緩存
redisCache.setCacheObject(key, newMaxValue, 30, TimeUnit.MINUTES); //30分鐘
buser.setUserNumber(newMaxValue);
bUserMapper.insertBUser(buser);
}catch (Exception e){
e.printStackTrace();
throw new CustomException("獲取redis分佈式鎖異常,請聯繫系統管理員");
}finally {
log.info("生成用戶編號,釋放redis分佈式鎖");
lock.unlock(); //釋放鎖
}
}
redisCache類;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* spring redis 工具類
**/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 緩存基本的對象,Integer、String、實體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
* @return 緩存的對象
*/
public <T> ValueOperations<String, T> setCacheObject(String key, T value)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
operation.set(key, value);
return operation;
}
/**
* 緩存基本的對象,Integer、String、實體類等
* @param key 緩存的鍵值
* @param value 緩存的值
* @param timeout 時間
* @return 緩存的對象
*/
public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
operation.set(key, value, timeout, TimeUnit.SECONDS);
return operation;
}
/**
* 緩存基本的對象,Integer、String、實體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
* @param timeout 時間
* @param timeUnit 時間顆粒度
* @return 緩存的對象
*/
public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
operation.set(key, value, timeout, timeUnit);
return operation;
}
/**
* 獲得緩存的基本對象。
*
* @param key 緩存鍵值
* @return 緩存鍵值對應的數據
*/
public <T> T getCacheObject(String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 刪除單個對象
*
* @param key
*/
public void deleteObject(String key)
{
redisTemplate.delete(key);
}
/**
* 刪除集合對象
*
* @param collection
*/
public void deleteObject(Collection collection)
{
redisTemplate.delete(collection);
}
/**
* 緩存List數據
*
* @param key 緩存的鍵值
* @param dataList 待緩存的List數據
* @return 緩存的對象
*/
public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList)
{
ListOperations listOperation = redisTemplate.opsForList();
if (null != dataList)
{
int size = dataList.size();
for (int i = 0; i < size; i++)
{
listOperation.leftPush(key, dataList.get(i));
}
}
return listOperation;
}
/**
* 獲得緩存的list對象
*
* @param key 緩存的鍵值
* @return 緩存鍵值對應的數據
*/
public <T> List<T> getCacheList(String key)
{
List<T> dataList = new ArrayList<T>();
ListOperations<String, T> listOperation = redisTemplate.opsForList();
Long size = listOperation.size(key);
for (int i = 0; i < size; i++)
{
dataList.add(listOperation.index(key, i));
}
return dataList;
}
/**
* 緩存Set
*
* @param key 緩存鍵值
* @param dataSet 緩存的數據
* @return 緩存數據的對象
*/
public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 獲得緩存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(String key)
{
Set<T> dataSet = new HashSet<T>();
BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key);
dataSet = operation.members();
return dataSet;
}
/**
* 緩存Map
*
* @param key
* @param dataMap
* @return
*/
public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap)
{
HashOperations hashOperations = redisTemplate.opsForHash();
if (null != dataMap)
{
for (Map.Entry<String, T> entry : dataMap.entrySet())
{
hashOperations.put(key, entry.getKey(), entry.getValue());
}
}
return hashOperations;
}
/**
* 獲得緩存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(String key)
{
Map<String, T> map = redisTemplate.opsForHash().entries(key);
return map;
}
/**
* 獲得緩存的基本對象列表
*
* @param pattern 字符串前綴
* @return 對象列表
*/
public Collection<String> keys(String pattern)
{
return redisTemplate.keys(pattern);
}
}
參考:https://www.cnblogs.com/qdhxhz/p/11046905.html