1. 程式人生 > >分庫分表之後,id 主鍵如何處理?

分庫分表之後,id 主鍵如何處理?

面試題

分庫分表之後,id 主鍵如何處理?

面試官心理分析

其實這是分庫分表之後你必然要面對的一個問題,就是 id 咋生成?因為要是分成多個表之後,每個表都是從 1 開始累加,那肯定不對啊,需要一個全域性唯一的 id 來支援。所以這都是你實際生產環境中必須考慮的問題。

面試題剖析

基於資料庫的實現方案

資料庫自增 id

這個就是說你的系統裡每次得到一個 id,都是往一個庫的一個表裡插入一條沒什麼業務含義的資料,然後獲取一個數據庫自增的一個 id。拿到這個 id 之後再往對應的分庫分表裡去寫入。

這個方案的好處就是方便簡單,誰都會用;缺點就是單庫生成自增 id,要是高併發的話,就會有瓶頸的;如果你硬是要改進一下,那麼就專門開一個服務出來,這個服務每次就拿到當前 id 最大值,然後自己遞增幾個 id,一次性返回一批 id,然後再把當前最大 id 值修改成遞增幾個 id 之後的一個值;但是無論如何都是基於單個數據庫。

適合的場景:你分庫分表就倆原因,要不就是單庫併發太高,要不就是單庫資料量太大;除非是你併發不高,但是資料量太大導致的分庫分表擴容,你可以用這個方案,因為可能每秒最高併發最多就幾百,那麼就走單獨的一個庫和表生成自增主鍵即可。

設定資料庫 sequence 或者表自增欄位步長

可以通過設定資料庫 sequence 或者表的自增欄位步長來進行水平伸縮。

比如說,現在有 8 個服務節點,每個服務節點使用一個 sequence 功能來產生 ID,每個 sequence 的起始 ID 不同,並且依次遞增,步長都是 8。

適合的場景:在使用者防止產生的 ID 重複時,這種方案實現起來比較簡單,也能達到效能目標。但是服務節點固定,步長也固定,將來如果還要增加服務節點,就不好搞了。

UUID

好處就是本地生成,不要基於資料庫來了;不好之處就是,UUID 太長了、佔用空間大,作為主鍵效能太差了;更重要的是,UUID 不具有有序性,會導致 B+ 樹索引在寫的時候有過多的隨機寫操作(連續的 ID 可以產生部分順序寫),還有,由於在寫的時候不能產生有順序的 append 操作,而需要進行 insert 操作,將會讀取整個 B+ 樹節點到記憶體,在插入這條記錄後會將整個節點寫回磁碟,這種操作在記錄佔用空間比較大的情況下,效能下降明顯。

適合的場景:如果你是要隨機生成個什麼檔名、編號之類的,你可以用 UUID,但是作為主鍵是不能用 UUID 的。

UUID.randomUUID().toString().replace(“-”, “”) -> sfsdf23423rr234sfdaf

獲取系統當前時間

這個就是獲取當前時間即可,但是問題是,併發很高的時候,比如一秒併發幾千,會有重複的情況,這個是肯定不合適的。基本就不用考慮了。

適合的場景:一般如果用這個方案,是將當前時間跟很多其他的業務欄位拼接起來,作為一個 id,如果業務上你覺得可以接受,那麼也是可以的。你可以將別的業務欄位值跟當前時間拼接起來,組成一個全域性唯一的編號。

snowflake 演算法

snowflake 演算法是 twitter 開源的分散式 id 生成演算法,採用 Scala 語言實現,是把一個 64 位的 long 型的 id,1 個 bit 是不用的,用其中的 41 bit 作為毫秒數,用 10 bit 作為工作機器 id,12 bit 作為序列號。

  • 1 bit:不用,為啥呢?因為二進位制裡第一個 bit 為如果是 1,那麼都是負數,但是我們生成的 id 都是正數,所以第一個 bit 統一都是 0。
  • 41 bit:表示的是時間戳,單位是毫秒。41 bit 可以表示的數字多達 2^41 - 1,也就是可以標識 2^41 - 1 個毫秒值,換算成年就是表示69年的時間。
  • 10 bit:記錄工作機器 id,代表的是這個服務最多可以部署在 2^10臺機器上哪,也就是1024臺機器。但是 10 bit 裡 5 個 bit 代表機房 id,5 個 bit 代表機器 id。意思就是最多代表 2^5個機房(32個機房),每個機房裡可以代表 2^5 個機器(32臺機器)。
  • 12 bit:這個是用來記錄同一個毫秒內產生的不同 id,12 bit 可以代表的最大正整數是 2^12 - 1 = 4096,也就是說可以用這個 12 bit 代表的數字來區分同一個毫秒內的 4096 個不同的 id。
0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000
public class IdWorker {

    private long workerId;
    private long datacenterId;
    private long sequence;

    public IdWorker(long workerId, long datacenterId, long sequence) {
        // sanity check for workerId
        // 這兒不就檢查了一下,要求就是你傳遞進來的機房id和機器id不能超過32,不能小於0
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(
                    String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        System.out.printf(
                "worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
                timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);

        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    private long twepoch = 1288834974657L;

    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;

    // 這個是二進位制運算,就是 5 bit最多隻能有31個數字,也就是說機器id最多隻能是32以內
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);

    // 這個是一個意思,就是 5 bit最多隻能有31個數字,機房id最多隻能是32以內
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private long sequenceBits = 12L;

    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long lastTimestamp = -1L;

    public long getWorkerId() {
        return workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }

    public long getTimestamp() {
        return System.currentTimeMillis();
    }

    public synchronized long nextId() {
        // 這兒就是獲取當前時間戳,單位是毫秒
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            // 這個意思是說一個毫秒內最多隻能有4096個數字
            // 無論你傳遞多少進來,這個位運算保證始終就是在4096這個範圍內,避免你自己傳遞個sequence超過了4096這個範圍
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        // 這兒記錄一下最近一次生成id的時間戳,單位是毫秒
        lastTimestamp = timestamp;

        // 這兒就是將時間戳左移,放到 41 bit那兒;
        // 將機房 id左移放到 5 bit那兒;
        // 將機器id左移放到5 bit那兒;將序號放最後12 bit;
        // 最後拼接起來成一個 64 bit的二進位制數字,轉換成 10 進位制就是個 long 型
        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    // ---------------測試---------------
    public static void main(String[] args) {
        IdWorker worker = new IdWorker(1, 1, 1);
        for (int i = 0; i < 30; i++) {
            System.out.println(worker.nextId());
        }
    }

}

怎麼說呢,大概這個意思吧,就是說 41 bit 是當前毫秒單位的一個時間戳,就這意思;然後 5 bit 是你傳遞進來的一個機房 id(但是最大隻能是 32 以內),另外 5 bit 是你傳遞進來的機器 id(但是最大隻能是 32 以內),剩下的那個 12 bit序列號,就是如果跟你上次生成 id 的時間還在一個毫秒內,那麼會把順序給你累加,最多在 4096 個序號以內。

所以你自己利用這個工具類,自己搞一個服務,然後對每個機房的每個機器都初始化這麼一個東西,剛開始這個機房的這個機器的序號就是 0。然後每次接收到一個請求,說這個機房的這個機器要生成一個 id,你就找到對應的 Worker 生成。

利用這個 snowflake 演算法,你可以開發自己公司的服務,甚至對於機房 id 和機器 id,反正給你預留了 5 bit + 5 bit,你換成別的有業務含義的東西也可以的。

這個 snowflake 演算法相對來說還是比較靠譜的,所以你要真是搞分散式 id 生成,如果是高併發啥的,那麼用這個應該效能比較好,一般每秒幾萬併發的場景,也足夠你用了。

本文在米兜公眾號連結:
https://mp.weixin.qq.com/s/mt8bVpM57SsI-nvTRKxSKg

歡迎關注米兜Java,一個注在共享、交流的Java學習平臺。

相關推薦

分庫之後id 如何處理

面試題 分庫分表之後,id 主鍵如何處理? 面試官心理分析 其實這是分庫分表之後你必然要面對的一個問題,就是 id 咋生成?因為要是分成多個表之後,每個表都是從 1 開始累加,那肯定不對啊,需要一個全域性唯一的 id 來支援。所以這都是你實際生產環境中必須考慮的問題。 面試題剖析 基於資料庫的實現方案 資

分庫之後全域性id怎麼生成

  資料庫自增id:   這個就是說你的系統裡每次得到一個id,都是往一個庫的一個表裡插入一條沒什麼業務含義的資料,然後獲取一個數據庫自增的一個id。拿到這個id之後再往對應的分庫分表裡去寫入。   這個方案的好處就是方便簡單;缺點就是單庫生成自增id,要是高併發的話,就會有瓶頸的;   適

45、分庫之後全域性id咋生成?

1、面試題 分庫分表之後,id主鍵如何處理? 2、面試官心裡分析 其實這是分庫分表之後你必然要面對的一個問題,就是id咋生成?因為要是分成多個表之後,每個表都是從1開始累加,那肯定不對啊,需要一個全域性唯一的id來支援。所以這都是你實際生產環境中必須考慮的問題。 3、面試題剖析

基於噹噹開源shardingjdbc的分庫的配置-單分庫策略配置

由於最近在做公司各種中介軟體的替換方案調研,其中就有調研到噹噹已經開源的shardingjdbc。 今天搞了一天,從一頭霧水到慢慢的撥開了迷霧。 最搞的是,分庫分表規則的配置,結合我們自己的實際使用場景,分庫分表規則採用的是單一主鍵,並不是gitgub上提供的demo,採

MySQL 分庫方案總結的非常好!

導致 一個 磁盤空間 所有 bsp 功能 編程 從庫 框架 前言 公司最近在搞服務分離,數據切分方面的東西,因為單張包裹表的數據量實在是太大,並且還在以每天60W的量增長。 之前了解過數據庫的分庫分表,讀過幾篇博文,但就只知道個模糊概念, 而且現在回想起來什麽都是模模糊糊的

分庫分割槽讀寫分離

一、分割槽的概念         資料分割槽是一種物理資料庫的設計技術,它的目的是為了在特定的SQL操作中減少資料讀寫的總量以縮減響應時間。         分割槽並不是生成新的資料表,而是將表的資料均衡分攤到不同的硬碟,系統或是不同伺服器儲存介子中,實際上還是一張表

資料庫分庫如何部署上線?

1. 引言 我們先來講一個段子 面試官:“有併發的經驗沒?” 應聘者:“有一點。” 面試官:“那你們為了處理併發,做了哪些優化?” 應聘者:“前後端分離啊,限流啊,分庫分表啊。。” 面試官:"談談分庫分表吧?" 應聘者:“bala。bala。bala

獲取oracle 表字段以及之類等等的資訊。

獲取表名: Oracle的user_talbes用於記錄了使用者表資訊。 select * from user_tables 獲取某個表的欄位: USER_TAB_COLS中記錄了使用者表的列資訊。下面是別人寫的: SELECT USER_TAB_COLS.TABLE_

超實用的mysql分庫策略輕鬆解決億級資料問題

    一、分庫分表的背景 在資料爆炸的年代,單表資料達到千萬級別,甚至過億的量,都是很常見的情景。這時候再對資料庫進行操作就是非常吃力的事情了,select個半天都出不來資料,這時候業務已經難以維繫。不得已,分庫分表提上日程,我們的目的很簡單,減小資料庫的壓力,縮短表的操作時間。 &n

數據庫分庫中間件 Sharding-JDBC 源碼分析 —— 布式

java 後端 架構 數據庫 中間件關註**微信公眾號:【芋道源碼】**有福利:RocketMQ / MyCAT / Sharding-JDBC 所有源碼分析文章列表RocketMQ / MyCAT / Sharding-JDBC 中文註釋源碼 GitHub 地址您對於源碼的疑問每條留言都將得到認真回復。甚至

MySQL主從(MySQL proxy Lua讀寫分離設置多從同步配置分庫方案)

否則 count user username 2個 ons 基礎 zxvf 路徑 Mysql Proxy Lua讀寫分離設置一.讀寫分離說明讀寫分離(Read/Write Splitting),基本的原理是讓主數據庫處理事務性增、改、刪操作(INSERT、UPDATE、DE

資料庫分庫分表(sharding)系列(三) 關於使用框架還是自主開發以及sharding實現層面的考量 資料庫分庫分表(sharding)系列(二) 全域性主鍵生成策略 資料庫分庫分表(sharding)系列(一) 拆分實施策略和示例演示

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Mysql叢集和一多從之後如何分庫的方案實現(三)

4-3、使用MyCat配置橫向拆分 之前文章中我們介紹瞭如何使用MyCat進行讀寫分離,類似的關係型資料庫的讀寫分離儲存方案可以在保持上層業務系統透明度的基礎上滿足70%業務系統的資料承載規模要求和效能要求。比起單純使用LVS + Replicaion的讀寫分離方案而言最大的優勢在於更能增加對上層業務系

資料庫分庫中介軟體 Sharding-JDBC 原始碼分析 —— 分散式

������關注微信公眾號:【芋道原始碼】有福利: 1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表 2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 Gi

mysql分庫時的生成策略

傳送門:http://blog.csdn.net/bluishglc/article/details/7710738#java請各位參考上面地址.這種主鍵生成方式,相對於twritter的生成方式,好處在於,id的值是可以控制的,你想讓它生成什麼樣子的id都可以實現.這裡貼一

資料庫分庫(sharding)系列(二) 全域性生成策略

第一部分:一些常見的主鍵生成策略一旦資料庫被切分到多個物理結點上,我們將不能再依賴資料庫自身的主鍵生成機制。一方面,某個分割槽資料庫自生成的ID無法保證在全域性上是唯一的;另一方面,應用程式在插入資料之前需要先獲得ID,以便進行SQL路由。目前幾種可行的主鍵生成策略有:1.

分庫(sharding)後全域性唯一性的解決方案

隨著資料量的增大,在資料庫的擴充套件上通常遇到切分時保證鍵值的唯一性問題,遇到這種情況,通常有如下幾種相對簡單的解決方案: 1  UUID 這種方案的優點是實現和管理簡單,缺點是佔用空間大,查詢效率低下。 2  Sequence Number 優點是實現和管理簡單,確定是有效

復制一條數據其他值相同

例子 cnblogs 實現 主鍵 nbsp src where code 說明 越深入的學習,越覺得一種知識的深厚底蘊。   平時使用SQL語言中的Insert的方式是:insert into Table value()。今天使用SQL的Insert語句時。 實現一個功能,

MySQL 使用自增ID和UUID 作為主的優劣比較具體過程(從百萬到千萬記錄測試)

popu tis pack 方案 表數據 lock 進行 args ios ?測試緣由? 一個開發同事做了一個框架。裏面主鍵是uuid。我跟他建議說mysql不要用uuid用自增主鍵,自增主鍵效率高,他說不一定高,我說inn

數據設計分庫

倉庫 聚集 AD 記錄 事務處理 功能 出了 cti 規模 當今的數據處理大致可以分成兩大類:聯機事務處理OLTP(On-Line Transaction Processing)、聯機分析處理OLAP(On-Line Analytical Processing)。OLTP是