日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
淺談訂單號生成的設計方案

今天討論分享下訂單號生成的簡單實現(xiàn)方案,為實際場景中需要用到訂單號生成服務提供解決思路。

10年積累的做網(wǎng)站、網(wǎng)站建設經(jīng)驗,可以快速應對客戶對網(wǎng)站的新想法和需求。提供各種問題對應的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡服務。我雖然不認識你,你也不認識我。但先網(wǎng)站設計后付款的網(wǎng)站建設流程,更有鳳陽免費網(wǎng)站建設讓你可以放心的選擇與我們合作。

最簡單的方式

基于數(shù)據(jù)庫 auto_increment_increment 來獲取 ID。首先在數(shù)據(jù)庫中創(chuàng)建一張 sequence 表,其中 seq_name 用以區(qū)分不同業(yè)務標識,從而實現(xiàn)支持多種業(yè)務場景下的自增 ID,current_value 為當前值,_increment 為步長,可支持分布式數(shù)據(jù)庫的哈希策略。

 
 
 
  1. CREATE TABLE `sequence` (
  2. `seq_name` varchar(200) NOT NULL,
  3. `current_value` bigint(20) NOT NULL,
  4. `_increment` int(4) NOT NULL,
  5.   PRIMARY KEY (`seq_name`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8

通過 SELECT LAST_INSERT_ID() 方法,更新 sequence 表,進行 ID 遞增,并同時獲取上次更新的值。這里注意,current_value = LAST_INSERT_ID(current_value + _increment) 將更新的 ID 賦值給了 LAST_INSERT_ID,否則返回的將是行 id。

 
 
 
  1.     UPDATE sequence
  2.     SET
  3.     current_value = LAST_INSERT_ID(current_value + _increment)
  4.     WHERE
  5.     seq_name = #{seqName}
  6.         

最后 Dao 提供服務,需要提醒的是注意數(shù)據(jù)庫的事務隔離級別,如果將 getSeq() 方法放到 Service 中有事務的方法里,將出現(xiàn)問題,因為數(shù)據(jù)庫事務開啟會創(chuàng)建一張視圖,在事務沒有提交之前,更新的 ID 還沒有被提交到數(shù)據(jù)庫中,這在多線程并發(fā)操作的情況下,如果事務里的其他方法導致性能慢了,可能出現(xiàn)兩個請求獲取到相同的 ID,所以解決方法一是不要將 getSeq() 方法放到有事務的方法里,另一種就是將 getSeq() 方法的隔離界別為 PROPAGATION_REQUIRES_NEW,實現(xiàn)開啟新事務,外層事務不會影響內部事務的提交。

 
 
 
  1. @Autowired
  2. private SeqDao seqDao;
  3. @Autowired
  4. private PlatformTransactionManager transactionManager;
  5. @Override
  6. public long getSeq(final String seqName) throws Exception {
  7.     TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
  8. // 事務行為,獨立于外部事物獨立運行
  9.     transactionTemplate
  10.             .setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
  11. return (Long) transactionTemplate.execute(new TransactionCallback() {
  12. public Object doInTransaction(TransactionStatus status) {
  13. try {
  14.                 Seq seq = new Seq();
  15.                 seq.setSeqName(seqName);
  16. if (seqDao.update(seq) == 0) {
  17. throw new RuntimeException("seq update failure.");
  18.                 }
  19. return seq.getId();
  20.             } catch (Exception e) {
  21. throw new RuntimeException("seq update error.");
  22.             }
  23.         }
  24.     });
  25. }

稍復雜一點的方法

上述的方法的問題,想必大家都知道,就是每次獲取 ID 都要調用數(shù)據(jù)庫,在高并發(fā)的情況下會對數(shù)據(jù)庫產(chǎn)生極大的壓力,我們的改進方法也很簡單,就是一次申請一個段的 ID,然后發(fā)到內存里,每次獲取 ID 先從內存里取,當內存中的 ID 段全部被獲取完畢,則再一次調用數(shù)據(jù)庫重新申請一個新的 ID 段。同樣有數(shù)據(jù)庫表的設計,通過 Name 區(qū)分業(yè)務,用 ID 標明已經(jīng)申請到的最大值。當然如果是分布式架構,也可以通過增加步長屬性來實現(xiàn)。

 
 
 
  1. CREATE TABLE `sequence_value` (
  2. `Name` varbinary(50) DEFAULT NULL,
  3. `ID` int(11) DEFAULT NULL
  4. ) ENGINE = InnoDB DEFAULT CHARSET = utf8

Step 是 ID 段的內存對象,有兩個屬性,其中 currentValue 當前的使用到的值,endValue 是內存申請的最大值。

 
 
 
  1. class Step {
  2. private long currentValue;
  3. private long endValue;
  4.     Step(long currentValue, long endValue) {
  5. this.currentValue = currentValue;
  6. this.endValue = endValue;
  7.     }
  8. public void setCurrentValue(long currentValue) {
  9. this.currentValue = currentValue;
  10.     }
  11. public void setEndValue(long endValue) {
  12. this.endValue = endValue;
  13.     }
  14. public  long incrementAndGet() {
  15. return ++currentValue;
  16.     }
  17. }

代碼的實現(xiàn)稍微復雜一點,獲取 ID 會根據(jù)業(yè)務標識 sequencename,先從內存獲取 Step 的 ID 段,如果為 null,則從數(shù)據(jù)庫中讀取當前最新的值,并根據(jù)步長計算 Step,然后返回請求 ID。如果從內存中直接獲取到 Step,則直接取 ID,并對 currentValue 進行加一。當 currentValue 的值超過 endValue 時,則更新數(shù)據(jù)庫的 ID,重新計算 Step。

 
 
 
  1. private Map stepMap = new HashMap();
  2. public synchronized long get(String sequenceName) {
  3.     Step step = stepMap.get(sequenceName);
  4. if(step ==null) {
  5.         step = new Step(startValue,startValue+blockSize);
  6.         stepMap.put(sequenceName, step);
  7.     } else {
  8. if (step.currentValue < step.endValue) {
  9. return step.incrementAndGet();
  10.         }
  11.     }
  12. if (getNextBlock(sequenceName,step)) {
  13. return step.incrementAndGet();
  14.     }
  15. throw new RuntimeException("No more value.");
  16. }
  17. private boolean getNextBlock(String sequenceName, Step step) {
  18. // "select id from sequence_value where name = ?";
  19.     Long value = getPersistenceValue(sequenceName);
  20. if (value == null) {
  21. try {
  22. // insert into sequence_value (id,name) values (?,?)
  23. value = newPersistenceValue(sequenceName);
  24.         } catch (Exception e) {
  25. value = getPersistenceValue(sequenceName); 
  26.         }
  27.     }
  28. // update sequence_value set id = ?  where name = ? and id = ?
  29.     boolean b = saveValue(value,sequenceName) == 1;
  30. if (b) {
  31.         step.setCurrentValue(value);
  32.         step.setEndValue(value+blockSize);
  33.     }
  34. return b;
  35. }

使用該方法獲取 ID 可以減少對數(shù)據(jù)庫的訪問量,以降低數(shù)據(jù)庫的壓力,但是同樣需要注意,獲取 ID 同樣關注數(shù)據(jù)庫事務問題,因為當系統(tǒng)重啟的時候,stepMap 為 null,所以會取數(shù)據(jù)庫查詢當前 ID,更計算更新 Step,然后更新數(shù)據(jù)庫的 ID。如果該方法被放到數(shù)據(jù)庫事務里,由于其他方法性能慢了,導致查詢之后沒有及時更新,并發(fā)情況下另一個線程查詢的時候,可能會獲取到該線程未提交的 ID,因而出現(xiàn)兩個線程獲取到相同的 ID 問題。

本文小結

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

【本文是專欄作者張開濤的原創(chuàng)文章,作者微信公眾號:開濤的博客,id:kaitao-1234567】


當前標題:淺談訂單號生成的設計方案
文章起源:http://www.dlmjj.cn/article/djhoieo.html