高併發下生成自定義規則的訂單號
目錄
-
背景
半年以前做的一個流程相關的專案,近期在做效能測試;之前的功能測試已經做完了,都沒有什麼問題。
專案採用的springmvc框架,生成訂單號以及儲存訂單號都是在activiti的監聽service中進行的。專案業務資料庫和activiti數
據庫是分離的。程式碼流程為 業務service-->監聽service-->業務service。
-
規則
這裡重點說明一下我們系統的訂單號生成規則:專案名-YYYYMMDD-(從當年1月1日計目前的訂單數量+1)COUNT;即
訂單號的生成由3部分組成(專案名、日期、COUNT),其中專案名固定,日期由系統生成,最後一個COUNT是需要通過讀取
資料庫來進行計算的。
-
問題
在效能測試時發現在多執行緒情況下生成的訂單號是重複的,導致了後臺報錯,只一條建立成功,其他建立都以失敗告終。
-
分析
之前的程式碼如下,沒有考慮到多執行緒高併發情況下的執行緒案例問題。
private String getNum(){
AtomicInteger count = new AtomicInteger(資料庫獲取大於今年的個數);
return String.format("%04d", count.incrementAndGet());
}
-
思路
面對多執行緒的情況,之前想到了2個思路:資料庫和執行緒鎖
資料庫
從資料庫層面解決,直接在資料庫建立一個function用來生成訂單號,在插入資料的時候就呼叫這個function以此來解決高併發的情
況,這種方式如果能實現效果是最好的。
create or replace function fillWords() returns varchar as $fillWords$ declare temp_word varchar; temp_count int4; begin SELECT (COUNT (T .node_id) + 1) into temp_count FROM t_base_order T WHERE T .start_time >= date_trunc('year', now()); temp_word := temp_count || ''; if(length(temp_word) < 4) then temp_word := lpad(temp_word, 4, '0'); end if; return 'RMS-' || to_char(now(), 'yyyyMMdd') || temp_word; end; $fillWords$ language plpgsql;
使用這種方式試了一下,還是會生成重複的原因。後來一分析發發現,單條插入資料時的事務並沒有commit,因此在高併發情況下
查詢的COUNT還是一樣的(資料庫預設的隔離級別為Read committed);一想那我就修改隔離等級吧,一查,我勒個去,postgresql直接
不支援Read uncommitted,那個傷心......
那就來做提交事務吧,專案採用的是宣告式事務,如果要單個提交事務就需要程式設計式事務,修改了一下午,是各種報錯,也可能是
我自己沒有修改對吧。總之這種手動事務的方式最終是沒有成功。
執行緒鎖
聽效能測試的同事說,執行緒鎖這種形式非常損耗效能,但沒辦法了,資料庫的方案形不通,那就只能這種方式了唄。
1、最開始的想法是使用LOCK直接把生成訂單號和插入資料的程式碼塊直接鎖起來,但不知道為什麼還是有重複的出現;後來一想,
也是事務沒有提交導致的。
2、聽另外一個同事說,加到程式碼塊上不行,應該把鎖加到方法上,於是一試,還是重複,崩潰呀。
想了2天也不知道使用什麼方法,網上百度的都是使用隨機數來進行處理,但與我們專案的規則不符合呀。
最終突然想到一個辦法,就是利用JAVA的AtomicInteger來實現,因此這個類是執行緒安全的。思路如下:
第一次的時候,直接把查詢的個數載入到static變數中,然後利用這個變數的自增來給COUNT設值,感覺這個方案不錯,一試還真
行,效率也不差。具體實現,請看最後一節。
-
方案
直接貼程式碼:
private static AtomicInteger count = new AtomicInteger(0);
private String getNum(){
if(count.get() == 0){
LOG.error("+++++++++"+count.get());
synchronized(count){
LOG.error("----------"+count.get());
//下面這個再次判斷很重要,防止了多次設值
if(count.get() == 0){
count.set(資料庫獲取大於今年的個數);
LOG.error("**********"+count.get());
}
}
}
return String.format("%04d", count.incrementAndGet());
}
這種實現方案,只是第一次併發的情況下加鎖損耗了效能,第二次併發的情況下就不會有鎖的影響了;並且由於鎖之中的再次判
斷,既防止了多次設值,也提交了效率,這個設計不錯(自我感覺哈^_^)。
-
討論
最後的這個方案其實也有問題,就是如果專案要做負載均衡的情況下,也會出現重複單號的情況,因此如果專案要做分散式部署的
情況下,可以將這段程式碼釋出成一個公用服務,所有的分散式部署都呼叫這個公用服務,這樣就不會有問題了。但我們專案不會做分散式
部署,因此就不考慮公用服務這種方案了。
各位有什麼好的想法,也希望大家一起交流哈。