4、如何生成分布式ID?

目錄

1、分布式ID介紹

什么是 ID?

什么是分布式 ID?

分布式 ID 需要滿足哪些要求?

2、分布式 ID 常見解決方案

1、數據庫

示例使用2:

2、數據庫號段模式

使用示例2:

一、核心設計思路

二、實現代碼

1. 數據庫表設計(MySQL)

2. 實體類(IdGeneratorDO)

3. Mapper 接口(IdGeneratorMapper)

4. Mapper XML 實現(IdGeneratorMapper.xml)

5. ID 生成器核心類(IdGenerator)

三、使用示例

3、NoSQL

使用示例2:

一、核心設計思路

二、實現代碼

1. 依賴配置(Spring Boot)

2. Redis 分布式 ID 生成器實現

3. 使用示例

三、核心優勢與注意事項

優勢

注意事項

四、擴展優化

五、適用場景

4、算法

4.1、UUID

4.2、Snowflake(雪花算法)

4.3 開源框架

UidGenerator(百度)

Leaf(美團)

Tinyid(滴滴)

IdGenerator(個人)

總結

1、數據庫

2、算法

3、開源框架



1、分布式ID介紹


什么是 ID?


日常開發中,我們需要對系統中的各種數據使用 ID 唯一表示,比如用戶 ID 對應且僅對應一個人,商品 ID 對應且僅對應一件商品,訂單 ID 對3應且僅對應一個訂單。
我們現實生活中也有各種 ID,比如身份證 ID 對應且僅對應一個人、地址 ID 對應且僅對應一個地址。
簡單來說,ID 就是數據的唯一標識。


什么是分布式 ID?


分布式 ID 是分布式系統下的 ID。分布式 ID 不存在與現實生活中,屬于計算機系統中的一個概念。
我簡單舉一個分庫分表的例子。
比如有一個項目,使用的是單機 MySQL 。但是,沒想到的是,項目上線一個月之后,隨著使用人數越來越多,整個系統的數據量將越來越大。單機 MySQL 已經沒辦法支撐了,需要進行分庫分表(推薦 Sharding-JDBC)。
在分庫之后, 數據遍布在不同服務器上的數據庫,數據庫的自增主鍵已經沒辦法滿足生成的主鍵唯一了。我們如何為不同的數據節點生成全局唯一主鍵呢?
這個時候就需要生成分布式 ID了。


分布式 ID 需要滿足哪些要求?


分布式 ID 作為分布式系統中必不可少的一環,很多地方都要用到分布式 ID。
一個最基本的分布式 ID 需要滿足下面這些要求:
????????●全局唯一:ID 的全局唯一性肯定是首先要滿足的!
????????●高性能:分布式 ID 的生成速度要快,對本地資源消耗要小。
????????●高可用:生成分布式 ID 的服務要保證可用性無限接近于 100%。
????????●方便易用:拿來即用,使用方便,快速接入!

除了這些之外,一個比較好的分布式 ID 還應保證:
????????●安全:ID 中不包含敏感信息。
????????●有序遞增:如果要把 ID 存放在數據庫的話,ID 的有序性可以提升數據庫寫入速度。并且,很多時候 ,我們還很有可能會直接通過 ID 來進行排序。
????????●有具體的業務含義:生成的 ID 如果能有具體的業務含義,可以讓定位問題以及開發更透明化(通過 ID 就能確定是哪個業務)。
????????●獨立部署:也就是分布式系統單獨有一個發號器服務,專門用來生成分布式 ID。這樣就生成 ID 的服務可以和業務相關的服務解耦。不過,這樣同樣帶來了網絡調用消耗增加的問題。總的來說,如果需要用到分布式 ID 的場景比較多的話,獨立部署的發號器服務還是很有必要的。

2、分布式 ID 常見解決方案

1、數據庫


數據庫主鍵自增


這種方式就比較簡單直白了,就是通過關系型數據庫的自增主鍵產生來唯一的 ID。


以 MySQL 舉例,我們通過下面的方式即可。
1.創建一個數據庫表。

CREATE TABLE `sequence_id` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`stub` char(10) NOT NULL DEFAULT '',PRIMARY KEY (`id`),UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


stub 字段無意義,只是為了占位,便于我們插入或者修改數據。并且,給 stub 字段創建了唯一索引,保證其唯一性。
2.通過 replace into 來插入數據。


BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub');
SELECT LAST_INSERT_ID();
COMMIT;


插入數據這里,我們沒有使用 insert into 而是使用 replace into 來插入數據,具體步驟是這樣的:
????????●第一步:嘗試把數據插入到表中。
????????●第二步:如果主鍵或唯一索引字段出現重復數據錯誤而插入失敗時,先從表中刪除含有重復關鍵字值的沖突行,然后再次嘗試把數據插入到表中。
這種方式的優缺點也比較明顯:
????????●優點:實現起來比較簡單、ID 有序遞增、存儲消耗空間小
????????●缺點:支持的并發量不大、存在數據庫單點問題(可以使用數據庫集群解決,不過增加了復雜度)、ID 沒有具體業務含義、安全問題(比如根據訂單 ID 的遞增規律就能推算出每天的訂單量,商業機密啊! )、每次獲取 ID 都要訪問一次數據庫(增加了對數據庫的壓力,獲取速度也慢)

解決上面的問題:
設置起始值和自增步長

MySQL_1 配置:


set @@auto increment offset =1;    -- 起始值
set @@auto increment increment=2;  -- 步長


MySQL_2 配置:


set @@auto increment offset =2;    -- 起始值
set @@auto increment increment=2;  -- 步長



這樣兩個MySQL實例的自增ID分別就是:

1、3、5、7、9
2、4、6、8、10
但是如果兩個還是無法滿足咋辦呢?增加第三臺MySQL實例需要人工修改一、二兩臺MySQL實例的起始值和步長,把第三臺機器的ID起始生成位置設定在比現有最大自增ID的位置遠一些,但必須在一、二兩臺MySQL實例ID還沒有增長到第三臺MySQL實例的起始ID值的時候,否則自增ID就要出現重復了,必要時可能還需要停機修改。

優點:解決DB單點問題
缺點:不利于后續擴容,而且實際上單個數據庫自身壓力還是大,依舊無法滿足高并發場景。

示例使用2:

當我們需要一個ID的時候,向表中插入一條記錄返回主鍵ID

CREATE DATABASE `SEQ_ID`;
CREATE TABLE SEQID.SEQUENCE_ID (id bigint(20) unsigned NOT NULL auto_increment, value char(10) NOT NULL default '',PRIMARY KEY (id),
) ENGINE=MyISAM;
insert into SEQUENCE_ID(value)  VALUES ('values');


2、數據庫號段模式


數據庫主鍵自增這種模式,每次獲取 ID 都要訪問一次數據庫,ID 需求比較大的時候,肯定是不行的。
如果我們可以批量獲取,然后存在在內存里面,需要用到的時候,直接從內存里面拿就舒服了!這也就是我們說的 基于數據庫的號段模式來生成分布式 ID
數據庫的號段模式也是目前比較主流的一種分布式 ID 生成方式。像滴滴開源的Tinyid 就是基于這種方式來做的。不過,TinyId 使用了雙號段緩存、增加多 db 支持等方式來進一步優化。
以 MySQL 舉例,我們通過下面的方式即可。
1. 創建一個數據庫表。


CREATE TABLE `sequence_id_generator` (`id` int(10) NOT NULL,`current_max_id` bigint(20) NOT NULL COMMENT '當前最大id',`step` int(10) NOT NULL COMMENT '號段的長度',`version` int(20) NOT NULL COMMENT '版本號',`biz_type`    int(20) NOT NULL COMMENT '業務類型',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


current_max_id 字段和step字段主要用于獲取批量 ID,獲取的批量 id 為:current_max_id ~ current_max_id+step。


version 字段主要用于解決并發問題(樂觀鎖),biz_type 主要用于表示業務類型。
2. 先插入一行數據。

INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES(1, 0, 100, 0, 101);


3. 通過 SELECT 獲取指定業務下的批量唯一 ID


SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101


結果:


id current_max_id step version biz_type
1 0 100 0 101


4. 不夠用的話,更新之后重新 SELECT 即可。

UPDATE sequence_id_generator SET current_max_id = 0+100, version=version+1 WHERE version = 0  AND `biz_type` = 101;
SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101


結果:

id current_max_id step version biz_type
1 100 100 1 101


相比于數據庫主鍵自增的方式,數據庫的號段模式對于數據庫的訪問次數更少,數據庫壓力更小。
另外,為了避免單點問題,你可以從使用主從模式來提高可用性。
數據庫號段模式的優缺點:
????????●優點:ID 有序遞增、存儲消耗空間小
????????●缺點:存在數據庫單點問題(可以使用數據庫集群解決,不過增加了復雜度)、ID 沒有具體業務含義、安全問題(比如根據訂單 ID 的遞增規律就能推算出每天的訂單量,商業機密啊! )

使用示例2:

基于數據庫的號段模式是生成分布式 ID 的常用方案,其核心思想是從數據庫批量獲取一段連續的 ID(號段),在本地內存中分配使用,減少數據庫訪問次數。以下是一個完整的實現示例:

一、核心設計思路
  1. 數據庫表:存儲每個業務的號段信息(當前最大 ID、號段步長)。
  2. 號段獲取:當本地號段用盡時,從數據庫申請下一段 ID(通過UPDATE加鎖保證并發安全)。
  3. 本地分配:在內存中維護當前號段的起始 ID 和結束 ID,逐次遞增分配,直到用盡后再次申請。
二、實現代碼
1. 數據庫表設計(MySQL)
CREATE TABLE `id_generator` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`biz_type` varchar(50) NOT NULL COMMENT '業務類型(如order、user)',`max_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '當前最大ID',`step` int(11) NOT NULL DEFAULT 1000 COMMENT '號段步長(每次申請的ID數量)',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',PRIMARY KEY (`id`),UNIQUE KEY `uk_biz_type` (`biz_type`) COMMENT '業務類型唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '分布式ID生成器';-- 初始化業務號段(示例:訂單業務)
INSERT INTO `id_generator` (`biz_type`, `max_id`, `step`) VALUES ('order', 0, 1000);
2. 實體類(IdGeneratorDO)
import lombok.Data;import java.util.Date;@Data
public class IdGeneratorDO {private Long id;private String bizType; // 業務類型private Long maxId;     // 當前最大IDprivate Integer step;   // 號段步長private Date updateTime; // 更新時間
}
3. Mapper 接口(IdGeneratorMapper)
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;public interface IdGeneratorMapper extends BaseMapper<IdGeneratorDO> {/*** 批量獲取號段(核心方法:通過UPDATE加鎖,保證并發安全)* @param bizType 業務類型* @param step 號段步長* @return 受影響的行數(1表示成功,0表示失敗)*/int updateMaxId(@Param("bizType") String bizType, @Param("step") int step);/*** 獲取當前業務的號段信息*/IdGeneratorDO selectByBizType(@Param("bizType") String bizType);
}
4. Mapper XML 實現(IdGeneratorMapper.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.IdGeneratorMapper"><!-- 更新max_id,獲取下一個號段 --><update id="updateMaxId">UPDATE id_generatorSET max_id = max_id + #{step},update_time = NOW()WHERE biz_type = #{bizType}</update><!-- 查詢業務對應的號段信息 --><select id="selectByBizType" resultType="com.example.entity.IdGeneratorDO">SELECT id, biz_type, max_id, step, update_timeFROM id_generatorWHERE biz_type = #{bizType}</select>
</mapper>
5. ID 生成器核心類(IdGenerator)
import cn.hutool.core.util.StrUtil;
import com.example.entity.IdGeneratorDO;
import com.example.mapper.IdGeneratorMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;/*** 基于數據庫號段模式的分布式ID生成器*/
@Component
public class IdGenerator {@Autowiredprivate IdGeneratorMapper idGeneratorMapper;/*** 本地緩存:存儲每個業務的當前號段信息* key: 業務類型(bizType)* value: 號段對象(Section)*/private final ConcurrentHashMap<String, Section> sectionCache = new ConcurrentHashMap<>();/*** 獲取下一個ID* @param bizType 業務類型(如"order")* @return 分布式ID*/public Long nextId(String bizType) {if (StrUtil.isEmpty(bizType)) {throw new IllegalArgumentException("業務類型不能為空");}// 從本地緩存獲取號段,若不存在或已用盡,則申請新號段Section section = sectionCache.get(bizType);if (section == null || section.is用盡()) {// 加鎖防止并發申請號段ReentrantLock lock = new ReentrantLock();lock.lock();try {// 雙重檢查:避免重復申請section = sectionCache.get(bizType);if (section == null || section.is用盡()) {section = applyNewSection(bizType); // 從數據庫申請新號段sectionCache.put(bizType, section);}} finally {lock.unlock();}}// 從當前號段獲取下一個IDreturn section.nextId();}/*** 從數據庫申請新號段*/private Section applyNewSection(String bizType) {// 1. 查詢該業務的當前號段配置IdGeneratorDO config = idGeneratorMapper.selectByBizType(bizType);if (config == null) {throw new RuntimeException("業務類型[" + bizType + "]未配置號段信息");}int step = config.getStep();// 2. 申請新號段:通過UPDATE將max_id增加step(數據庫層面加鎖,保證并發安全)int updateRows = idGeneratorMapper.updateMaxId(bizType, step);if (updateRows != 1) {// 并發沖突時重試(最多重試3次)for (int i = 0; i < 3; i++) {updateRows = idGeneratorMapper.updateMaxId(bizType, step);if (updateRows == 1) {break;}try {Thread.sleep(10); // 短暫休眠后重試} catch (InterruptedException e) {Thread.currentThread().interrupt();}}if (updateRows != 1) {throw new RuntimeException("申請號段失敗,業務類型:" + bizType);}}// 3. 重新查詢最新的max_id(申請后的值)IdGeneratorDO newConfig = idGeneratorMapper.selectByBizType(bizType);long newMaxId = newConfig.getMaxId();// 號段范圍:[newMaxId - step + 1, newMaxId]long startId = newMaxId - step + 1;long endId = newMaxId;return new Section(startId, endId);}/*** 號段內部類:維護當前號段的起始ID、結束ID和當前ID*/private static class Section {private final long startId; // 號段起始IDprivate final long endId;   // 號段結束IDprivate long currentId;     // 當前分配到的IDpublic Section(long startId, long endId) {this.startId = startId;this.endId = endId;this.currentId = startId - 1; // 初始化為起始ID的前一個}/*** 獲取下一個ID*/public synchronized Long nextId() {if (is用盡()) {throw new RuntimeException("號段已用盡,startId=" + startId + ", endId=" + endId);}currentId++;return currentId;}/*** 判斷號段是否用盡*/public boolean is用盡() {return currentId >= endId;}}
}
三、使用示例
import com.example.service.IdGenerator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest
@RunWith(SpringRunner.class)
public class IdGeneratorTest {@Autowiredprivate IdGenerator idGenerator;@Testpublic void testNextId() {// 生成訂單業務的IDfor (int i = 0; i < 5; i++) {Long orderId = idGenerator.nextId("order");System.out.println("生成訂單ID:" + orderId);}}
}

輸出結果(首次運行):

生成訂單ID:1
生成訂單ID:2
生成訂單ID:3
生成訂單ID:4
生成訂單ID:5

3、NoSQL


一般情況下,NoSQL 方案使用 Redis 多一些。我們通過 Redis 的 incr 命令即可實現對 id 原子順序遞增。


127.0.0.1:6379> set sequence_id_biz_type 1
OK
127.0.0.1:6379> incr sequence_id_biz_type
(integer) 2
127.0.0.1:6379> get sequence_id_biz_type
"2"


為了提高可用性和并發,我們可以使用 Redis Cluster。Redis Cluster 是 Redis 官方提供的 Redis 集群解決方案(3.0+版本)。
Redis 方案的優缺點:
????????●優點:性能不錯并且生成的 ID 是有序遞增的
????????●缺點:和數據庫主鍵自增方案的缺點類似

注意:

????????用redis實現需要注意一點,要考慮到redis持久化的問題。redis有兩種持久化方式RDBAOF

  • RDB會定時打一個快照進行持久化,假如連續自增但redis沒及時持久化,而這會Redis掛掉了,重啟Redis后會出現ID重復的情況。
  • AOF會對每條寫命令進行持久化,即使Redis掛掉了也不會出現ID重復的情況,但由于incr命令的特殊性,會導致Redis重啟恢復的數據時間過長。


除了 Redis 之外,MongoDB ObjectId 經常也會被拿來當做分布式 ID 的解決方案。


MongoDB ObjectId 一共需要 12 個字節存儲:
????????●0~3:時間戳
????????●3~6:代表機器 ID
????????●7~8:機器進程 ID
????????●9~11:自增值
MongoDB 方案的優缺點:
????????●優點:性能不錯并且生成的 ID 是有序遞增的
????????●缺點:需要解決重復 ID 問題(當機器時間不對的情況下,可能導致會產生重復 ID)、有安全性問題(ID 生成有規律性)

使用示例2:

基于 Redis 生成分布式 ID 是一種高性能方案,利用 Redis 的原子操作(如INCR)保證 ID 的唯一性和遞增性。以下是完整的實現示例:

一、核心設計思路
  1. 原子遞增:使用 Redis 的INCR命令對每個業務類型的 ID 鍵進行原子遞增,確保分布式環境下 ID 唯一。
  2. 前綴區分業務:為不同業務(如訂單、用戶)設置獨立的 Redis 鍵(如id:orderid:user),避免 ID 沖突。
  3. ID 結構優化:可組合 “時間戳 + 自增數 + 機器標識” 等信息,使 ID 具備趨勢遞增性和可追溯性。
  4. 緩存與容錯:本地緩存 Redis 生成的 ID 段,減少 Redis 訪問次數;當 Redis 不可用時,使用備用方案避免服務中斷。
二、實現代碼
1. 依賴配置(Spring Boot)

pom.xml?引入 Redis 依賴:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml?配置 Redis:

spring:redis:host: localhostport: 6379password:  # 若有密碼請填寫database: 0timeout: 3000mslettuce:pool:max-active: 16  # 連接池最大連接數max-idle: 8     # 連接池最大空閑連接數
2. Redis 分布式 ID 生成器實現
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;@Component
public class RedisIdGenerator {private final RedisTemplate<String, Long> redisTemplate;// Redis鍵前綴(區分不同業務)private static final String ID_KEY_PREFIX = "distributed:id:";// 本地緩存的ID段大小(減少Redis訪問次數)private static final int BATCH_SIZE = 1000;// 本地緩存:key=業務類型,value=ID段信息private final LocalIdCache localCache = new LocalIdCache();public RedisIdGenerator(RedisTemplate<String, Long> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 生成分布式ID(帶業務前綴)* @param bizType 業務類型(如"order"、"user")* @return 分布式ID*/public Long generateId(String bizType) {// 從本地緩存獲取ID,若緩存不足則從Redis批量獲取IdSegment segment = localCache.getSegment(bizType);if (segment == null || segment.isExhausted()) {ReentrantLock lock = new ReentrantLock();lock.lock();try {// 雙重檢查,避免并發重復申請segment = localCache.getSegment(bizType);if (segment == null || segment.isExhausted()) {// 從Redis申請新的ID段segment = fetchIdSegmentFromRedis(bizType);localCache.setSegment(bizType, segment);}} finally {lock.unlock();}}return segment.nextId();}/*** 從Redis批量獲取ID段(原子操作)*/private IdSegment fetchIdSegmentFromRedis(String bizType) {String redisKey = ID_KEY_PREFIX + bizType;// 原子遞增,一次獲取BATCH_SIZE個ID(返回遞增后的結果)Long end = redisTemplate.opsForValue().increment(redisKey, BATCH_SIZE);if (end == null) {throw new RuntimeException("Redis生成ID失敗,key=" + redisKey);}// ID段范圍:[end - BATCH_SIZE + 1, end]long start = end - BATCH_SIZE + 1;return new IdSegment(start, end);}/*** 生成帶時間戳的復合ID(如:1717234567890 + 12345 → 171723456789012345)* 適用于需要ID包含時間信息的場景*/public Long generateTimestampId(String bizType) {long timestamp = System.currentTimeMillis(); // 時間戳(毫秒級)long sequence = generateId(bizType) % 100000; // 取5位自增數(避免ID過長)// 組合:時間戳(13位) + 自增數(5位) → 18位IDreturn timestamp * 100000 + sequence;}/*** 本地ID段緩存:減少Redis訪問次數*/private static class LocalIdCache {private final java.util.Map<String, IdSegment> cache = new java.util.HashMap<>();public IdSegment getSegment(String bizType) {return cache.get(bizType);}public void setSegment(String bizType, IdSegment segment) {cache.put(bizType, segment);}}/*** ID段內部類:維護批量獲取的ID范圍*/private static class IdSegment {private final long start; // 起始IDprivate final long end;   // 結束IDprivate long current;     // 當前分配到的IDpublic IdSegment(long start, long end) {this.start = start;this.end = end;this.current = start - 1; // 初始化為起始ID的前一個}/*** 獲取下一個ID*/public synchronized long nextId() {if (isExhausted()) {throw new RuntimeException("ID段已用盡,start=" + start + ", end=" + end);}return ++current;}/*** 判斷ID段是否用盡*/public boolean isExhausted() {return current >= end;}}
}
3. 使用示例
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisIdGeneratorTest {@Autowiredprivate RedisIdGenerator idGenerator;@Testpublic void testGenerateId() {// 生成訂單業務IDfor (int i = 0; i < 5; i++) {Long orderId = idGenerator.generateId("order");System.out.println("訂單ID:" + orderId);}// 生成帶時間戳的用戶IDfor (int i = 0; i < 5; i++) {Long userId = idGenerator.generateTimestampId("user");System.out.println("用戶ID(帶時間戳):" + userId);}}
}

輸出結果

訂單ID:1
訂單ID:2
訂單ID:3
訂單ID:4
訂單ID:5
用戶ID(帶時間戳):171723456789000001
用戶ID(帶時間戳):171723456789000002
用戶ID(帶時間戳):171723456789000003
用戶ID(帶時間戳):171723456789000004
用戶ID(帶時間戳):171723456789000005
三、核心優勢與注意事項
優勢
  1. 高性能:Redis 單線程原子操作 + 本地緩存,支持高并發(每秒可達 10 萬 + ID 生成)。
  2. 唯一性INCR命令保證分布式環境下 ID 絕對唯一,無重復風險。
  3. 趨勢遞增:ID 單調遞增,適合數據庫索引優化(B + 樹索引對有序數據更友好)。
  4. 輕量易用:無需復雜的數據庫表設計,通過 Redis 命令即可實現。
  5. 可擴展性:支持多業務類型隔離,通過鍵前綴區分不同場景。
注意事項
  1. Redis 可用性:依賴 Redis 服務,需確保 Redis 高可用(如主從 + 哨兵 / 集群模式)。
  2. ID 連續性:服務重啟后,本地緩存的 ID 段會丟失,可能導致 ID 不連續(但不影響唯一性)。
  3. 步長設置BATCH_SIZE(批量獲取的 ID 數量)需根據業務并發調整:
    • 高并發場景設大(如 10000),減少 Redis 訪問次數;
    • 低并發場景設小(如 100),減少 ID 浪費。
  4. 數據持久化:Redis 需開啟 RDB/AOF 持久化,避免重啟后 ID 從 0 開始導致重復。
  5. ID 長度控制:復合 ID(如時間戳 + 自增數)需注意長度,避免超出數據庫字段限制(如 MySQL 的 BIGINT 最大支持 19 位)。
四、擴展優化
  1. 容錯降級:當 Redis 不可用時,切換到本地自增 ID(需保證機器標識唯一,避免分布式重復)。

    // 簡化示例:本地降級方案
    private Long fallbackGenerateId(String bizType) {// 機器標識(如IP后兩位)+ 時間戳 + 本地自增數String machineId = "10"; // 實際應動態獲取long timestamp = System.currentTimeMillis() / 1000; // 秒級時間戳long localSeq = localSequence.getAndIncrement();return Long.parseLong(machineId + timestamp + localSeq);
    }
    
  2. 過期清理:為 Redis 鍵設置過期時間(如 1 年),避免長期積累無效鍵。

    // 生成ID時同步設置過期時間
    redisTemplate.expire(redisKey, 365, TimeUnit.DAYS);
    
  3. 預加載 ID 段:當本地 ID 段剩余不足 20% 時,異步提前從 Redis 獲取新號段,避免生成 ID 時的阻塞。

  4. 自定義 ID 格式:根據業務需求擴展 ID 結構,例如:

    • 多機房場景:機房ID(2位)+ 時間戳(13位)+ 自增數(4位)
    • 分庫分表場景:表標識(3位)+ 自增數(16位)
五、適用場景
  • 高并發業務(如秒殺、電商訂單):依賴 Redis 的高性能。
  • 需要 ID 趨勢遞增的場景:如數據庫分庫分表、日志排序。
  • 跨服務 ID 生成:如分布式系統中多個微服務共享 ID 生成器。

通過 Redis 生成分布式 ID,既能保證唯一性和性能,又能靈活擴展 ID 格式,是分布式系統的優選方案之一。


4、算法


4.1、UUID


UUID 是 Universally Unique Identifier(通用唯一標識符) 的縮寫。UUID 包含 32 個 16 進制數字(8-4-4-4-12)。
JDK 就提供了現成的生成 UUID 的方法,一行代碼就行了。

//輸出示例:cb4a9edefa5e4585b9bbd60bce986eaa
UUID.randomUUID().tostring().replaceA11("-","");


RFC 4122 中關于 UUID 的示例是這樣的:


我們這里重點關注一下這個 Version(版本),不同的版本對應的 UUID 的生成規則是不同的。
5 種不同的 Version(版本)值分別對應的含義(參考維基百科對于 UUID 的介紹):
????????●版本 1 : UUID 是根據時間和節點 ID(通常是 MAC 地址)生成;
????????●版本 2 : UUID 是根據標識符(通常是組或用戶 ID)、時間和節點 ID 生成;
????????●版本 3、版本 5 : 版本 5 - 確定性 UUID 通過散列(hashing)名字空間(namespace)標識符和名稱生成;
????????●版本 4 : UUID 使用隨機性或偽隨機性生成。
下面是 Version 1 版本下生成的 UUID 的示例:


JDK 中通過 UUID 的 randomUUID() 方法生成的 UUID 的版本默認為 4。


UUID uuid = UUID.randomUUID();
int version = uuid.version();// 4


從上面的介紹中可以看出,UUID 可以保證唯一性,因為其生成規則包括 MAC 地址、時間戳、名字空間(Namespace)、隨機或偽隨機數、時序等元素,計算機基于這些規則生成的 UUID 是肯定不會重復的。
雖然,UUID 可以做到全局唯一性,但是,我們一般很少會使用它。
比如使用 UUID 作為 MySQL 數據庫主鍵的時候就非常不合適:
????????●數據庫主鍵要盡量越短越好,而 UUID 的消耗的存儲空間比較大(32 個字符串,128 位)。
????????●UUID 是無順序的,InnoDB 引擎下,數據庫主鍵的無序性會嚴重影響數據庫性能。
最后,我們再簡單分析一下 UUID 的優缺點
????????●優點:生成足夠簡單,本地生成無網絡消耗,具有唯一性
????????●缺點:存儲消耗空間大(32 個字符串,128 位)、 不安全(基于 MAC 地址生成 UUID 的算法會造成 MAC 地址泄露)、無序(非自增)、沒有具體業務含義、需要解決重復 ID 問題(當機器時間不對的情況下,可能導致會產生重復 ID)


4.2、Snowflake(雪花算法)


Snowflake 是 Twitter 開源的分布式 ID 生成算法。Snowflake 由 64 bit 的二進制數字組成,這 64bit 的二進制被分成了幾部分,每一部分存儲的數據都有特定的含義:


????????●sign(1bit):符號位(標識正負),始終為 0,代表生成的 ID 為正數。
????????●timestamp (41 bits):一共 41 位,用來表示時間戳,單位是毫秒,可以支撐 2 ^41 毫秒(約 69 年)
????????●datacenter id + worker id (10 bits):一般來說,前 5 位表示機房 ID,后 5 位表示機器 ID(實際項目中可以根據實際情況調整)。這樣就可以區分不同集群/機房的節點。
????????●sequence (12 bits):一共 12 位,用來表示序列號。 序列號為自增值,代表單臺機器每毫秒能夠產生的最大 ID 數(2^12 = 4096),也就是說單臺機器每毫秒最多可以生成 4096 個 唯一 ID。
在實際項目中,我們一般也會對 Snowflake 算法進行改造,最常見的就是在 Snowflake 算法生成的 ID 中加入業務類型信息。
我們再來看看 Snowflake 算法的優缺點:
????????●優點:生成速度比較快、生成的 ID 有序遞增、比較靈活(可以對 Snowflake 算法進行簡單的改造比如加入業務 ID)
????????●缺點:需要解決重復 ID 問題(ID 生成依賴時間,在獲取時間的時候,可能會出現時間回撥的問題,也就是服務器上的時間突然倒退到之前的時間,進而導致會產生重復 ID)、依賴機器 ID 對分布式環境不友好(當需要自動啟停或增減機器時,固定的機器 ID 可能不夠靈活)。


4.3 開源框架

UidGenerator(百度)


UidGenerator 是百度開源的一款基于 Snowflake(雪花算法)的唯一 ID 生成器。
不過,UidGenerator 對 Snowflake(雪花算法)進行了改進,生成的唯一 ID 組成如下:


????????●sign(1bit):符號位(標識正負),始終為 0,代表生成的 ID 為正數。
????????●delta seconds (28 bits):當前時間,相對于時間基點"2016-05-20"的增量值,單位:秒,最多可支持約 8.7 年
????????●worker id (22 bits):機器 id,最多可支持約 420w 次機器啟動。內置實現為在啟動時由數據庫分配,默認分配策略為用后即棄,后續可提供復用策略。
????????●sequence (13 bits):每秒下的并發序列,13 bits 可支持每秒 8192 個并發。
可以看出,和原始 Snowflake(雪花算法)生成的唯一 ID 的組成不太一樣。并且,上面這些參數我們都可以自定義。
UidGenerator 官方文檔中的介紹如下:


自 18 年后,UidGenerator 就基本沒有再維護了

Leaf(美團)


Leaf 是美團開源的一個分布式 ID 解決方案 。這個項目的名字 Leaf(樹葉) 起源于德國哲學家、數學家萊布尼茨的一句話:“There are no two identical leaves in the world”(世界上沒有兩片相同的樹葉) 。這名字起得真心挺不錯的,有點文藝青年那味了!
Leaf 提供了 號段模式 和 Snowflake(雪花算法) 這兩種模式來生成分布式 ID。并且,它支持雙號段,還解決了雪花 ID 系統時鐘回撥問題。不過,時鐘問題的解決需要弱依賴于 Zookeeper(使用 Zookeeper 作為注冊中心,通過在特定路徑下讀取和創建子節點來管理 workId) 。
Leaf 的誕生主要是為了解決美團各個業務線生成分布式 ID 的方法多種多樣以及不可靠的問題。
Leaf 對原有的號段模式進行改進,比如它這里增加了雙號段避免獲取 DB 在獲取號段的時候阻塞請求獲取 ID 的線程。簡單來說,就是我一個號段還沒用完之前,我自己就主動提前去獲取下一個號段


根據項目 README 介紹,在 4C8G VM 基礎上,通過公司 RPC 方式調用,QPS 壓測結果近 5w/s,TP999 1ms。

Tinyid(滴滴)


Tinyid 是滴滴開源的一款基于數據庫號段模式的唯一 ID 生成器。
數據庫號段模式的原理我們在上面已經介紹過了。Tinyid 有哪些亮點呢?
為了搞清楚這個問題,我們先來看看基于數據庫號段模式的簡單架構方案。(圖片來自于 Tinyid 的官方 wiki:《Tinyid 原理介紹》)


在這種架構模式下,我們通過 HTTP 請求向發號器服務申請唯一 ID。負載均衡 router 會把我們的請求送往其中的一臺 tinyid-server。
這種方案有什么問題呢?在我看來(Tinyid 官方 wiki 也有介紹到),主要由下面這 2 個問題:
????????●獲取新號段的情況下,程序獲取唯一 ID 的速度比較慢。
????????●需要保證 DB 高可用,這個是比較麻煩且耗費資源的。
除此之外,HTTP 調用也存在網絡開銷。
Tinyid 的原理比較簡單,其架構如下圖所示:


相比于基于數據庫號段模式的簡單架構方案,Tinyid 方案主要做了下面這些優化:
????????●雙號段緩存:為了避免在獲取新號段的情況下,程序獲取唯一 ID 的速度比較慢。 Tinyid 中的號段在用到一定程度的時候,就會去異步加載下一個號段,保證內存中始終有可用號段。
????????●增加多 db 支持:支持多個 DB,并且,每個 DB 都能生成唯一 ID,提高了可用性。
????????●增加 tinyid-client:純本地操作,無 HTTP 請求消耗,性能和可用性都有很大提升。
Tinyid 的優缺點這里就不分析了,結合數據庫號段模式的優缺點和 Tinyid 的原理就能知道。
?

IdGenerator(個人)


和 UidGenerator、Leaf 一樣,IdGenerator 也是一款基于 Snowflake(雪花算法)的唯一 ID 生成器。IdGenerator 有如下特點:
????????●生成的唯一 ID 更短;
????????●兼容所有雪花算法(號段模式或經典模式,大廠或小廠);
????????●原生支持 C#/Java/Go/C/Rust/Python/Node.js/PHP(C 擴展)/SQL/ 等語言,并提供多線程安全調用動態庫(FFI);
????????●解決了時間回撥問題,支持手工插入新 ID(當業務需要在歷史時間生成新 ID 時,用本算法的預留位能生成 5000 個每秒);
????????●不依賴外部存儲系統;
????????●默認配置下,ID 可用 71000 年不重復。
IdGenerator 生成的唯一 ID 組成如下:


????????●timestamp (位數不固定):時間差,是生成 ID 時的系統時間減去 BaseTime(基礎時間,也稱基點時間、原點時間、紀元時間,默認值為 2020 年) 的總時間差(毫秒單位)。初始為 5bits,隨著運行時間而增加。如果覺得默認值太老,你可以重新設置,不過要注意,這個值以后最好不變。
????????●worker id (默認 6 bits):機器 id,機器碼,最重要參數,是區分不同機器或不同應用的唯一 ID,最大值由 WorkerIdBitLength(默認 6)限定。如果一臺服務器部署多個獨立服務,需要為每個服務指定不同的 WorkerId。
????????●sequence (默認 6 bits):序列數,是每毫秒下的序列數,由參數中的 SeqBitLength(默認 6)限定。增加 SeqBitLength 會讓性能更高,但生成的 ID 也會更長。
Java 語言使用示例:https://github.com/yitter/idgenerator/tree/master/Java。


總結


1、數據庫


????????a、主鍵自增
????????????????■優點:實現起來比較簡單、ID 有序遞增、存儲消耗空間小。
????????????????■缺點:支持的并發量不大、存在數據庫單點問題(可以使用數據庫集群解決,不過增加了復雜度)、ID 沒有具體業務含義、安全問題(比如根據訂單 ID 的遞增規律就能推算出每天的訂單量,商業機密啊! )、每次獲取 ID 都要訪問一次數據庫(增加了對數據庫的壓力,獲取速度也慢)
????????b、號段模式
????????????????■優點:ID 有序遞增、存儲消耗空間小
????????????????■缺點:存在數據庫單點問題(可以使用數據庫集群解決,不過增加了復雜度)、ID 沒有具體業務含義、安全問題(比如根據訂單 ID 的遞增規律就能推算出每天的訂單量,商業機密啊!)
????????c、NoSQL,比如Redis
????????????????■優點:性能不錯并且生成的 ID 是有序遞增的
????????????????■缺點:和數據庫主鍵自增方案的缺點類似


2、算法


????????a、UUID
????????????????■優點:生成速度比較快、簡單易用
????????????????■缺點:存儲消耗空間大(32 個字符串,128 位)、 不安全(基于 MAC 地址生成 UUID 的算法會造成 MAC 地址泄露)、無序(非自增)、沒有具體業務含義、需要解決重復 ID 問題(當機器時間不對的情況下,可能導致會產生重復 ID)
????????b、Snowflake(雪花算法)
????????????????■優點:生成速度比較快、生成的 ID 有序遞增、比較靈活(可以對 Snowflake 算法進行簡單的改造比如加入業務 ID)
????????????????■缺點:需要解決重復 ID 問題(ID 生成依賴時間,在獲取時間的時候,可能會出現時間回撥的問題,也就是服務器上的時間突然倒退到之前的時間,進而導致會產生重復 ID)、依賴機器 ID 對分布式環境不友好(當需要自動啟停或增減機器時,固定的機器 ID 可能不夠靈活)。

3、開源框架


????????a、UidGenerator(百度):基于Snowflake(雪花算法)做了改進
????????b、Leaf(美團): 支持號段模式 和 Snowflake(雪花算法) 來生成分布式 ID,增加了雙號段避免獲取 DB 在獲取號段的時候阻塞請求獲取 ID 的線程
????????c、Tinyid(滴滴)
????????????????■雙號段緩存:為了避免在獲取新號段的情況下,程序獲取唯一 ID 的速度比較慢。 Tinyid 中的號段在用到一定程度的時候,就會去異步加載下一個號段,保證內存中始終有可用號段。
????????????????■增加多 db 支持:支持多個 DB,并且,每個 DB 都能生成唯一 ID,提高了可用性。
????????????????■增加 tinyid-client:純本地操作,無 HTTP 請求消耗,性能和可用性都有很大提升。
????????d、IdGenerator(個人)
????????????????■生成的唯一 ID 更短;
????????????????■兼容所有雪花算法(號段模式或經典模式,大廠或小廠);
????????????????■原生支持 C#/Java/Go/C/Rust/Python/Node.js/PHP(C 擴展)/SQL/ 等語言,并提供多線程安全調用動態庫(FFI);
????????????????■解決了時間回撥問題,支持手工插入新 ID(當業務需要在歷史時間生成新 ID 時,用本算法的預留位能生成 5000 個每秒);
????????????????■不依賴外部存儲系統;
????????????????■默認配置下,ID 可用 71000 年不重復。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/91068.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/91068.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/91068.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Rust 實戰三 | HTTP 服務開發及 Web 框架推薦

往期回顧 Rust 實戰二 | 開發簡易版命令行工具 grepRust 實戰一 | 用 RustRover 開發猜數字游戲Rust 安裝與版本更新 代碼開源地址&#xff1a;https://github.com/0604hx/rust-journey &#x1f680; Web 框架 名稱性能&#xff08;QPS&#xff09;WebSocket / SSEGitHub ?…

墨者:SQL過濾字符后手工注入漏洞測試(第1題)

1. 墨者學院&#xff1a;SQL過濾字符后手工注入漏洞測試(第1題)&#x1f680; 2. 漏洞背景與測試思路&#x1f50d; 在Web安全測試中&#xff0c;當遇到對輸入字符有過濾的SQL注入漏洞時&#xff0c;我們需要使用特殊技巧繞過過濾機制。本次測試的目標URL存在字符過濾&#xff…

深入解析 Vue 3 中 v-model 與表單元素的綁定機制

v-model 是 Vue 中最強大的指令之一&#xff0c;它簡化了表單數據雙向綁定的實現。本文將系統梳理各種 HTML 表單元素與 v-model 的綁定關系&#xff0c;特別是那些容易引起困惑的類型。一、v-model 的本質v-model 是一個語法糖&#xff0c;它實際上是 :value 和 input 的組合&…

【趙渝強老師】MySQL中的數據庫對象

MySQL數據庫中包含各自數據庫對象&#xff0c;常見的數據庫對象有&#xff1a;表、索引、視圖、事件、存儲過程和存儲函數等等。 視頻講解如下 【趙渝強老師】MySQL中的數據庫對象一、 創建與管理表 表是一種非常重要的數據庫對象&#xff0c;MySQL數據庫的數據都是存儲在表中…

Angular面試題目和答案大全

基礎概念篇1. 什么是Angular&#xff1f;它與AngularJS有什么區別&#xff1f;答案&#xff1a; Angular是由Google開發的基于TypeScript的開源Web應用框架&#xff0c;用于構建單頁應用程序&#xff08;SPA&#xff09;。Angular vs AngularJS對比&#xff1a;特性AngularJSAn…

CSS 語音參考

CSS 語音參考 概述 CSS&#xff08;層疊樣式表&#xff09;是用于描述HTML或XML文檔樣式的樣式表語言。它為網頁元素提供了一種統一的方式來定義字體、顏色、布局和其他視覺屬性。CSS語音參考旨在為開發者提供一個詳盡的指南&#xff0c;以便他們能夠更有效地使用CSS來增強網頁…

C# WPF 實現讀取文件夾中的PDF并顯示其頁數

文章目錄技術選型第一步&#xff1a;創建項目并安裝依賴庫第二步&#xff1a;定義數據模型 (Model)第三步&#xff1a;創建視圖模型 (ViewModel)第四步&#xff1a;設計用戶界面 (View)總結與解釋后記關于轉換器的錯誤工作中需要整理一些PDF格式文件&#xff0c;程序員的存在就…

設計模式(五)創建型:原型模式詳解

設計模式&#xff08;五&#xff09;創建型&#xff1a;原型模式詳解原型模式&#xff08;Prototype Pattern&#xff09;是 GoF 23 種設計模式中的創建型模式之一&#xff0c;其核心價值在于通過復制現有對象來創建新對象&#xff0c;而不是通過 new 關鍵字調用構造函數。它特…

K8S 八 數據存儲-高級存儲PV PVC 生命周期;配置存儲ConfigMap Secret

目錄數據存儲 Volume8.1 基本存儲8.1.1 EmptyDir8.1.2 HostPath 掛載目錄8.1.3 NFSnfs的服務8.2 高級存儲8.2.1 PV和PVC8.2.2 PV 持久化卷申請8.2.3 PVC 資源申請PVC的配置參數8.2.4 生命周期配置存儲8.3.1 ConfigMap8.3.2 Secret數據存儲 Volume Kubernetes的Volume支持多種類…

Baumer工業相機堡盟工業相機如何通過YoloV8深度學習模型實現輪船檢測識別(C#代碼UI界面版)

Baumer工業相機堡盟工業相機如何通過YoloV8深度學習模型實現輪船檢測識別&#xff08;C#代碼UI界面版&#xff09;工業相機使用YoloV8模型實現輪船檢測識別工業相機通過YoloV8模型實現輪船檢測識別的技術背景在相機SDK中獲取圖像轉換圖像的代碼分析工業相機圖像轉換Bitmap圖像格…

自習室預約小程序的設計與實現

自習室預約小程序的設計與實現現代學習環境對高效、便捷的預約系統需求日益增長。自習室預約小程序結合前沿技術棧&#xff0c;提供流暢的用戶體驗和強大的后臺管理功能&#xff0c;滿足學生、職場人士等群體的自習需求。技術架構與核心功能Vue.js 構建動態前端界面 采用 Vue.j…

Docker 實戰大綱

文章目錄Docker 實戰 – Mysql &#xff08;敬請期待……&#xff09;

從一個“詭異“的C++程序理解狀態機、防抖與系統交互

引言 在編程世界中&#xff0c;有時一個看似簡單的代碼片段可能隱藏著令人驚訝的復雜性。本文將從一個"故意設計"的C程序出發&#xff0c;深入探討其背后涉及的狀態機模式、防抖機制以及操作系統與控制臺的交互原理。通過這個案例&#xff0c;我們不僅能理解這些核心…

NAS-Bench-101: Towards Reproducible Neural Architecture Search

概述這篇題為"NAS-Bench-101: Towards Reproducible Neural Architecture Search"的論文由Chris Ying等人合作完成&#xff0c;旨在解決神經網絡架構搜索(NAS)領域面臨的重大挑戰&#xff1a;計算資源需求高和實驗難以復現的問題。論文提出了NAS-Bench-101&#xff0…

SpringBoot整合Fastexcel/EasyExcel導出Excel導出多個圖片

整個工具的代碼都在Gitee或者Github地址內 gitee&#xff1a;solomon-parent: 這個項目主要是總結了工作上遇到的問題以及學習一些框架用于整合例如:rabbitMq、reids、Mqtt、S3協議的文件服務器、mongodb、xxl-job、powerjob還有用Docker compose部署各類中間組件。如果大家有…

網絡原理--HTTPHTTPS

目錄 一、HTTP 1.1 HTTP是什么 1.2 HTTP協議的工作過程 1.3 HTTP協議格式 1.3.1 抓包工具的使用 1.3.2 抓包結果 1.4 HTTP請求 1.4.1 URL 1.4.2 認識“方法” (method) 1.4.3 認識請求“報頭”(header) 1.4.4 認識請求“正文”(body) 1.5 HTTP 響應詳解 1.5.1 HTTP…

『 C++ 入門到放棄 』- 哈希表

一、哈希的概念 哈希&#xff0c;也稱「 散列 」是一種用來進行高效查找的數據結構&#xff0c;查找的時間復雜度平均為O(1)&#xff0c;其本質就是依賴哈希函數這個算法來將 key 和該 key 存儲位置建立一個映射關系。 而因為是有著映射關系&#xff0c;所以哈希的事件復雜度為…

零售收銀系統開源代碼全解析:連鎖門店一體化解決方案(含POS+進銷存+商城)

過去10年&#xff0c;收銀系統技術經歷了從單機版到云服務、從單純結算到全渠道整合的快速演進。面對連鎖多門店、AI稱重、智能分賬、跨店庫存同步等新需求&#xff0c;很多企業的現有傳統saas系統已顯乏力。本文將梳理收銀系統關鍵技術指標&#xff0c;助您在系統升級時做出明…

能源高效利用如何實現?樓宇自控系統智能化監管建筑設備

隨著全球能源危機日益嚴峻和“雙碳”目標的持續推進&#xff0c;建筑領域作為能耗大戶&#xff08;約占社會總能耗的40%&#xff09;&#xff0c;其節能潛力備受關注。樓宇自控系統&#xff08;Building Automation System&#xff0c;簡稱BAS&#xff09;作為建筑智能化的核心…

校園二手交易小程序的設計與實現

文章目錄前言詳細視頻演示具體實現截圖后端框架SpringBoot微信小程序持久層框架MyBaits成功系統案例&#xff1a;參考代碼數據庫源碼獲取前言 博主介紹:CSDN特邀作者、985高校計算機專業畢業、現任某互聯網大廠高級全棧開發工程師、Gitee/掘金/華為云/阿里云/GitHub等平臺持續…