?微信公眾號訪問地址:基于Redis實現全局唯一Id
推薦文章:
? ? 1、使用原生Redis命令實現分布式鎖
? ? ?2、為什么引入Redisson分布式鎖?
? ? 3、SpringBoot整合多數據源,并支持動態新增與切換(詳細教程)
? ? 4、SpringBoot統一標準響應格式及異常處理
? ?5、SpringBoot用線程池ThreadPoolTaskExecutor異步處理百萬級數據
一、簡介
使用數據庫自增ID就存在一些問題:
????1、受單表自增數量的限制;
??????原因:mysql單表的容量不宜超過500W條,數據量過大之后,就需要進行拆庫拆表,但拆分表之后,它們從邏輯上講仍然是同一張表,所以他們的id是不能一樣的(不同表,若使用自增ID,是可能一樣的),所以隨著我們的業務數據越來越大,我們需要保證id的唯一性。
????2、id的規律性太明顯。
??????原因:自增id具有太明顯的規則,用戶或者說商業對手很容易猜測出來一些敏感信息,例如:在一天時間內,我們賣出了多少單,這明顯不合適。
二、全局唯一ID生成策略
?
??????一般要滿足下列特性:
?
??????為了增加ID的安全性,可以不直接使用Redis自增的數值,而是拼接一些其它信息:
?
組成說明:
????1、符號位:1bit,永遠為0;
????2、時間戳(31?Bit):31bit,以秒為單位,可以使用69年;
????3、序列號:32bit,秒內的計數器,支持每秒產生2^32個不同ID。
三、基于Redis實現全局唯一Id案例
????原理:基于Redis?的INCR?命令生成分布式全局唯一id。INCR?命令主要有以下2個特征:
??????1、具備了“INCR?AND?GET”的原子操作,即:增加并返回結果的原子操作。這個原子性很方便我們實現獲取ID。
??????2、Redis是單進程單線程架構,INCR命令不會出現id重復。
3.1、構建RedisIdUtils類
/*** 功能描述:使用redis生成全局唯一ID* @Author: yyalin* @CreateDate: 2023/8/13 11:35*/
@Component
public class RedisIdUtils {//預生成開始時間戳private static final long BEGIN_TIMESTAMP = 1640995200L;//序列號的位數private static final int COUNT_BITS = 32;//redis提供的字符串private StringRedisTemplate stringRedisTemplate;//有參構造函數public RedisIdUtils(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 功能描述:根據keyPrefix前綴生成對應業務的全局唯一ID* @MethodName: nextId* @MethodParam: [keyPrefix:使用前綴來區分不同的業務]* @Return: long* @Author: yyalin* @CreateDate: 2023/8/13 12:20*/public long nextId(String keyPrefix) {// 1.生成時間戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timestamp = nowSecond - BEGIN_TIMESTAMP;// 2.生成序列號// 2.1.獲取當前日期,精確到天String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 2.2.自增長 icr:order:2023:08:13long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 3.拼接并返回 timestamp << COUNT_BITS :向左移動32位//原本時間戳在低位上,通過向左移動32位,變位到高位存儲,低32位都是0,然后與自增序列按位操作//形成低32位為序列號。return timestamp << COUNT_BITS | count;}
}
3.2、構建多線程測試類
**
?*?@Description:?TODO:測試RedisIdUtils
?*?@Author:?yyalin
?*?@CreateDate:?2023/8/13?12:27
?*?@Version:?V1.0
?*/
@Service
@Slf4j
public?class?TestRedisIdUtils?{
????@Autowired
????private?RedisIdUtils?redisIdUtils;
????//使用自定義的線程池
????private?ExecutorService?executorService?=?Executors.newFixedThreadPool(500);
????/**
?????*?功能描述:使用多線程測試生成40000條數據耗時
?????*?@MethodName:?testIdWorker
?????*?@MethodParam:?[nums]
?????*?@Return:?void
?????*?@Author:?yyalin
?????*?@CreateDate:?2023/8/13?12:36
?????*/
????public?void?testIdWorker(int?nums)?throws?InterruptedException?{
????????//同步協調在多線程的等待于喚醒問題?分線程全部走完之后,主線程再走
????????CountDownLatch?latch?=?new?CountDownLatch(nums);
????????Runnable?task?=?()?->?{
????????????for?(int?i?=?0;?i?<?100;?i++)?{
????????????????long?id?=?redisIdUtils.nextId("order");
????????????????System.out.println("id?=?"?+?id);
????????????}
????????????latch.countDown();
????????};
????????long?begin?=?System.currentTimeMillis();
????????for?(int?i?=?0;?i?<?nums;?i++)?{
????????????executorService.submit(task);
????????}
????????//阻塞方法?讓main線程阻塞
????????latch.await();
????????long?end?=?System.currentTimeMillis();
????????log.info("生成"+nums*100+"條id共計耗時(毫秒)?=?"?+?(end?-?begin));
????}
}
3.3、測試結果
?
序列 ? | 線程數 | 條數 | 耗時(秒) |
1 | 500 | 30000 | 0.781 |
2 | 500 | 40000 | 0.993 |
3 | 500 | 50000 | 1.164 |
??????總結:從測試結果不難看出,基于redis實現全局唯一ID,性能還是非常高的,并且耗時非常短的。
更多優秀文章,請關注個人微信公眾號或搜索“程序猿小楊”查閱。
參考文章:
https://blog.csdn.net/weixin_43811057/article/details/130798254