背景
分布式系統(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)