高並發情況下分布式全局ID


 

 

1、高並發情況下,生成分布式全局id策略
2、利用全球唯一UUID生成訂單號優缺點
3、基於數據庫自增或者序列生成訂單號
4、數據庫集群如何考慮數據庫自增唯一性
5、基於Redis生成生成全局id策略
6、Twitter的Snowflake算法生成全局id
7、基於Zookeeper生成全局id

 

 

高並發情況下,生成分布式全局id策略

1、注意冪等性且全局唯一性
2、注意安全性,不能被猜疑
3、趨勢遞增性

訂單號命名規則:比如“業務編碼 + 時間戳 + 機器編號[前4位] + 隨機4位數 + 毫秒數”。

 

利用全球唯一UUID生成訂單號
UUID基本概念:
UUID是指在一台機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。
UUID組成部分:當前日期和時間+時鍾序列+隨機數+全局唯一的IEEE機器識別號
全局唯一的IEEE機器識別號:如果有網卡,從網卡MAC地址獲得,沒有網卡以其他方式獲得。
UUID優缺點:
優點:
簡單,代碼方便
生成ID性能非常好,基本不會有性能問題
全球唯一,在遇見數據遷移,系統數據合並,或者數據庫變更等情況下,可以從容應對
缺點:
沒有排序,無法保證趨勢遞增
UUID往往是使用字符串存儲,查詢的效率比較低
存儲空間比較大,如果是海量數據庫,就需要考慮存儲量的問題。
傳輸數據量大

 

UUID不需要聯網生成,redis需要。

 

基於數據庫自增方式

實現思路:利用數據庫自增或者序列號方式實現訂單號
注意:在數據庫集群環境下,默認自增方式存在問題,因為都是從1開始自增,可能會存在重復,應該設置每台不同數據庫自增的間隔方式不同。
優點:
簡單,代碼方便,性能可以接受。
數字ID天然排序,對分頁或者需要排序的結果很有幫助。
缺點:
不同數據庫語法和實現不同,數據庫遷移的時候或多數據庫版本支持的時候需要處理。
在性能達不到要求的情況下,比較難於擴展。
在單個數據庫或讀寫分離或一主多從的情況下,只有一個主庫可以生成。有單點故障的風險。
分表分庫的時候會有麻煩。

 

數據庫集群如何考慮數據庫自增唯一性

在數據庫集群環境下,默認自增方式存在問題,因為都是從1開始自增,可能會存在重復,應該設置每台節點自增步長不同。
查詢自增的步長
SHOW VARIABLES LIKE 'auto_inc%'
修改自增的步長
SET @@auto_increment_increment=10;    
修改起始值
SET @@auto_increment_offset=5;
假設有兩台mysql數據庫服務器
節點①自增 1 3 5 7 9 11 ….
節點②自增 2 4 6 8 10 12 ….
注意:在最開始設置好了每台節點自增方式步長后,確定好了mysql集群數量后,無法擴展新的mysql,不然生成步長的規則可能會發生變化。

 

MySQL1    1 2 3

MySQL2    1 2 3

  方法1   讀寫分離

  方法2  設置自增步長        需要提前設置好步長  否則如果新增一台MySQL就麻煩了

             如果想提高擴展性 采用UUID方式作為主鍵

 

 

基於Redis生成生成全局id策略
因為Redis是單線的,天生保證原子性,可以使用Redis的原子操作 INCR和INCRBY來實現
優點:
不依賴於數據庫,靈活方便,且性能優於數據庫。
數字ID天然排序,對分頁或者需要排序的結果很有幫助。
缺點:
如果系統中沒有Redis,還需要引入新的組件,增加系統復雜度。
需要編碼和配置的工作量比較大。
注意:在Redis集群情況下,同樣和Redis一樣需要設置不同的增長步長,同時key一定要設置有效期
可以使用Redis集群來獲取更高的吞吐量。假如一個集群中有5台Redis。可以初始化每台Redis的值分別是1,2,3,4,5,然后步長都是5。各個Redis生成的ID為:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25


比較適合使用Redis來生成每天從0開始的流水號。比如訂單號=日期+當日自增長號。可以每天在Redis中生成一個Key,使用INCR進行累加。

如果生成的訂單號超過自增增長的話,可以采用前綴+自增+並且設置有效期

 

 

當前日期-5位自增

統一時間 最多生成10w-1個不重復的

 

假設雙十一 每秒99w訂單

   

package com.toov5.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {
    @Autowired
    private RedisTemplate redisTemplate;
    
   @RequestMapping("/order")    
   public Long order(String key) {
       
       RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory() );
       long andIncrement = redisAtomicLong.getAndIncrement();
       return andIncrement;
       
   }
}

 補零:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {
    @Autowired
    private RedisTemplate redisTemplate;
    
   @RequestMapping("/order")    
   public String order(String key) {
       
       RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory() );
       long increment = redisAtomicLong.getAndIncrement();
       String id = String.format("%1$05d", increment);  //5位數
       return id;
       
   }
}

 

 

 加上前綴:

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {
    @Autowired
    private RedisTemplate redisTemplate;
    
   @RequestMapping("/order")    
   public String order(String key) {
       
       RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory() );
       long increment = redisAtomicLong.getAndIncrement();
       String id =prefix()+"-"+String.format("%1$05d", increment);  //5位數
       return id;
       
   }
   
   public static String prefix() {
        String temp_str = "";
        Date dt = new Date();
        // 最后的aa表示“上午”或“下午” HH表示24小時制 如果換成hh表示12小時制
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        temp_str = sdf.format(dt);
        return temp_str;
    }
}

 

   

 

 

 Redis如果做集群,會產生重復的問題!
   

@RequestMapping("/order1")
    public String order1(String key) {
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        // // 起始值
        // redisAtomicLong.set(10);
        // 設置步長加10!!!!
        redisAtomicLong.addAndGet(9);
        return redisAtomicLong.incrementAndGet() + "";
    }

 

 redis 的key的失效時間問題!

   24h  第二天時間變了 不會重復了哦

 

 

Twitter的snowflake(雪花)算法  (跟UUID一樣不用聯網)
snowflake是Twitter開源的分布式ID生成算法,結果是一個long型的ID。其核心思想是:
高位隨機+毫秒數+機器碼(數據中心+機器id)+10位的流水號碼
Github地址: https://github.com/twitter-archive/snowflake
Snowflake 原理:
snowflake生產的ID是一個18位的long型數字,二進制結構表示如下(每部分用-分開):
0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000
第一位未使用,接下來的41位為毫秒級時間(41位的長度可以使用69年,從1970-01-01 08:00:00),然后是5位datacenterId(最大支持2^5=32個,二進制表示從00000-11111,也即是十進制0-31),和5位workerId(最大支持2^5=32個,原理同datacenterId),所以datacenterId*workerId最多支持部署1024個節點,最后12位是毫秒內的計數(12位的計數順序號支持每個節點每毫秒產生2^12=4096個ID序號).所有位數加起來共64位,恰好是一個Long型(轉換為字符串長度為18).單台機器實例,通過時間戳保證前41位是唯一的,分布式系統多台機器實例下,通過對每個機器實例分配不同的datacenterId和workerId避免中間的10位碰撞。最后12位每毫秒從0遞增生產ID,再提一次:每毫秒最多生成4096個ID,每秒可達4096000個。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM