成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

高可用高性能可擴展的單號生成方案

開發 開發工具
在業務開發中經常會遇到各種單號生成,另外有多少業務量就會至少有多少的單號生成需求,所以單號生成必須高可用,必須高性能。 另外業務不同需要的單號規則可能也不相同, 所以單號服務還必須具備足夠的擴展性。

在業務開發中經常會遇到各種單號生成, 例如快遞單號、服務單號、訂單號等等。 這些單號生成往往是業務邏輯處理的第一步, 單號生成出問題,必然導致業務走不下去;另外有多少業務量就會至少有多少的單號生成需求。所以單號生成必須高可用,必須高性能。 另外業務不同需要的單號規則可能也不相同, 所以單號服務還必須具備足夠的擴展性。

一、單號定義

在進入正題之前我們先給單號下個定義, 看幾個常見的單號形式。

單號是一個數字和字符組成的序列, 它要滿足兩個條件: 一個是唯一, 保證唯一才可以作為業務標識; 另一個是符合業務需要的規則。 例如下面三個單號:

  • 2017030400001 這個單號由兩個部分序列號日期20170304+定長5位補0數字00001。
  • 010-6541-00001 此單號分三部分, 中間用減號連接, 第一部分為區號, 第二部分為作業單位號碼, 第三部分為作業單位產生作業的序號。
  • QJ000001 則是由字符QJ開頭后面跟隨數字序列的單號。

二、單號數字序列部分的生成

上述單號定義中的數字部分通常是一個自增的數字序列。 我們可以通過數據庫的自增列、 數據庫的列+1方式、 redis或者memcached的INCR指令來生成這種數字的序列。 這四種方式都可以生成序列, 但各自有各自的好處。

1. 數據庫自增列的方式

是通過數據庫的內部機制生成的, 在普通PC上每秒約可以生成4000個數字序列, 它的好處是每一個數字序列都會保留一條記錄, 記錄生成使用時間, 缺點是吞吐量一般, 會占用一定的數據庫資源, 如下是一種推薦的表結構:

  1. CREATE TABLE `xx_code_sequence` ( 
  2.    `id` bigint(20) NOT NULL AUTO_INCREMENT, 
  3.    `generate_time` timestamp NOT NULL 
  4.       default CURRENT_TIMESTAMP, 
  5.    PRIMARY KEY (`id`) 
  6. ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULTCHARSET=utf8

此表有兩列, id列為bigint類型的自增長字段,作為數字序列的值, generate_time時間戳字段可以記錄每一個單號的生成時間。生成數字序列的方式用sql說明如下:

  1. begin trans; 
  2. insert into `xx_code_sequence`(generate_time)values(current_timestamp); 
  3. select last_insert_id(); 
  4. commit; 

說明:

  • 表名格式xx_code_sequence,sto_code_0分為三部分sto為ownerKey, code固定不變,0表示表的序號,可以有多個下標不同表來支持更高的并發,共有幾個表需要在開始確認了,確認的依據是需要滿足的并發請求。表的個數必須是2的n次方,例如1, 2, 4, 8,16;
  • `id` 即序列的部分值,是通過mysql的自增特性生成的,最終的序列值是id和表序號共同組成的,假定有4個表,序號分別為0,1,2,3;那么序列值為 id<< 2 | table_index; 即id向左移位2位(移位幾位取決于表的個數),然后和表序號求或;
  • `generate_time` 為id生成時間,無其他含義。

不同序號的表可以建在不同的數據庫上,當某個序號的表不可用時要報警,并切換到其他表上生成數字序列。

2. 數據庫的列+1方式

通過對數據庫的某列做+1操作, 來得到唯一的數字序列, 是通過數據庫的行鎖來保障唯一的, 因為涉及到行鎖, 所以這種方式生成序列的單行吞吐量不會太大, 適合需要生成多種(每一種放到一行)不同數字生成需求。 如下是一種推薦的表結構:

  1. create table `xx_rowbased_sequence` ( 
  2.    `owner_key` varchar(32) NOT NULL, 
  3.    `current_value` bigint NOT NULL, 
  4.    PRIMARY KEY (`owner_key`) 
  5. ); 

表中的ownerKey列為單號種類標識, current_value為+1操作列。生成序列的方式用sql說明如下

  1. begin trans; 
  2. UPDATE  `xx_rowbased_sequence`SET current_valuecurrent_value=current_value+1 WHERE owner_key=’order-no’; 
  3. SELECT current_value FROM `xx_rowbased_sequence` WHERE owner_key=’order-no’; 
  4. commit; 

需要注意使用此方式生成數字序列事務隔離級別需要是RR。

3. 使用redis/memcached的INCR指令方式

redis/memcached本身可以保證生成數字的唯一性,和高性能。 單一redis服務器每秒可以生成約6w左右的數字序列。 但需要注意redis必須配置主從和存儲, 以避免在極端情況下redis節點down機, 導致丟失序列或序列重復。

三、高可用實現

上面介紹了4種生成數字序列的方式, 但要保證高可用, 單靠一種序列生成方式還是不夠的, 我們還需要一種高可用的實現。

高可用數字序列生成器內部是2的n次方個底層數字序列生成器, 每個底層序列生成器對應一個下標值, 下標值的范圍為[0, 2n-1]。 在生成序列時, 輪詢底層生成器, 如果正常, 則將生成結果向左移n位, 并與當前底層序列生成器下標取或得到最終序列值。 如果底層序列生成器發生異常, 則將其標記為不可用, 并輪詢下一個底層序列生成器, 直到成功。

高可用實現類com.jd.coo.sa.sequence.ha.BitwiseLoadBalanceSequenceGen,其內部有x個底層SequenceGen實現,此類會輪詢的調用底層SequenceGen來生成序列,如果某個底層序列生成出錯,會從可用列表中移除掉,被移除掉的底層SequenceGen在過xx時間(默認為5分鐘)后,可以重新加入到可用列表中。如果內部序列生成單個序列時間超時,并在最近n時間內連續超時x次,會被移動到異常列表,在異常列表中時間超過xx時間后,也會被重新放入可用列表中。

如果一個底層序列被標記為不可用, 過配置時間后會將其恢復到可用列表中, 自動恢復機制可以避免底層序列生成器已恢復可用, 而程序卻一直不使用此底層序列生成器的情況。

高可用實現的內部結構圖, 如下圖所示:

高可用實現的內部結構圖

其核心方法如下所示:

  1. public long gen(String ownerKey){ 
  2.     long sequence=0
  3.     int currentPartitionIndex=-1; 
  4.     SequenceGen innerGen=null
  5.     do{ 
  6.         long startTime=System.currentTimeMillis(); 
  7.         boolean hasError=false
  8.         try{ 
  9.             currentPartitionIndex=getCurrentPartitionIndex(ownerKey); 
  10.             LOGGER.trace("current partition index {}",currentPartitionIndex); 
  11.             innerGen=innerSequences.get(currentPartitionIndex); 
  12.             if(innerGen==SkipSequence.INSTANCE){ 
  13.                 LOGGER.warn("current partition index {} is skipped",currentPartitionIndex); 
  14.                 if(availablePartitionIndices.contains(currentPartitionIndex)){ 
  15.                     LOGGER.warn("current partition index {} is skipped, remove it",currentPartitionIndex); 
  16.                     availablePartitionIndices.remove(Integer.valueOf(currentPartitionIndex)); 
  17.                 } 
  18.  
  19.                 continue; 
  20.             } 
  21.  
  22.             HighAvailablePartitionHolder.setPartition(currentPartitionIndex); 
  23.             sequence=innerGen.gen(ownerKey); 
  24.             onGenNewId(ownerKey,currentPartitionIndex,sequence); 
  25.             LOGGER.trace("genNewId {} with inner {}",sequence,currentPartitionIndex); 
  26.             break; 
  27.         }catch(SequenceOutOfRangeException ex){ 
  28.             LOGGER.error("gen error SequenceOutOfRangeException index {} total available {}", 
  29.                     currentPartitionIndex, 
  30.                     availablePartitionIndices.size()); 
  31.             hasError=true
  32.  
  33.             LOGGER.error("set {} to SKIP",currentPartitionIndex); 
  34.             this.innerSequences.set(currentPartitionIndex,SkipSequence.INSTANCE); 
  35.             onError(ownerKey,currentPartitionIndex,innerGen,ex); 
  36.             LOGGER.error("after onError total available {}/{}",currentPartitionIndex, 
  37.                     availablePartitionIndices.size()); 
  38.  
  39.         }catch(Exception ex){ 
  40.             LOGGER.error("gen error index {} total available {}",currentPartitionIndex, 
  41.                     availablePartitionIndices.size()); 
  42.             LOGGER.error("gen error ",ex); 
  43.             hasError=true
  44.             onError(ownerKey,currentPartitionIndex,innerGen,ex); 
  45.             LOGGER.error("after onError total available {}/{}",currentPartitionIndex, 
  46.                     availablePartitionIndices.size()); 
  47.         }finally{ 
  48.             long usedTime=System.currentTimeMillis()-startTime; 
  49.             boolean isTimeout=usedTime>timeoutThresholdInMilliseconds; 
  50.             if(!hasError&&isTimeout){ 
  51.                 onTimeout(currentPartitionIndex,innerGen,usedTime); 
  52.             } 
  53.             LOGGER.trace("gen usedTime {}",usedTime); 
  54.         } 
  55.     }while(true); 
  56.     return sequence; 

使用時配置bean使用即可, 如下spring bean xml配置:

  1. <bean id="highAvailableSequenceGen" class="com.jd.coo.sa.sequence.ha.BitwiseLoadBalanceSequenceGen"> 
  2.       <!-- 指定高可用序列底層序列生成序列后向左移位位數--> 
  3.     <constructor-arg index="0" value="2"/> 
  4.      <!-- 指定底層序列 --> 
  5.     <constructor-arg index="1"> 
  6.         <map> 
  7.             <!-- key 為底層序列生成值左移位后或的下標--> 
  8.         <entry key="0"> 
  9.                 <bean class="com.jd.coo.sa.sequence.AutoIncrementTablesSequenceGen"> 
  10.                     <property name="dataSource" ref="dataSourceA"/> 
  11.                     <property name="sequenceTableFormat" value="%s_code_%d"/> 
  12.                 </bean> 
  13.             </entry> 
  14.             <entry key="1"> 
  15.                 <bean class="com.jd.coo.sa.sequence.AutoIncrementTablesSequenceGen"> 
  16.                     <property name="dataSource" ref="dataSourceB"/> 
  17.                     <property name="sequenceTableFormat" value="%s_code_%d"/> 
  18.                 </bean> 
  19.             </entry> 
  20.             <entry key="2"> 
  21.                 <bean class="com.jd.coo.sa.sequence.AutoIncrementTablesSequenceGen"> 
  22.                     <property name="dataSource" ref="dataSourceA"/> 
  23.                     <property name="sequenceTableFormat" value="%s_code_%d"/> 
  24.                 </bean> 
  25.             </entry> 
  26.             <entry key="3"> 
  27.                 <bean class="com.jd.coo.sa.sequence.AutoIncrementTablesSequenceGen"> 
  28.                     <property name="dataSource" ref="dataSourceB"/> 
  29.                     <property name="sequenceTableFormat" value="%s_code_%d"/> 
  30.                 </bean> 
  31.             </entry> 
  32.         </map> 
  33.     </constructor-arg> 
  34.     <!-- 將timeout判斷的閾值設置為一個很大的值, 避免timeout應用error的情況發生--> 
  35.    <property name="timeoutThresholdInMilliseconds" value="200"/> 
  36.     <!-- 超時多少次后會移出可用列表 --> 
  37.    <property name="timeoutEventCountThreshold" value="3"/> 
  38.     <!-- 計算超時異常的時間周期, 以秒為單位 --> 
  39.    <property name="timeoutTimeThresholdInSeconds" value="60" /> 
  40.     <!-- 移到不可用隊列多長時間后會被重新放入可用隊列 --> 
  41.    <property name="onErrorRescueThresholdInSeconds" value="2000"/> 
  42. </bean> 

四、高性能實現

單號生成只是業務操作的第一個步驟, 業務操作往往是復雜耗時的, 我們必須保證單號生成的性能, 使其幾乎不會影響業務時間。

上述介紹的四種序列生成方式都是跨網絡通過中間件獲得的序列號,要進一步優化其性能,我們需要將序列放在離CPU更近的地方――內存中。我們使用如下兩種方式將數字序列放到CPU更近的地方:

  • 將內部序列值向左移位n位, 然后序列的最右n位在內存生成,一次生成2的n次方個數字序列, 然后放在內存隊列中;
  • 異步提前生成:實時計算序列號方法被調用的速度, 然后在異步線程(池)中生成最近x ms需要的序列,放入內存隊列中備用

這兩種方式并不一定都需要, 置放入內存隊列中的數字序列越多,重啟時丟失的也會越多。

其內部結構圖示如下:

高性能序列使用的bean配置如下:

  1. <bean class="com.jd.coo.sa.sequence.QueuedSequenceGen" id="queuedSequenceGen" init-method="start" destroy-method="stop"> 
  2.     <!-- 指定內部序列, 通常是一個高可用的內部序列--> 
  3.     <constructor-arg index="0" ref="haSequenceGen" /> 
  4.     <!-- 指定內存中生成的bit位數--> 
  5.     <property name="memoryBitLength" value="3"/> 
  6.     <!--異步生成配置--> 
  7.     <property name="enableAsync" value="true"/> 
  8.     <property name="asyncTask"> 
  9.         <bean class="com.jd.coo.sa.sequence.QueuedSequenceGen$AsyncTask"> 
  10.             <constructor-arg index="0" ref="queuedSequenceGen"/> 
  11.             <property name="loopSleepInterval" value="20"/> 
  12.             <property name="reserveTimeInMilliseconds" value="10"/> 
  13.         </bean> 
  14.     </property> 
  15.     <!--結束異步配置--> 
  16. </bean> 

通過設定memoryBitLength,指定序列的最右的memoryBitLength位在內存中生成以提高生成的效率。 需要注意memoryBitLength值越大則在內存中的序列條數越多, 性能越高, 如果發生重啟時丟失的序列也會越多, 要根據情況來設置。 支持異步生成序列值, 異步生成的速度會根據序列值消費速度自適應。

五、關于可擴展性

單號規則多種多樣, 不能每增加一種規則就增加一個需求, 我們需要相對靈活的擴展性。 上述介紹了多種單號數字序列的生成方式, 和數字序列生成的高可用和高性能實現, 他們都實現了同一個接口:

  1. /** 
  2.  * 根據序列業務類型生成新序列的接口 
  3.  * 
  4.  * 生成序列是大致遞增的 
  5.  * 
  6.  * Created by zhaoyukai on 2016/8/8. 
  7.  */ 
  8. public interface SequenceGen { 
  9.     /** 
  10.      * 生成序列 
  11.      * @param ownerKey 序列業務key 
  12.      * @return 新序列值 
  13.      */ 
  14.     long gen(String ownerKey); 

有了這個統一的數字序列生成接口, 我們可以擴展多種不同的數字序列生成方式。 或者實現不同的高可用、高性能機制。

另外在本文的開頭我們介紹了多種不同的單號生成規則, 要靈活滿足這些不同的規則, 我們使用表達式來表達單號的組合規則, 通過將表達式解析成不同的Expression來實現不同單號部分的生成。 下面我們看一個單號表達式的示例, 如下是一個spring bean配置:

  1. <!-- 單號生成bean, 在應用中注入此bean生成單號 --> 
  2. <bean class="com.jd.coo.sa.sn.SmartSNGen" name="snGen"> 
  3.     <!-- 序列號的表達式, 見下面說明 --> 
  4.     <constructor-arg value="@{ownerKey, value=SN}-@{bean, ref=sequence}-@{com.jd.coo.sa.sn.expression.CheckSumExpression}"/> 
  5.     <!-- 表達式解析器 --> 
  6.     <property name="interpreter"> 
  7.         <!-- 單號生成器的表達式解釋器, 固定為SmartInterpreter--> 
  8.         <bean class="com.jd.coo.sa.sn.expression.SmartInterpreter" name="smartInterpreter"/> 
  9.     </property> 
  10. </bean> 

SmartSNGen類負責根據表達式生成不同規則的單號,其構造函數第一個參數值:

 

  1. @{ownerKey, value=SN}-@{bean, ref=sequence}- 

@{com.jd.coo.sa.sn.expression.CheckSumExpression} 即為表達式, 該表達式分為五個部分:

  • @{ownerKey, value=SN} 在表達式生成的上下文中寫入key為ownerKey值為SN的參數
  • “-“ 表示靜態表達式“-”
  • @{bean, ref=sequence} 指定引用id為sequence的spring bean來生成表達式的一部分
  • “-“表示靜態表達式”-“
  • @{com.jd.coo.sa.sn.expression.CheckSumExpression} 表示要創建指定類com.jd.coo.sa.sn.expression.CheckSumExpression的實例來生成表達式的一部分

該bean的interpreter屬性指定了表達式的解釋器,該解釋器會將表達式值轉換為實現了Expression接口的對象,通過該對象可以計算出單號的值。

表達式解釋器查找表達式中的“@{”和“}”對,將其內部的表達式解析為動態表達式,將其他部分的表達式解析為靜態表達式。動態表達式分為三種類型:

  1. spring配置文件中的bean引用表達式
  2. 指定上下文參數的表達式
  3. 指定自定義類型的表達式

第3種表達式留出任意擴展自定義表達式的擴展點。

Expression接口定義如下:

  1. import com.jd.coo.sa.sn.GenContext; 
  2.  
  3. /** 
  4.  * SmartSNGen表達式接口 
  5.  * 
  6.  * Created by zhaoyukai on 2016/10/18. 
  7.  */ 
  8. public interface Expression { 
  9.     /** 
  10.      * 計算表達式的值 
  11.      * @param context 表達式計算上下文, 表達式可以根據需要將計算中間值存儲到上下文中, 以便在表達式之間共享數據 
  12.      * @return 表達式計算值 
  13.      */ 
  14.     Object eval(GenContext context); 
  15.  
  16.     /** 
  17.      * 計算優先級, 優先級越高越先執行, 如果表達式需要依賴其他表達式的值, 則要在依賴表達式計算之后執行 
  18.      * @return 執行順序 
  19.      */ 
  20.     ExecuteOrder executeOrder(); 
  21.  
  22.     /** 
  23.      * 該表達式的最大字符串長度值 
  24.      * 
  25.      * @return 最大長度值 
  26.      */ 
  27.     int maxLength(); 

通過實現此接口即可實現任何自定義的單號生成邏輯。如下是自定義的單號校驗位生成表達式示例:

  1. public class CheckSumExpressionimplements Expression { 
  2.     public Object eval(GenContext context) { 
  3.         Long newId = (Long) context.get("sequence"); 
  4.         if (newId == null) { 
  5.             throw newRuntimeException("sequence can not be null when calculate checksum"); 
  6.         } 
  7.         return newId * 9 % 31 % 10; 
  8.     } 
  9.     public ExecuteOrder executeOrder() { 
  10.         return ExecuteOrder.AfterNormal; 
  11.     } 
  12.     public int maxLength() { 
  13.         return 1; 
  14.     } 

總結

本文提到了多種單號數字序列生成方式,還介紹了高可用、高性能以及擴展性的實現方式。

 

  1. 要根據場景, 并發量, 單號類型數量選擇數字序列生成方式;
  2. 不要裸奔, 要使用高可用+高性能序列生成器, 保證單號生成方式的可用性和性能;
  3. 底層序列要從物理上做隔離, 否則出現硬件故障高可用機制也會時效;
  4. 使用了多個底層序列生成方式時生成的序列是大致自增, 不能保證完全自增, 這是設計使然, 如果要保證完全自增, 則會出現單點, 在完全自增和單點的選擇上, 我們選擇了大致自增+非單點;
  5. 高性能序列生成的性能可以通過調節其memoryBitLength屬性來提高, 但要根據業務實際情況來做選擇,memoryBitLength屬性值越高在內存生成的序列數越多,性能越高, 但在進程停止時丟失的序列也會越多。

作者:趙玉開,十年以上互聯網研發經驗,2013年加入京東,在運營研發部任架構師,期間先后主持了物流系統自動化運維平臺、青龍數據監控系統和物流開放平臺的研發工作,具有豐富的物流系統業務和架構經驗。在此之前在和訊網負責股票基金行情系統的研發工作,具備高并發、高可用互聯網應用研發經驗。

【本文來自51CTO專欄作者張開濤的微信公眾號(開濤的博客),公眾號id: kaitao-1234567】

 戳這里,看該作者更多好文

責任編輯:趙寧寧 來源: 51CTO專欄
相關推薦

2021-05-24 09:28:41

軟件開發 技術

2017-11-27 09:14:29

2012-06-13 02:10:46

Java并發

2012-07-19 10:59:18

Jav并發

2012-11-14 15:25:58

2022-06-02 12:56:25

容器網絡云原生

2020-12-09 09:21:41

微服務架構數據

2011-10-20 15:36:36

高可用高性能MySQL

2017-12-22 09:21:02

API架構實踐

2023-03-21 08:01:44

Crimson硬件CPU

2018-03-26 09:02:54

MongoDB高可用架構

2025-05-06 01:00:00

Excel高性能內存

2019-03-01 11:03:22

Lustre高性能計算

2013-03-13 10:08:17

用友UAP高可用高性能

2012-04-17 16:48:43

應用優化負載均衡Array APV

2019-08-23 08:09:18

訂單號生成數據庫ID

2013-04-09 10:16:28

OpenStackGrizzlyHyper-V

2013-06-07 11:30:32

2023-08-22 13:16:00

分布式數據庫架構數據存儲

2009-07-31 11:41:12

光纖連接數據中心
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国内精品视频免费观看 | 午夜精品一区二区三区在线视 | 国产亚洲精品区 | 亚洲精品视频一区二区三区 | 99久久国产综合精品麻豆 | 日韩福利在线观看 | 最新中文字幕一区 | 手机三级电影 | 国产丝袜一区二区三区免费视频 | 亚洲激情一区二区 | 精品国产18久久久久久二百 | 日韩五月天 | 在线看免费的a | 97人人澡人人爽91综合色 | 天天天操天天天干 | 91精品国产综合久久久密闭 | 成人妇女免费播放久久久 | 精品国产欧美 | av午夜电影 | 国产福利网站 | 免费成人高清在线视频 | 日韩精品视频在线免费观看 | 日韩三| 91深夜福利视频 | 中文字幕在线不卡播放 | 成人免费视频观看视频 | 久久精品这里 | 免费三级网站 | 国产大学生情侣呻吟视频 | 欧美视频第三页 | 国产98色在线 | 日韩 | 久久久久久免费毛片精品 | 不卡在线视频 | 99热热 | 人人看人人爽 | 91精品国产91久久综合桃花 | 国产精品伦一区二区三级视频 | 超碰成人免费观看 | 有码一区 | 国产精品欧美精品 | 久久婷婷av |