怎樣生成分散式的流水ID

2022-06-02 18:00:17

流水編號

日常在我們開發的過程中可能會用到編號的功能,如銷售訂單號,採購訂單號,紀錄檔編號,憑證號...等等,為了保證唯一有些表的主鍵要麼用自增長,要麼用GUID值,或通過雪花ID演演算法生成。這此方式基本都能產生唯一的ID,但如果在分散式環境下產生流水ID,以上這幾種方式可能就不太好用,如有以下場景

  1. 工作流的流水編號

工作流的編號通常會是以下格式 如2022060200001-2022060299999 到了第二天時尾數又要生00001開始編,這種編號規則有一個好處就是非常直觀的通過工作流編號就可以看出來這是哪一天申請的流程,一天大概有多少流水碼。那麼如果我們用常規編號規則其實是比較難完成此需求的。

  1. 採購訂單號

如在實際業務需求中 標準採購訂單要用5000000000-5999999999 這個號進行進行編碼,委外採購訂單用4000000000-4999999999號段進行編碼

  1. XX業務受理單編號

在辦裡某業務時根據業務型別使用不同的編號 如財務收款用的流水號是 SK10000000-SK99999999,支出用的是ZC10000000-ZC99999999

根據以上的業務場景如果使用常規的編號是比較難實現的(要保證分散式環境編號不重複且按流水編碼),那麼HiSql 提供了比較方便的解決方案

該版本還未正式更新nuget 需要使用請下載原始碼

怎樣開啟編號規則

  //如果需要使用編號那麼一定要開啟此功能
  HiSql.Global.SnroOn = true;


  //開啟編號後進行初始化  
  sqlClient.CodeFirst.InstallHisql();//僅需執行一次 如果使用的低版本的HiSql 升級參照包手需要重新初始化安裝


初始安裝完成後會生成表Hi_Snro 這個編號設定表

編號設定介紹

Hi_Snro 表詳細說明

欄位 描述 備註
SNRO 編號規則名稱 主鍵 字串(10)
SNUM 子編號 主鍵 整數
IsSnow 是否雪花ID bool true:表示雪花id false:表示自定義流水編號
SnowTick 雪花ID時間戳 int64 IsSnow 為true時設定 大於0 都可以,其它的都可以不用設定
StartNum 編號開始值 字串(20) 當IsNumber 為true 時編號只能純數位 為false時 可以以0-9 A-Z 混編
EndNum 編號結束值 字串(20) 當編號超過此時時將會丟擲異常號段池已滿
CurrNum 當前編號值 字串(20) 第一次設定時值要等於StartNum
CurrAllNum 當前完整編號 字串(40) 不需要設定,產生流水時會自動生成
Length 編號長度 StartNumEndNum 的長度 兩者的長度要一至否則會報錯
IsNumber 是否純數位編號 當為true 編號按0-9的10進位制數位編號,當為false時按0-9 A-Z 36進度數位和字母混合編號
IsHasPre 是否有前輟 可以按到年月日時分秒作為前輟 在PreType 設定
PreType 前輟編號型別 詳細請見 PreType 前置編號型別設定
FixPreChar 固定前輟 固定一個字串 每個生成的碼前面都加上這個值
PreChar 當前前輟 不需要設定 在編號的過程中會將 FixPreCharPreType 存在此昝
CacheSpace 號段快取 編號使用越頻繁這個值設定越大 常的建議設定為10 值越大編號效能越好
CurrCacheSpace 當前快取池使用的數量 不需要設定
Descript 編號描述 備註一下當前編號規則

PreType 前置編號型別設定

PreType 是一個列舉類

欄位 備註
PreType.None 0 表示無前置
PreType.Y 1 表示前置為日期格式[yyyy] 即當前年份如2022
PreType.Y2 12 表示前置為日期格式[yy] 即當前年份後兩位如22
PreType.YM 2 表示前置為日期格式[yyyyMM] 即當前年月如202206
PreType.Y2M 22 表示前置為日期格式[yyMM] 即當前年月如2206
PreType.YMD 3 表示前置為日期格式[yyyyMMdd] 即當前年月日如20220602
PreType.Y2MD 32 表示前置為日期格式[yyMMdd] 即當前年月日如220602
PreType.YMDH 4 表示前置為日期格式[yyyyMMddHH] 即當前年月日時如2022060216
PreType.Y2MDH 42 表示前置為日期格式[yyMMddHH] 即當前年月日時如22060216
PreType.YMDHm 5 表示前置為日期格式[yyyyMMddHHmm] 即當前年月日時分如202206021630
PreType.Y2MDHm 52 表示前置為日期格式[yyMMddHHmm] 即當前年月日時分如2206021630
PreType.YMDHms 6 表示前置為日期格式[yyyyMMddHHmmss] 即當前年月日時分秒如20220602163020
PreType.Y2MDHms 62 表示前置為日期格式[yyMMddHHmmss] 即當前年月日時分秒如220602163020

根據以上設定規則將資料設定到表中

設定樣例

Hi_Snro 的設定可以自行做個設定介面進行設定,以下是通過程式進行設定儲存.

  List<Hi_Snro> list = new List<Hi_Snro>();

  ///工作流編號設定
  ///按天產生流水號 如2205061000000-2205069999999 之間

  list.Add( new Hi_Snro { SNRO = "WFNO", SNUM = 1, IsSnow = false, SnowTick = 0, StartNum = "1000000", EndNum = "9999999", Length = 7, CurrNum = "1000000", IsNumber = true, PreType=PreType.Y2MD, FixPreChar="", IsHasPre = true, CacheSpace = 5, Descript = "工作流編號" });

  ///生成銷售訂單編碼 每分鐘從0開始編號 如20220602145800001-20220602145899999
  list.Add(new Hi_Snro{ SNRO = "SALENO", SNUM = 1, IsSnow = false, SnowTick = 0, StartNum = "10000", EndNum = "99999", Length = 5, CurrNum = "10000", IsNumber = true, PreType = PreType.YMDHm, FixPreChar = "", IsHasPre = true, CacheSpace = 10, Descript = "銷售訂單號流水" });
  ///生成另外一種銷售訂單編碼 年的是取後兩位 按每秒順序生成 如22060214581200001-22060214581299999
  list.Add(new Hi_Snro { SNRO = "SALENO", SNUM = 2, IsSnow = false, SnowTick = 0, StartNum = "10000", EndNum = "99999", Length = 5, CurrNum = "10000", IsNumber = true, PreType = PreType.Y2MDHms, FixPreChar = "", IsHasPre = true, CacheSpace = 10, Descript = "銷售訂單號流水" });


  ///通過雪花ID生成
  list.Add( new Hi_Snro { SNRO = "Order", SNUM = 1, IsSnow=true, SnowTick=145444, StartNum = "", EndNum = "",Length=7, CurrNum = "", IsNumber = true, IsHasPre = false, CacheSpace = 10, Descript = "訂單號雪花ID" });


  //儲存設定到表中
  sqlClient.Modi("Hi_Snro", list).ExecCommand();

編號生成程式碼環境設定

建議把SeriNumber 做成一個局靜態變數

  public static SeriNumber number = null;  //定義個全域性變更的流水號物件

設定連線

  //sqlClient 為資料庫連線物件
  number = new SeriNumber(sqlClient);

如果應用進行了分散式佈署 請一定要啟用以下程式碼

  HiSql.Global.RedisOn = true;//開啟redis快取
  HiSql.Global.RedisOptions = new RedisOptions { Host = "172.16.80.178", PassWord = "qwe123", Port = 6379, CacheRegion = "HRM", Database = 2 };



  HiSql.Global.NumberOptions.MultiMode= true;


  //如果部署了分散式且也要生成雪花ID 一定要設定以下當前應用的ID 多個應用間只要不重複即可0-31之間
  HiSql.Global.NumberOptions.WorkId=1;

通過以上設定則分散式編號環境設定完成,下面就可以進行編號測試了

編號測試

為了測試編號是否有重複我這裡建了一個測試記錄表H_nlog ,編號生成完成後可以分析該表的資料看是否有重複資料,(測試環境 為SqlServer) sql程式碼如下


  SET ANSI_NULLS ON
  GO

  SET QUOTED_IDENTIFIER ON
  GO

  CREATE TABLE [dbo].[H_nlog](
    [Nid] [int] IDENTITY(1,1) NOT NULL,
    [Numbers] [varchar](50) NULL,
    [CreateTime] [datetime] NULL,
    [CreateName] [nvarchar](50) NULL,
    [ModiTime] [datetime] NULL,
    [ModiName] [nvarchar](50) NULL,
  CONSTRAINT [PK_H_nlog] PRIMARY KEY CLUSTERED 
  (
    [Nid] ASC
  )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  ) ON [PRIMARY]
  GO

  ALTER TABLE [dbo].[H_nlog] ADD  CONSTRAINT [DF_H_nlog_CreateTime]  DEFAULT (getdate()) FOR [CreateTime]
  GO

  ALTER TABLE [dbo].[H_nlog] ADD  CONSTRAINT [DF_H_nlog_CreateName]  DEFAULT ('') FOR [CreateName]
  GO

  ALTER TABLE [dbo].[H_nlog] ADD  CONSTRAINT [DF_H_nlog_ModiTime]  DEFAULT (getdate()) FOR [ModiTime]
  GO

  ALTER TABLE [dbo].[H_nlog] ADD  CONSTRAINT [DF_H_nlog_ModiName]  DEFAULT ('') FOR [ModiName]
  GO

  EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'建立時間' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_nlog', @level2type=N'COLUMN',@level2name=N'CreateTime'
  GO

  EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'建立人' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_nlog', @level2type=N'COLUMN',@level2name=N'CreateName'
  GO

  EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'修改時間' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_nlog', @level2type=N'COLUMN',@level2name=N'ModiTime'
  GO

  EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'修改人' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'H_nlog', @level2type=N'COLUMN',@level2name=N'ModiName'
  GO



根據編號規則WFNO 子編號1 生成編號

  List<object> lst = new List<object>();
  for (int i = 0; i < 1000; i++)
  {
      var num = number.NewNumber("WFNO", 1);
      lst.Add(new { Numbers = num });
      Console.WriteLine(num);
  }

    sqlClient.Insert("H_nlog", lst).ExecCommand();

測試結果
編號流水結果

啟用了分散式多個應用同時對同一個編號規則產生編號不會產生重複編號。