基於Redis的redisson框架實現分佈式單號,按照有序生成分佈式ID(自定義規則生成)

一、一些業務背景下,業務要求單號需要按照不同的業務進行生成不同前綴單號。那麼在分佈式的架構下如何自定義單號而且還能保證唯一呢?

二、當我們在設計分佈式鎖的時候,我們應該考慮分佈式鎖至少要滿足的一些條件,同時考慮如何高效的設計分佈式鎖,這裏我認爲以下幾點是必須要考慮的。

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

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章