1. 程式人生 > >高併發環境下生成訂單唯一流水號方法:SnowFlake

高併發環境下生成訂單唯一流水號方法:SnowFlake

業務需求:

  • 訂單號不能重複
  • 訂單號沒有規則,即編碼規則不能加入任何和公司運營相關的數
  • 據,外部人員無法通過訂單ID猜測到訂單量。不能被遍歷。
  • 訂單號長度固定,且不能太長
  • 易讀,易溝通,不要出現數字字母換亂現象
  • 生成耗時
    關於訂單號的生成,一些比較簡單的方案:
  • 資料庫自增長ID
    優勢:無需編碼
    缺陷:
    大表不能做水平分表,否則插入刪除時容易出現問題
    高併發下插入資料需要加入事務機制
    在業務操作父、子表(關聯表)插入時,先要插入父表,再插入子表
  • 時間戳+隨機數
    優勢:編碼簡單
    缺陷:隨機數存在重複問題,即使在相同的時間戳下。每次插入資料庫前需要校驗下是否已經存在相同的數值。
  • 時間戳+會員ID
    優勢:同一時間,一個使用者不會存在兩張訂單
    缺陷:會員ID也會透露運營資料,雞生蛋,蛋生雞的問題
  • GUID/UUID
    優勢:簡單
    劣勢:使用者不友好,索引關聯效率較低。
    • 今天要分享的方案:來自twitter的SnowFlake
      Twitter-Snowflake演算法產生的背景相當簡單,為了滿足Twitter每秒上萬條訊息的請求,每條訊息都必須分配一條唯一的id,這些id還需要一些大致的順序(方便客戶端排序),並且在分散式系統中不同機器產生的id必須不同.Snowflake演算法核心把時間戳,工作機器id,序列號(毫秒級時間41位+機器ID 10位+毫秒內序列12位)組合在一起。
      SnowFlake-64bit
    • 在上面的字串中,第一位為未使用(實際上也可作為long的符號位),接下來的41位為毫秒級時間,然後5位datacenter標識位,5位機器ID(並不算識別符號,實際是為執行緒標識),然後12位該毫秒內的當前毫秒內的計數,加起來剛好64位,為一個Long型。
      除了最高位bit標記為不可用以外,其餘三組bit佔位均可浮動,看具體的業務需求而定。預設情況下41bit的時間戳可以支援該演算法使用到2082年,10bit的工作機器id可以支援1023臺機器,序列號支援1毫秒產生4095個自增序列id。
      詳細參考:
      https://www.biaodianfu.com/snowflake.html
import java.math.BigInteger;
/**
 * 42位的時間字首+10位的節點標識+12位的sequence避免併發的數字(12位不夠用時強制得到新的時間字首)
 * <p>
 * <b>對系統時間的依賴性非常強,需要關閉ntp的時間同步功能,或者當檢測到ntp時間調整後,拒絕分配id。
 * 
 * @author sumory.wu
 * @date 2012-2-26 下午6:40:28
 */
public class IdWorker {

    private final
long workerId; private final long snsEpoch = 1330328109047L;// 起始標記點,作為基準 private long sequence = 0L;// 0,併發控制 private final long workerIdBits = 10L;// 只允許workid的範圍為:0-1023 private final long maxWorkerId = -1L ^ -1L << this.workerIdBits;// 1023,1111111111,10位 private final long sequenceBits = 12L;// sequence值控制在0-4095 private final long workerIdShift = this.sequenceBits;// 12 private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;// 22 private final long sequenceMask = -1L ^ -1L << this.sequenceBits;// 4095,111111111111,12位 private long lastTimestamp = -1L; public IdWorker(long workerId) { super(); if (workerId > this.maxWorkerId || workerId < 0) {// workid < 1024[10位:2的10次方] throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", this.maxWorkerId)); } this.workerId = workerId; } public synchronized long nextId() throws Exception { long timestamp = this.timeGen(); if (this.lastTimestamp == timestamp) {// 如果上一個timestamp與新產生的相等,則sequence加一(0-4095迴圈),下次再使用時sequence是新值 //System.out.println("lastTimeStamp:" + lastTimestamp); this.sequence = this.sequence + 1 & this.sequenceMask; if (this.sequence == 0) { timestamp = this.tilNextMillis(this.lastTimestamp);// 重新生成timestamp } } else { this.sequence = 0; } if (timestamp < this.lastTimestamp) { System.out.println("Clock moved backwards.Refusing to generate id for "+(this.lastTimestamp - timestamp)+"milliseconds."); throw new Exception(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp))); } this.lastTimestamp = timestamp; // 生成的timestamp return timestamp - this.snsEpoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence; } /** * 保證返回的毫秒數在引數之後 * * @param lastTimestamp * @return */ private long tilNextMillis(long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; } /** * 獲得系統當前毫秒數 * * @return */ private long timeGen() { return System.currentTimeMillis(); } public static void main(String[] args) throws Exception { IdWorker iw1 = new IdWorker(1); IdWorker iw2 = new IdWorker(2); IdWorker iw3 = new IdWorker(3); // System.out.println(iw1.maxWorkerId); // System.out.println(iw1.sequenceMask); System.out.println("---------------------------"); long workerId = 1L; long twepoch = 1330328109047L; long sequence = 0L;// 0 long workerIdBits = 10L; long maxWorkerId = -1L ^ -1L << workerIdBits;// 1023,1111111111,10位 long sequenceBits = 12L; long workerIdShift = sequenceBits;// 12 long timestampLeftShift = sequenceBits + workerIdBits;// 22 long sequenceMask = -1L ^ -1L << sequenceBits;// 4095,111111111111,12位 long ct = System.currentTimeMillis();// 1330328109047L;// System.out.println(ct); System.out.println(ct - twepoch); System.out.println(ct - twepoch << timestampLeftShift);// 左移22位:*2的22次方 System.out.println(workerId << workerIdShift);// 左移12位:*2的12次方 System.out.println("哈哈"); System.out.println(ct - twepoch << timestampLeftShift | workerId << workerIdShift); long result = ct - twepoch << timestampLeftShift | workerId << workerIdShift | sequence;// 取時間的低40位 | (小於1024:只有12位)的低12位 | 計算的sequence System.out.println(result); System.out.println("---------------"); for (int i = 0; i < 10; i++) { System.out.println("IdWorker 01:"+iw1.nextId()); System.out.println("IdWorker 02:"+iw2.nextId()); System.out.println("IdWorker 03:"+iw3.nextId()); } } }