使用redis生成分布式全局唯一ID

背景

分布式系統(tǒng)中由于跨進程跨系統(tǒng),在某些場景中,我們需要生成全局的唯一ID,例如訂單系統(tǒng),并發(fā)情況下,不同的系統(tǒng)需要同時生成不一樣的訂單ID方便后續(xù)的訂單下單與查詢等等。

解決

網(wǎng)上有很多解決方法,例如:雪花算法,薄霧算法,利用單臺數(shù)據(jù)庫生成唯一主鍵方法,以及redis生成唯一ID方法,等等

redis生成全局唯一ID的原理

我們生成的訂單號一般需要存在Long類型中,正好Long類型是64位,所以將第一位永遠設置成0,表示正數(shù)。后面31位表示時間戳,可以表示的數(shù)字為2的31次方(0-2147483648),單位秒,再后面的32位可以表示成2的32次方的訂單號(0-4294967296)。這種思想主要是借鑒雪花算法的原理。

全局唯一ID.png

解釋
1.符號位:1bit,永遠為0,表示正數(shù)
2.時間戳:31bit,最大2147483648秒,大概69年
3.序列號:32bit,最大4294967296,表示一秒中內能生成的不同的訂單數(shù)(接近43億)
一般一秒中能產生43億個不一樣的訂單號,基本滿足各種電商場景了。

java代碼實現(xiàn)

@Component
public class RedisIdMaker {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 時間戳開始時間,從2022年1月1號0點0時0分開始
     */
    private static final Long START_TIME = 1640995200L;

    /**
     * 訂單生成數(shù)量  每天最多2的31次方個訂單數(shù)量
     */
    private static final int COUNT_BITS = 32;

    private static final String ORDER_COUNT_KEY = "order:";


    /**
     * 根據(jù)redis生成唯一訂單號
     *
     * @return
     */
    public Long generateNextId() {
        // 獲取當前時間
        LocalDateTime now = LocalDateTime.now();
        long currentStamp = now.toEpochSecond(ZoneOffset.UTC);
        // 獲取當前時間戳(秒)
        long timeStamp = currentStamp - START_TIME;
        // 組裝成key=order:2022:01:01(組裝成這種形式方便日后根據(jù)日期統(tǒng)計當天的訂單數(shù)量)
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
        String redisKey = ORDER_COUNT_KEY + date;
        // 訂單自增長
        long orderCount = stringRedisTemplate.opsForValue().increment(redisKey);
        // 返回唯一訂單號(拼接而來的)
        return timeStamp << COUNT_BITS | orderCount;
    }

    /**
     * 獲取2022年1月1號0點0時0分的時間戳
     * @param args
     */
    public static void main(String[] args) {
        LocalDateTime startLocalTime = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
        long startTime = startLocalTime.toEpochSecond(ZoneOffset.UTC);
        System.out.println(startTime);
        LocalDateTime now = LocalDateTime.now();

        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
        System.out.println(date);
    }
}

解釋
時間戳開始是從1970年開始的,當31位時間戳全部是0的情況下,那么就是最開始,當當31位時間戳全部是1的情況下那么就是2039年,也就是說只能用到2039年

64位表示2022年1月1號0點0時0分.png

圖中后32位表示2022年1月1號0點0時0分的秒時間戳。

timeStamp << COUNT_BITS

這行代碼表示將上圖中的時間戳往前位移32位,就變成了下面的


左移32位后.png

最后就是這段代碼

timeStamp << COUNT_BITS |orderCount

| 是把某兩個數(shù)中, 只要其中一個的某一位為1,則結果的該位就為1。
由于我們現(xiàn)在表示成二進制,只有0和1,所以這樣運算后變成了


第一筆訂單.png

測試

 @Test
 public void test() throws InterruptedException {
        System.out.println(redisIdMaker.generateNextId());
    }

結果

39001021761978369

進制轉換


image.png

批量生成測試

 @Test
 public  void contextLoads() throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(300);
        // 定義任務
        Runnable task = ()->{
            for (int i = 0; i < 100; i++) {
                long id = redisIdMaker.generateNextId();
                System.out.println(id);
            }
            countDownLatch.countDown();
        };
        for (int i = 0; i < 300; i++) {
            es.submit(task);
        }
        countDownLatch.await();
    }

結果:控制臺一共生成了30000條ID


image.png

在看我們的redis,同樣記錄了,這一秒中生成的訂單號數(shù)是30000

image.png

總結

利用redis生成全局唯一ID,其實redis扮演的角色就是一個計數(shù)器的作用,方便后續(xù)的統(tǒng)計。
優(yōu)點:高性能,高并發(fā),唯一性,遞增性,安全性。
缺點:需要依賴redis去實現(xiàn)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容