全局唯一序列號生成器-支持分佈式

全局唯一序列號生成器-支持分佈式

場景:在某些業務場景,需要生成唯一的序列號,來定位某一個條數據,且必須保證全局唯一,例如:交易流水號等。

核心類

/**
 * 序列號生成服務
 */
@Service
public class SnGenerator {

    private static final Logger logger = LoggerFactory.getLogger(SnGenerator.class);

    private HashMap<String,SeqFragmentDefine> seqMap;

    @Resource
    TblXxxBatseqCtrlMapper tblXxxBatseqCtrlMapper;


     SnGenerator()
     {
         seqMap=new HashMap();
     }
    public synchronized String getSeq(String seqName,int length)
    {
        //從內存中去取
        SeqFragmentDefine seqDefine=  seqMap.get(seqName);
          if(seqDefine==null)
          {
              //如果爲空,存入新對象
              seqMap.put(seqName,new SeqFragmentDefine());
              getNewFragment(seqName) ;
          }
          else
          {
              //如果不爲空,將內存中seqDefine對象seqCurrent+1
              seqDefine.setSeqCurrent(seqDefine.getSeqCurrent()+1);
              //如果當前值大於最大值,重新生成seqDefine對象
              if(seqDefine.getSeqCurrent()>seqDefine.getSeqFragmentMax())
              {
                  getNewFragment(seqName) ;
              }
          }
          return String.format(String.format("%%0%dd",length),seqMap.get(seqName).getSeqCurrent());
    }
    @Transactional
    protected   void getNewFragment(String seqName)
    {
        logger.debug("獲取流水號段  事務開始");
        //從緩存中取出Seq序列號對象
        SeqFragmentDefine seqDefine=  seqMap.get(seqName);
        TblXxxBatseqCtrl TblXxxBatseqCtrl=tblXxxBatseqCtrlMapper.selectForUpdateByPrimaryKey(seqName);
        //如果對象存在,則賦值+1
        if(TblXxxBatseqCtrl!=null)
        {
            seqDefine.setSeqFragmentMax(TblXxxBatseqCtrl.getSeqNo()+TblXxxBatseqCtrl.getStepLength());//130000
            seqDefine.setSeqCurrent(TblXxxBatseqCtrl.getSeqNo()+1);

            TblPipXxxBatseqCtrl.setSeqNo(seqDefine.getSeqFragmentMax());//130000
            //TblPipXxxBatseqCtrl.setSeqNo(seqDefine.getSeqCurrent());//120001
            tblPipXxxBatseqCtrlMapper.updateByPrimaryKey(TblPipXxxBatseqCtrl);
        }
        else
        {//如果不存在對象,重新創建TblPipXxxBatseqCtrlInsert值,SeqCurrent從0開始,SeqFragmentMax(10000)
            TblPipXxxBatseqCtrl TblPipXxxBatseqCtrlInsert=new TblPipXxxBatseqCtrl();
            TblPipXxxBatseqCtrlInsert.setSeqName(seqName);
            TblPipXxxBatseqCtrlInsert.setStepLength(10000);
            TblPipXxxBatseqCtrlInsert.setSeqNo(Long.valueOf(10000));
            tblPipXxxBatseqCtrlMapper.insertSelective(TblXxxBatseqCtrlInsert);
            seqDefine.setSeqCurrent(0+1);//下一個從1開始
            seqDefine.setSeqFragmentMax(0+10000);//最大值爲10000
        }
        logger.debug("獲取流水號段  事務結束");
    }
    class SeqFragmentDefine
    {
        //當前段的最大值(包含)
        private long  seqFragmentMax;
        private long  seqCurrent;

        public long getSeqFragmentMax() {
            return seqFragmentMax;
        }

        public void setSeqFragmentMax(long seqFragmentMax) {
            this.seqFragmentMax = seqFragmentMax;
        }


        public long getSeqCurrent() {
            return seqCurrent;
        }

        public void setSeqCurrent(long seqCurrent) {
            this.seqCurrent = seqCurrent;
        }
    }
}

實體類

public class TblXxxBatseqCtrl extends BaseEntity {
    private String seqName;

    private Long seqNo;

    private Integer stepLength;

    private Date recCrtTs;

    private Date recUpdTs;

    public String getSeqName() {
        return seqName;
    }

    public void setSeqName(String seqName) {
        this.seqName = seqName == null ? null : seqName.trim();
    }

    public Long getSeqNo() {
        return seqNo;
    }

    public void setSeqNo(Long seqNo) {
        this.seqNo = seqNo;
    }

    public Integer getStepLength() {
        return stepLength;
    }

    public void setStepLength(Integer stepLength) {
        this.stepLength = stepLength;
    }

    public Date getRecCrtTs() {
        return recCrtTs;
    }

    public void setRecCrtTs(Date recCrtTs) {
        this.recCrtTs = recCrtTs;
    }

    public Date getRecUpdTs() {
        return recUpdTs;
    }

    public void setRecUpdTs(Date recUpdTs) {
        this.recUpdTs = recUpdTs;
    }
}

表結構

drop table if exists tbl_Xxx_batseq_ctrl;
CREATE TABLE tbl_Xxx_batseq_ctrl (seq_name varchar(20) NOT NULL, 
seq_no bigint NOT NULL comment '已經使用到的最大值(包含)', 
step_length int,
rec_crt_ts timestamp DEFAULT CURRENT_TIMESTAMP, 
rec_upd_ts timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
PRIMARY KEY (seq_name)) ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci;

核心邏輯解釋

  • format器

String.format(String.format("%%0%dd",length),seqMap.get(seqName).getSeqCurrent())
等價於String.format("%0"+length+“d”,seqMap.get(seqName).getSeqCurrent())
例如: String.format("%08d",1)
輸出: 00000001
例如: String.format("%08d",-1)
輸出:-0000001
例如: String.format("%08d",200000001)
輸出:200000001 (超出長度,輸出原值)

  • 設計邏輯

a.每次啓動,分配步長爲10000的序列號給服務器節點使用
b.以損耗序列號資源來換取數據庫服務器的高性能,每個節點的序列號值,不更新到數據庫
c.由於synchronized關鍵字,解決了併發問題

  • 內存圖解
    在這裏插入圖片描述

學習Java的同學注意了!!!
學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流羣,羣號碼:543120397 我們一起學Java!

發佈了80 篇原創文章 · 獲贊 82 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章