1. 程式人生 > >高併發下生成自定義規則的訂單號

高併發下生成自定義規則的訂單號

目錄

背景

規則

問題

分析

思路

資料庫

執行緒鎖

方案

討論

  • 背景

半年以前做的一個流程相關的專案,近期在做效能測試;之前的功能測試已經做完了,都沒有什麼問題。   

專案採用的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());
}

這種實現方案,只是第一次併發的情況下加鎖損耗了效能,第二次併發的情況下就不會有鎖的影響了;並且由於鎖之中的再次判

斷,既防止了多次設值,也提交了效率,這個設計不錯(自我感覺哈^_^)。

  • 討論

最後的這個方案其實也有問題,就是如果專案要做負載均衡的情況下,也會出現重複單號的情況,因此如果專案要做分散式部署的

情況下,可以將這段程式碼釋出成一個公用服務,所有的分散式部署都呼叫這個公用服務,這樣就不會有問題了。但我們專案不會做分散式

部署,因此就不考慮公用服務這種方案了。

各位有什麼好的想法,也希望大家一起交流哈。