雪花算法
為什么需要分布式全局唯一ID 以及分布式ID的業(yè)務(wù)需求?
- 在復(fù)雜分布式系統(tǒng)中,往往需要對大量對數(shù)據(jù)和消息進(jìn)行標(biāo)識
- 如在美團(tuán)、支付、餐飲 中 系統(tǒng)的數(shù)據(jù)日漸增長,對數(shù)據(jù)分庫分表需要有一個唯一來標(biāo)識一條數(shù)據(jù)或消息
- 此時一個能夠生成全局唯一ID的系統(tǒng)是非常有必要的
ID生成規(guī)則部分硬性要求
- 全局唯一 :不能出現(xiàn)重復(fù)的ID,要 唯一標(biāo)識
- 趨勢遞增 :在Mysql 的InnoDB引擎使用的是聚集索引,由于多數(shù)RDBMS 使用的是Btree數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù),在主鍵的選擇上面我們應(yīng)該盡量使用有序的主鍵保證數(shù)據(jù)寫入
- 單調(diào)遞增 :保證下一個ID一定大于上一個ID,例如事物版本號,增量消息
- 信息安全 :如果ID是連續(xù)的,惡意用戶的扒取數(shù)據(jù)就非常容易來,直接按照順序下載指定的URL,如果是訂單號就更危險來,競爭對手可以知道我們一天的單量,所以在一些應(yīng)用場景下,需要ID不規(guī)則
- 含時間戳 :這樣就能夠在開發(fā)中快速了解這個分布式id的生成時間
ID生成系統(tǒng)的可用性要求
- 高可用 :發(fā)一個獲取分布式ID的請求,服務(wù)器就要保證99.99%的情況下給我創(chuàng)建一個唯一分布式ID
- 低延遲 :發(fā)一個獲取分布式ID的請求,服務(wù)器就是要快,極速
- 高QPS :假如并發(fā)一口氣10萬個創(chuàng)建分布式ID請求同時殺過來,服務(wù)器要頂?shù)淖∫幌伦映晒?chuàng)建10w個分布式ID
我們平時的方案
UUID 、 數(shù)據(jù)庫自增主鍵 、基于Redis 生成全局ID策略
弊端
UUID 不能生成順序,遞增的數(shù)據(jù),并且長,不是很推薦
數(shù)據(jù)庫自增,集群多的情況下,擴(kuò)容簡直就是噩夢
Redis 使用Redis INCR 和 INCRBY 實現(xiàn)
snowflake(雪花算法)
Twitter的分布式自增ID算法:snowflake(雪花算法)
概述
最初 Twitter把存儲系統(tǒng)從Mysql 遷移到 Cassandra (由Facebook 開發(fā)一套開源分布式Nosql系統(tǒng)) 因為Cassandra沒有順序ID生成機(jī)制,所以開發(fā)成了這樣一套全局唯一 ID生成服務(wù)
Twitter 的分布式雪花算法SnowFlake , 經(jīng)測試 snowflake 每秒能產(chǎn)出26 萬個自增可排序的ID
- twitter的SnowFlake生成ID能夠按照時間有序生成
- SnowFlake 算法生成id 的結(jié)果是一個64 bit 大小的整數(shù),為一個Long 型(轉(zhuǎn)換成字符后長度19位)
- 分布式系統(tǒng)不會產(chǎn)生ID碰撞(由datacenter 和 workerld 區(qū)分)并且效率較高
結(jié)構(gòu)
號段解析:
1bit ,
- 不用,因為二進(jìn)制中最高位是符號位,毫秒級,生成的id一般用整數(shù),所以最高位 0
41bit - 時間戳,用來記錄時間戳,毫秒級,
- 41位可以表示 2 ^ {41}-1個數(shù)字
- 如果只用來表示正整數(shù)(計算機(jī)中正整數(shù)包含0)??梢员硎緮?shù)值范圍:0 至 2^{41}-1 , 減1 是因為表示的數(shù)值是從0開始算的 ,而不是1.
- 也就是說 41 位可以表示 2 ^ {41}-1 個毫秒的值,裝換成單位年則 (2^{41}-1)/ (1000 * 60 * 60 * 24 * 365)=69年
10bit- 工作機(jī)器ID,用來記錄工作機(jī)器ID
- 可以部署在 2^{10} = 1024 個節(jié)點,包括5位 datacenterId 和 5位的 workeId
- 5位(bit)可以表示的最大正整數(shù)是 2 ^ {5}-1 =31 , 即可以用0、1、2、3....31這32個數(shù)字,來表示不同的datacenterId 或者 workId
12 bit -序列號,序列號,用來記錄同毫秒內(nèi)產(chǎn)生的不同的id。
- 12位可以表示的最大正整數(shù)是2^{12}-1 = 4095 即可以用0、1、2、34094 這 4095個數(shù)字來表示同一機(jī)器同一時間(毫秒)產(chǎn)生的4095個ID序號
SnowFlake可以保證
- 所有生成的id 按時間趨勢遞增
- 整個分布式系統(tǒng)內(nèi)不會產(chǎn)生重復(fù)的id
源碼
twitter的雪花算法:https://github.com/twitter-archive/snowflake
GitHub上java版的雪花算法:
https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java
https://github.com/souyunku/SnowFlake/blob/master/SnowFlake.java
java版??雪花算法
public class SnowflakeIdWorker {
// ==============================Fields==================
/** 開始時間截 (2019-08-06) */
private final long twepoch = 1565020800000L;
/** 機(jī)器id所占的位數(shù) */
private final long workerIdBits = 5L;
/** 數(shù)據(jù)標(biāo)識id所占的位數(shù) */
private final long datacenterIdBits = 5L;
/** 支持的最大機(jī)器id,結(jié)果是31 (這個移位算法可以很快的計算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大數(shù)據(jù)標(biāo)識id,結(jié)果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中占的位數(shù) */
private final long sequenceBits = 12L;
/** 機(jī)器ID向左移12位 */
private final long workerIdShift = sequenceBits;
/** 數(shù)據(jù)標(biāo)識id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 時間截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作機(jī)器ID(0~31) */
private long workerId;
/** 數(shù)據(jù)中心ID(0~31) */
private long datacenterId;
/** 毫秒內(nèi)序列(0~4095) */
private long sequence = 0L;
/** 上次生成ID的時間截 */
private long lastTimestamp = -1L;
//==============================Constructors====================
/**
* 構(gòu)造函數(shù)
* @param workerId 工作ID (0~31)
* @param datacenterId 數(shù)據(jù)中心ID (0~31)
*/
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
// ==============================Methods=================================
/**
* 獲得下一個ID (該方法是線程安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果當(dāng)前時間小于上一次ID生成的時間戳,說明系統(tǒng)時鐘回退過這個時候應(yīng)當(dāng)拋出異常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一時間生成的,則進(jìn)行毫秒內(nèi)序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒內(nèi)序列溢出
if (sequence == 0) {
//阻塞到下一個毫秒,獲得新的時間戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//時間戳改變,毫秒內(nèi)序列重置
else {
sequence = 0L;
}
//上次生成ID的時間截
lastTimestamp = timestamp;
//移位并通過或運(yùn)算拼到一起組成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一個毫秒,直到獲得新的時間戳
* @param lastTimestamp 上次生成ID的時間截
* @return 當(dāng)前時間戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒為單位的當(dāng)前時間
* @return 當(dāng)前時間(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
//==============================Test=============================================
/** 測試 */
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 10; i++) {
long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
}
}
}
springboot整合雪花算法
- 新建項目snowflake
- pom
<dependencies>
<!--hutool 引入糊涂工具包,測試雪花算法-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- yml
server:
port: 7777
- 新建 utils包 IdGeneratorSnowflake 類
@Slf4j
@Component
public class IdGeneratorSnowflake {
private long workerId = 0; //第幾號機(jī)房
private long datacenterId = 1; //第幾號機(jī)器
private Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
@PostConstruct //構(gòu)造后開始執(zhí)行,加載初始化工作
public void init(){
try{
//獲取本機(jī)的ip地址編碼
workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
log.info("當(dāng)前機(jī)器的workerId: " + workerId);
}catch (Exception e){
e.printStackTrace();
log.warn("當(dāng)前機(jī)器的workerId獲取失敗 ----> " + e);
workerId = NetUtil.getLocalhostStr().hashCode();
}
}
public synchronized long snowflakeId(){
return snowflake.nextId();
}
public synchronized long snowflakeId(long workerId, long datacenterId){
Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
return snowflake.nextId();
}
//測試
public static void main(String[] args) {
System.out.println(new IdGeneratorSnowflake().snowflakeId()); //1277896081711169536
}
}
- 新建service包 OrderService 接口 與 service.impl包 OrderServiceImpl 實現(xiàn)OrderService的接口
public interface OrderService {
String getIDBySnowFlake();
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private IdGeneratorSnowflake idGenerator;
public String getIDBySnowFlake() {
//新建線程池(5個線程)
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 20; i++) {
threadPool.submit(() -> {
System.out.println(idGenerator.snowflakeId());
});
}
threadPool.shutdown();
return "hello snowflake";
}
}
- 新建 controller 包 OrderController
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/snowflake")
public String index(){
return orderService.getIDBySnowFlake();
}
}
- 主啟動類
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}
啟動項目 瀏覽器輸入http://localhost:7777/snowflake
優(yōu)缺點
解決時鐘回?fù)軉栴}
百度開源的分布式唯一ID生成器 UidGenerator
美團(tuán)開源的分布式ID生成系統(tǒng) Leaf