天天看點

java批量生成訂單号_Java技術幹貨分享:淺談訂單号生成設計方案

最簡單的方式

基于資料庫 auto_increment_increment 來擷取 ID。首先在資料庫中建立一張 sequence 表,其中 seq_name 用以區分不同業務辨別,進而實作支援多種業務場景下的自增 ID, current_value 為目前值, _increment 為步長,可支援分布式資料庫的哈希政策。

CREATE TABLE `sequence` (

`seq_name` varchar(200) NOT NULL,

`current_value` bigint(20) NOT NULL,

`_increment` int(4) NOT NULL,

PRIMARY KEY (`seq_name`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

通過 SELECT LAST_INSERT_ID() 方法,更新 sequence 表,進行 ID 遞增,并同時擷取上次更新的值。這裡注意, current_value = LAST_INSERT_ID(current_value + _increment) 将更新的 ID 指派給了 LAST_INSERT_ID ,否則傳回的将是行 id。

UPDATE sequence

SET

current_value = LAST_INSERT_ID(current_value + _increment)

WHERE

seq_name = #{seqName}

最後 Dao 提供服務,需要提醒的是注意資料庫的事務隔離級别,如果将 getSeq() 方法放到 Service 中有事務的方法裡,将出現問題,因為資料庫事務開啟會建立一張視圖,在事務沒有送出之前,更新的 ID 還沒有被送出到資料庫中,這在多線程并發操作的情況下,如果事務裡的其他方法導緻性能慢了,可能出現兩個請求擷取到相同的 ID,是以解決方法一是不要将 getSeq() 方法放到有事務的方法裡,另一種就是将 getSeq() 方法的隔離界别為 PROPAGATION_REQUIRES_NEW ,實作開啟新事務,外層事務不會影響内部事務的送出。

@Autowired

private SeqDao seqDao;

@Autowired

private PlatformTransactionManager transactionManager;

@Override

public long getSeq(final String seqName) throws Exception {

TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

// 事務行為,獨立于外部事物獨立運作

transactionTemplate

.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

return (Long) transactionTemplate.execute(new TransactionCallback() {

public Object doInTransaction(TransactionStatus status) {

try {

Seq seq = new Seq();

seq.setSeqName(seqName);

if (seqDao.update(seq) == 0) {

throw new RuntimeException("seq update failure.");

}

return seq.getId();

} catch (Exception e) {

throw new RuntimeException("seq update error.");

}

}

});

}

稍複雜一點的方法

上述的方法的問題,想必大家都知道,就是每次擷取 ID 都要調用資料庫,在高并發的情況下會對資料庫産生極大的壓力,我們的改進方法也很簡單,就是一次申請一個段的 ID,然後發到記憶體裡,每次擷取 ID 先從記憶體裡取,當記憶體中的 ID 段全部被擷取完畢,則再一次調用資料庫重新申請一個新的 ID 段。

java批量生成訂單号_Java技術幹貨分享:淺談訂單号生成設計方案

同樣有資料庫表的設計,通過 Name 區分業務,用 ID 标明已經申請到的最大值。當然如果是分布式架構,也可以通過增加步長屬性來實作。

CREATE TABLE `sequence_value` (

`Name` varbinary(50) DEFAULT NULL,

`ID` int(11) DEFAULT NULL

) ENGINE = InnoDB DEFAULT CHARSET = utf8

Step 是 ID 段的記憶體對象,有兩個屬性,其中 currentValue 目前的使用到的值,endValue 是記憶體申請的最大值。

class Step {

private long currentValue;

private long endValue;

Step(long currentValue, long endValue) {

this.currentValue = currentValue;

this.endValue = endValue;

}

public void setCurrentValue(long currentValue) {

this.currentValue = currentValue;

}

public void setEndValue(long endValue) {

this.endValue = endValue;

}

public long incrementAndGet() {

return ++currentValue;

}

}

代碼的實作稍微複雜一點,擷取 ID 會根據業務辨別 sequencename,先從記憶體擷取 Step 的 ID 段,如果為 null,則從資料庫中讀取目前最新的值,并根據步長計算 Step,然後傳回請求 ID。如果從記憶體中直接擷取到 Step,則直接取 ID,并對 currentValue 進行加一。當 currentValue 的值超過 endValue 時,則更新資料庫的 ID,重新計算 Step。

java批量生成訂單号_Java技術幹貨分享:淺談訂單号生成設計方案

private Map stepMap = new HashMap();

public synchronized long get(String sequenceName) {

Step step = stepMap.get(sequenceName);

if(step ==null) {

step = new Step(startValue,startValue+blockSize);

stepMap.put(sequenceName, step);

} else {

if (step.currentValue < step.endValue) {

return step.incrementAndGet();

}

}

if (getNextBlock(sequenceName,step)) {

return step.incrementAndGet();

}

throw new RuntimeException("No more value.");

}

private boolean getNextBlock(String sequenceName, Step step) {

// "select id from sequence_value where name = ?";

Long value = getPersistenceValue(sequenceName);

if (value == null) {

try {

// insert into sequence_value (id,name) values (?,?)

value = newPersistenceValue(sequenceName);

} catch (Exception e) {

value = getPersistenceValue(sequenceName);

}

}

// update sequence_value set id = ? where name = ? and id = ?

boolean b = saveValue(value,sequenceName) == 1;

if (b) {

step.setCurrentValue(value);

step.setEndValue(value+blockSize);

}

return b;

}

使用該方法擷取 ID 可以減少對資料庫的通路量,以降低資料庫的壓力,但是同樣需要注意,擷取 ID 同樣關注資料庫事務問題,因為當系統重新開機的時候,stepMap 為 null,是以會取資料庫查詢目前 ID,更計算更新 Step,然後更新資料庫的 ID。如果該方法被放到資料庫事務裡,由于其他方法性能慢了,導緻查詢之後沒有及時更新,并發情況下另一個線程查詢的時候,可能會擷取到該線程未送出的 ID,因而出現兩個線程擷取到相同的 ID 問題。

java批量生成訂單号_Java技術幹貨分享:淺談訂單号生成設計方案

本文小結

訂單号生成是一個非常簡單的功能,但是在高并發的場景下,高性能和高可用就成為了需要關注的要點。是以,實際工作中的每一個小細節都值得我們去深思。

繼續閱讀