最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Redis生成分布式全局唯一ID的3種策略

 更新時(shí)間:2025年04月20日 09:21:33   作者:風(fēng)象南  
在分布式系統(tǒng)設(shè)計(jì)中,全局唯一ID是一個(gè)基礎(chǔ)而關(guān)鍵的組件,Redis具備高性能、原子操作及簡(jiǎn)單易用的特性,因此我們可以基于Redis實(shí)現(xiàn)全局唯一ID的生成,下面我們來看看實(shí)現(xiàn)的三種方法吧

在分布式系統(tǒng)設(shè)計(jì)中,全局唯一ID是一個(gè)基礎(chǔ)而關(guān)鍵的組件。隨著業(yè)務(wù)規(guī)模擴(kuò)大和系統(tǒng)架構(gòu)向微服務(wù)演進(jìn),傳統(tǒng)的單機(jī)自增ID已無法滿足需求。高并發(fā)、高可用的分布式ID生成方案成為構(gòu)建可靠分布式系統(tǒng)的必要條件。

Redis具備高性能、原子操作及簡(jiǎn)單易用的特性,因此我們可以基于Redis實(shí)現(xiàn)全局唯一ID的生成。

分布式ID的核心需求

一個(gè)優(yōu)秀的分布式ID生成方案應(yīng)滿足以下要求

  • 全局唯一性:在整個(gè)分布式系統(tǒng)中保證ID不重復(fù)
  • 高性能:能夠快速生成ID,支持高并發(fā)場(chǎng)景
  • 高可用:避免單點(diǎn)故障,確保服務(wù)持續(xù)可用
  • 趨勢(shì)遞增:生成的ID大致呈遞增趨勢(shì),便于數(shù)據(jù)庫索引和分片
  • 安全性(可選) :不包含敏感信息,不易被推測(cè)和偽造

1. 基于INCR命令的簡(jiǎn)單自增ID

原理

這是最直接的Redis分布式ID實(shí)現(xiàn)方式,利用Redis的INCR命令原子性遞增一個(gè)計(jì)數(shù)器,確保在分布式環(huán)境下ID的唯一性。

代碼實(shí)現(xiàn)

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisSimpleIdGenerator {
    private final RedisTemplate<String, String> redisTemplate;
    private final String ID_KEY;
    
    public RedisSimpleIdGenerator(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.ID_KEY = "distributed:id:generator";
    }
    
    /**
     * 生成下一個(gè)ID
     * @return 唯一ID
     */
    public long nextId() {
        Long id = redisTemplate.opsForValue().increment(ID_KEY);
        if (id == null) {
            throw new RuntimeException("Failed to generate id");
        }
        return id;
    }
    
    /**
     * 為指定業(yè)務(wù)生成ID
     * @param bizTag 業(yè)務(wù)標(biāo)簽
     * @return 唯一ID
     */
    public long nextId(String bizTag) {
        String key = ID_KEY + ":" + bizTag;
        Long id = redisTemplate.opsForValue().increment(key);
        if (id == null) {
            throw new RuntimeException("Failed to generate id for " + bizTag);
        }
        return id;
    }
    
    /**
     * 獲取當(dāng)前ID值但不遞增
     * @param bizTag 業(yè)務(wù)標(biāo)簽
     * @return 當(dāng)前ID值
     */
    public long currentId(String bizTag) {
        String key = ID_KEY + ":" + bizTag;
        String value = redisTemplate.opsForValue().get(key);
        return value != null ? Long.parseLong(value) : 0;
    }
}

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 實(shí)現(xiàn)極其簡(jiǎn)單,僅需一次Redis操作
  • ID嚴(yán)格遞增,適合作為數(shù)據(jù)庫主鍵
  • 支持多業(yè)務(wù)ID隔離

缺點(diǎn)

  • Redis單點(diǎn)故障會(huì)導(dǎo)致ID生成服務(wù)不可用
  • 主從切換可能導(dǎo)致ID重復(fù)
  • 無法包含業(yè)務(wù)含義

適用場(chǎng)景

  • 中小規(guī)模系統(tǒng)的自增主鍵生成
  • 對(duì)ID連續(xù)性有要求的業(yè)務(wù)場(chǎng)景
  • 單數(shù)據(jù)中心部署的應(yīng)用

2. 基于Lua腳本的批量ID生成

原理

通過Lua腳本一次性獲取一批ID,減少網(wǎng)絡(luò)往返次數(shù),客戶端可在內(nèi)存中順序分配ID,顯著提高性能。

代碼實(shí)現(xiàn)

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Component
public class RedisBatchIdGenerator {
    private final RedisTemplate<String, String> redisTemplate;
    private final String ID_KEY = "distributed:batch:id";
    private final DefaultRedisScript<Long> batchIncrScript;
    
    // 批量獲取的大小
    private final int BATCH_SIZE = 1000;
    
    // 本地計(jì)數(shù)器和鎖
    private AtomicLong currentId = new AtomicLong(0);
    private AtomicLong endId = new AtomicLong(0);
    private final Lock lock = new ReentrantLock();
    
    public RedisBatchIdGenerator(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
        
        // 創(chuàng)建Lua腳本
        String scriptText = 
            "local key = KEYS[1] " +
            "local step = tonumber(ARGV[1]) " +
            "local currentValue = redis.call('incrby', key, step) " +
            "return currentValue";
        
        this.batchIncrScript = new DefaultRedisScript<>();
        this.batchIncrScript.setScriptText(scriptText);
        this.batchIncrScript.setResultType(Long.class);
    }
    
    /**
     * 獲取下一個(gè)ID
     */
    public long nextId() {
        // 如果當(dāng)前ID超過了分配范圍,則重新獲取一批
        if (currentId.get() >= endId.get()) {
            lock.lock();
            try {
                // 雙重檢查,防止多線程重復(fù)獲取
                if (currentId.get() >= endId.get()) {
                    // 執(zhí)行Lua腳本獲取一批ID
                    Long newEndId = redisTemplate.execute(
                        batchIncrScript, 
                        Collections.singletonList(ID_KEY),
                        String.valueOf(BATCH_SIZE)
                    );
                    
                    if (newEndId == null) {
                        throw new RuntimeException("Failed to generate batch ids");
                    }
                    
                    // 設(shè)置新的ID范圍
                    endId.set(newEndId);
                    currentId.set(newEndId - BATCH_SIZE);
                }
            } finally {
                lock.unlock();
            }
        }
        
        // 分配下一個(gè)ID
        return currentId.incrementAndGet();
    }
    
    /**
     * 為指定業(yè)務(wù)生成ID
     */
    public long nextId(String bizTag) {
        // 實(shí)際項(xiàng)目中應(yīng)該為每個(gè)業(yè)務(wù)標(biāo)簽維護(hù)獨(dú)立的計(jì)數(shù)器和范圍
        // 這里簡(jiǎn)化處理,僅使用不同的Redis key
        String key = ID_KEY + ":" + bizTag;
        
        Long newEndId = redisTemplate.execute(
            batchIncrScript, 
            Collections.singletonList(key),
            String.valueOf(1)
        );
        
        return newEndId != null ? newEndId : -1;
    }
}

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 顯著減少Redis網(wǎng)絡(luò)請(qǐng)求次數(shù)
  • 客戶端緩存ID段,大幅提高性能
  • 降低Redis服務(wù)器壓力
  • 支持突發(fā)流量處理

缺點(diǎn)

  • 實(shí)現(xiàn)復(fù)雜度增加
  • 服務(wù)重啟可能導(dǎo)致ID段浪費(fèi)

適用場(chǎng)景

  • 高并發(fā)系統(tǒng),需要極高ID生成性能的場(chǎng)景
  • 對(duì)ID連續(xù)性要求不嚴(yán)格的業(yè)務(wù)
  • 能容忍小部分ID浪費(fèi)的場(chǎng)景

3. 基于Redis的分段式ID分配(號(hào)段模式)

原理

號(hào)段模式是一種優(yōu)化的批量ID生成方案,通過預(yù)分配號(hào)段(ID范圍)減少服務(wù)間競(jìng)爭(zhēng),同時(shí)引入雙Buffer機(jī)制提高可用性。

代碼實(shí)現(xiàn)

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Component
public class RedisSegmentIdGenerator {
    private final RedisTemplate<String, String> redisTemplate;
    private final String SEGMENT_KEY = "distributed:segment:id";
    private final DefaultRedisScript<Long> segmentScript;
    
    // 號(hào)段大小
    private final int SEGMENT_STEP = 1000;
    // 加載因子,當(dāng)前號(hào)段使用到這個(gè)百分比時(shí)就異步加載下一個(gè)號(hào)段
    private final double LOAD_FACTOR = 0.7;
    
    // 存儲(chǔ)業(yè)務(wù)號(hào)段信息的Map
    private final Map<String, SegmentBuffer> businessSegmentMap = new ConcurrentHashMap<>();
    
    public RedisSegmentIdGenerator(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
        
        // 創(chuàng)建Lua腳本
        String scriptText = 
            "local key = KEYS[1] " +
            "local step = tonumber(ARGV[1]) " +
            "local value = redis.call('incrby', key, step) " +
            "return value";
        
        this.segmentScript = new DefaultRedisScript<>();
        this.segmentScript.setScriptText(scriptText);
        this.segmentScript.setResultType(Long.class);
    }
    
    /**
     * 獲取下一個(gè)ID
     * @param bizTag 業(yè)務(wù)標(biāo)簽
     * @return 唯一ID
     */
    public long nextId(String bizTag) {
        // 獲取或創(chuàng)建號(hào)段緩沖區(qū)
        SegmentBuffer buffer = businessSegmentMap.computeIfAbsent(
            bizTag, k -> new SegmentBuffer(bizTag));
        
        return buffer.nextId();
    }
    
    /**
     * 內(nèi)部號(hào)段緩沖區(qū)類,實(shí)現(xiàn)雙Buffer機(jī)制
     */
    private class SegmentBuffer {
        private String bizTag;
        private Segment[] segments = new Segment[2]; // 雙Buffer
        private volatile int currentPos = 0; // 當(dāng)前使用的segment位置
        private Lock lock = new ReentrantLock();
        private volatile boolean isLoadingNext = false; // 是否正在異步加載下一個(gè)號(hào)段
        
        public SegmentBuffer(String bizTag) {
            this.bizTag = bizTag;
            segments[0] = new Segment(0, 0);
            segments[1] = new Segment(0, 0);
        }
        
        /**
         * 獲取下一個(gè)ID
         */
        public long nextId() {
            // 獲取當(dāng)前號(hào)段
            Segment segment = segments[currentPos];
            
            // 如果當(dāng)前號(hào)段為空或已用完,切換到另一個(gè)號(hào)段
            if (!segment.isInitialized() || segment.getValue() > segment.getMax()) {
                lock.lock();
                try {
                    // 雙重檢查當(dāng)前號(hào)段狀態(tài)
                    segment = segments[currentPos];
                    if (!segment.isInitialized() || segment.getValue() > segment.getMax()) {
                        // 切換到另一個(gè)號(hào)段
                        currentPos = (currentPos + 1) % 2;
                        segment = segments[currentPos];
                        
                        // 如果另一個(gè)號(hào)段也未初始化或已用完,則同步加載
                        if (!segment.isInitialized() || segment.getValue() > segment.getMax()) {
                            loadSegmentFromRedis(segment);
                        }
                    }
                } finally {
                    lock.unlock();
                }
            }
            
            // 檢查是否需要異步加載下一個(gè)號(hào)段
            long value = segment.incrementAndGet();
            if (value > segment.getMin() + (segment.getMax() - segment.getMin()) * LOAD_FACTOR
                    && !isLoadingNext) {
                isLoadingNext = true;
                // 異步加載下一個(gè)號(hào)段
                new Thread(() -> {
                    Segment nextSegment = segments[(currentPos + 1) % 2];
                    loadSegmentFromRedis(nextSegment);
                    isLoadingNext = false;
                }).start();
            }
            
            return value;
        }
        
        /**
         * 從Redis加載號(hào)段
         */
        private void loadSegmentFromRedis(Segment segment) {
            String key = SEGMENT_KEY + ":" + bizTag;
            
            // 執(zhí)行Lua腳本獲取號(hào)段最大值
            Long max = redisTemplate.execute(
                segmentScript, 
                Collections.singletonList(key),
                String.valueOf(SEGMENT_STEP)
            );
            
            if (max == null) {
                throw new RuntimeException("Failed to load segment from Redis");
            }
            
            // 設(shè)置號(hào)段范圍
            long min = max - SEGMENT_STEP + 1;
            segment.setMax(max);
            segment.setMin(min);
            segment.setValue(min - 1); // 設(shè)置為min-1,第一次incrementAndGet返回min
            segment.setInitialized(true);
        }
    }
    
    /**
     * 內(nèi)部號(hào)段類,存儲(chǔ)號(hào)段的范圍信息
     */
    private class Segment {
        private long min; // 最小值
        private long max; // 最大值
        private AtomicLong value; // 當(dāng)前值
        private volatile boolean initialized; // 是否已初始化
        
        public Segment(long min, long max) {
            this.min = min;
            this.max = max;
            this.value = new AtomicLong(min);
            this.initialized = false;
        }
        
        public long getValue() {
            return value.get();
        }
        
        public void setValue(long value) {
            this.value.set(value);
        }
        
        public long incrementAndGet() {
            return value.incrementAndGet();
        }
        
        public long getMin() {
            return min;
        }
        
        public void setMin(long min) {
            this.min = min;
        }
        
        public long getMax() {
            return max;
        }
        
        public void setMax(long max) {
            this.max = max;
        }
        
        public boolean isInitialized() {
            return initialized;
        }
        
        public void setInitialized(boolean initialized) {
            this.initialized = initialized;
        }
    }
}

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 雙Buffer設(shè)計(jì),高可用性
  • 異步加載下一個(gè)號(hào)段,性能更高
  • 大幅降低Redis訪問頻率
  • 即使Redis短暫不可用,仍可分配一段時(shí)間的ID

缺點(diǎn)

  • 實(shí)現(xiàn)復(fù)雜,代碼量大
  • 多實(shí)例部署時(shí),各實(shí)例獲取的號(hào)段不連續(xù)
  • 重啟服務(wù)時(shí)號(hào)段內(nèi)的ID可能浪費(fèi)
  • 需要在內(nèi)存中維護(hù)狀態(tài)

適用場(chǎng)景

  • 對(duì)ID生成可用性要求高的業(yè)務(wù)
  • 需要高性能且多服務(wù)器部署的分布式系統(tǒng)

4. 性能對(duì)比與選型建議

策略性能可用性ID長(zhǎng)度實(shí)現(xiàn)復(fù)雜度單調(diào)遞增
INCR命令★★★☆☆★★☆☆☆遞增整數(shù)嚴(yán)格遞增
Lua批量生成★★★★★★★★☆☆遞增整數(shù)批次內(nèi)遞增
分段式ID★★★★★★★★★☆遞增整數(shù)段內(nèi)遞增

5. 實(shí)踐優(yōu)化技巧

1. Redis高可用配置

// 配置Redis哨兵模式,提高可用性
@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
        .master("mymaster")
        .sentinel("127.0.0.1", 26379)
        .sentinel("127.0.0.1", 26380)
        .sentinel("127.0.0.1", 26381);
    
    return new LettuceConnectionFactory(sentinelConfig);
}

2. ID預(yù)熱策略

// 系統(tǒng)啟動(dòng)時(shí)預(yù)熱ID生成器
@PostConstruct
public void preWarmIdGenerator() {
    // 預(yù)先獲取一批ID,確保系統(tǒng)啟動(dòng)后立即可用
    for (int i = 0; i < 10; i++) {
        try {
            segmentIdGenerator.nextId("order");
            segmentIdGenerator.nextId("user");
            segmentIdGenerator.nextId("payment");
        } catch (Exception e) {
            log.error("Failed to pre-warm ID generator", e);
        }
    }
}

3. 降級(jí)策略

// Redis不可用時(shí)的降級(jí)策略
public long nextIdWithFallback(String bizTag) {
    try {
        return segmentIdGenerator.nextId(bizTag);
    } catch (Exception e) {
        log.warn("Failed to get ID from Redis, using local fallback", e);
        // 使用本地UUID或其他替代方案
        return Math.abs(UUID.randomUUID().getMostSignificantBits());
    }
}

6. 結(jié)論

選擇合適的分布式ID生成策略時(shí),需要綜合考慮系統(tǒng)規(guī)模、性能需求、可靠性要求和實(shí)現(xiàn)復(fù)雜度。無論選擇哪種方案,都應(yīng)注重高可用性設(shè)計(jì),增加監(jiān)控和預(yù)警機(jī)制,確保ID生成服務(wù)的穩(wěn)定運(yùn)行。

在實(shí)踐中,可以基于業(yè)務(wù)需求對(duì)這些方案進(jìn)行組合和優(yōu)化,例如為不同業(yè)務(wù)選擇不同策略,或者在ID中嵌入業(yè)務(wù)標(biāo)識(shí)等,打造更適合自身系統(tǒng)的分布式ID生成解決方案。

到此這篇關(guān)于基于Redis生成分布式全局唯一ID的3種策略的文章就介紹到這了,更多相關(guān)Redis生成分布式全局唯一ID內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring?自定義propertyEditor的示例代碼

    Spring?自定義propertyEditor的示例代碼

    這篇文章主要介紹了Spring?自定義propertyEditor的示例代碼,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-12-12
  • SpringBoot的java -jar命令啟動(dòng)原理解讀

    SpringBoot的java -jar命令啟動(dòng)原理解讀

    這篇文章主要介紹了SpringBoot的java -jar命令啟動(dòng)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • Mybatis傳遞List集合方式

    Mybatis傳遞List集合方式

    MyBatis傳遞List參數(shù)時(shí),若XML變量名不匹配默認(rèn)的"list"鍵,會(huì)報(bào)錯(cuò),需通過@Param指定名稱、使用collection屬性或索引方式正確引用
    2025-07-07
  • Elasticsearch索引結(jié)構(gòu)與算法解析

    Elasticsearch索引結(jié)構(gòu)與算法解析

    ?作為搜索引擎的一部分,ES自然具有速度快、結(jié)果準(zhǔn)確、結(jié)果豐富等特點(diǎn),那么ES是如何達(dá)到“搜索引擎”級(jí)別的查詢效率呢?首先是索引,其次是壓縮算法,接下來我們就一起了解下ES的索引結(jié)構(gòu)和壓縮算法
    2023-04-04
  • Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式

    Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式

    這篇文章主要介紹了Java獲取當(dāng)前時(shí)間并轉(zhuǎn)化為yyyy-MM-dd?HH:mm:ss格式的多種方式,每種方式結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • SSH框架網(wǎng)上商城項(xiàng)目第29戰(zhàn)之使用JsChart技術(shù)顯示商品銷售報(bào)表

    SSH框架網(wǎng)上商城項(xiàng)目第29戰(zhàn)之使用JsChart技術(shù)顯示商品銷售報(bào)表

    這篇文章主要為大家詳細(xì)介紹了SSH框架網(wǎng)上商城項(xiàng)目第29戰(zhàn)之使用JsChart技術(shù)顯示商品銷售報(bào)表,感興趣的小伙伴們可以參考一下
    2016-06-06
  • SpringCloudGateway開發(fā)過程解析

    SpringCloudGateway開發(fā)過程解析

    這篇文章主要介紹了SpringCloudGateway開發(fā)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • MyBatis延遲加載實(shí)現(xiàn)步驟詳解

    MyBatis延遲加載實(shí)現(xiàn)步驟詳解

    這篇文章主要介紹了MyBatis延遲加載實(shí)現(xiàn)步驟詳解,? MyBatis中的延遲加載,也成為懶加載,是指在進(jìn)行關(guān)聯(lián)查詢時(shí),按照設(shè)置的延遲規(guī)則推遲對(duì)關(guān)聯(lián)對(duì)象的查詢,延遲加載可以有效的減少數(shù)據(jù)庫的壓力,需要的朋友可以參考下
    2023-10-10
  • Java中實(shí)現(xiàn)多重排序的幾種方法小結(jié)

    Java中實(shí)現(xiàn)多重排序的幾種方法小結(jié)

    Java中的多重排序通常指的是同時(shí)對(duì)一個(gè)集合中的兩個(gè)或更多列或多維度的數(shù)據(jù)進(jìn)行排序,這通常通過自定義Comparator實(shí)現(xiàn),可以結(jié)合Arrays.sort()或Collections.sort()方法,當(dāng)需要進(jìn)行多重排序時(shí),即根據(jù)多個(gè)字段進(jìn)行排序,我們可以采用以下幾種方法
    2024-10-10
  • 淺談Springmvc中的頁面跳轉(zhuǎn)問題

    淺談Springmvc中的頁面跳轉(zhuǎn)問題

    這篇文章主要介紹了淺談Springmvc中的頁面跳轉(zhuǎn)問題,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-12-12

最新評(píng)論

科技| 金华市| 武强县| 庐江县| 新沂市| 海淀区| 运城市| 牙克石市| 清镇市| 大兴区| 日照市| 汤阴县| 新田县| 岗巴县| 腾冲县| 中宁县| 枣庄市| 化德县| 巩义市| 荔波县| 乌鲁木齐县| 东海县| 兴宁市| 吐鲁番市| 辉南县| 虎林市| 新密市| 三门峡市| 武胜县| 耿马| 乌海市| 延寿县| 右玉县| 元江| 时尚| 新竹市| 剑川县| 龙口市| 延庆县| 奉节县| 靖江市|