雪花算法
為什么需要分布式全局唯一ID 以及分布式ID的業(yè)務(wù)需求汗盘?
- 在復(fù)雜分布式系統(tǒng)中,往往需要對(duì)大量對(duì)數(shù)據(jù)和消息進(jìn)行標(biāo)識(shí)
- 如在美團(tuán)询一、支付隐孽、餐飲 中 系統(tǒng)的數(shù)據(jù)日漸增長(zhǎng)癌椿,對(duì)數(shù)據(jù)分庫(kù)分表需要有一個(gè)唯一來(lái)標(biāo)識(shí)一條數(shù)據(jù)或消息
- 此時(shí)一個(gè)能夠生成全局唯一ID的系統(tǒng)是非常有必要的
ID生成規(guī)則部分硬性要求
- 全局唯一 :不能出現(xiàn)重復(fù)的ID,要 唯一標(biāo)識(shí)
- 趨勢(shì)遞增 :在Mysql 的InnoDB引擎使用的是聚集索引菱阵,由于多數(shù)RDBMS 使用的是Btree數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)踢俄,在主鍵的選擇上面我們應(yīng)該盡量使用有序的主鍵保證數(shù)據(jù)寫(xiě)入
- 單調(diào)遞增 :保證下一個(gè)ID一定大于上一個(gè)ID,例如事物版本號(hào)晴及,增量消息
- 信息安全 :如果ID是連續(xù)的都办,惡意用戶(hù)的扒取數(shù)據(jù)就非常容易來(lái),直接按照順序下載指定的URL虑稼,如果是訂單號(hào)就更危險(xiǎn)來(lái)琳钉,競(jìng)爭(zhēng)對(duì)手可以知道我們一天的單量,所以在一些應(yīng)用場(chǎng)景下蛛倦,需要ID不規(guī)則
- 含時(shí)間戳 :這樣就能夠在開(kāi)發(fā)中快速了解這個(gè)分布式id的生成時(shí)間
ID生成系統(tǒng)的可用性要求
- 高可用 :發(fā)一個(gè)獲取分布式ID的請(qǐng)求歌懒,服務(wù)器就要保證99.99%的情況下給我創(chuàng)建一個(gè)唯一分布式ID
- 低延遲 :發(fā)一個(gè)獲取分布式ID的請(qǐng)求,服務(wù)器就是要快溯壶,極速
- 高QPS :假如并發(fā)一口氣10萬(wàn)個(gè)創(chuàng)建分布式ID請(qǐng)求同時(shí)殺過(guò)來(lái)及皂,服務(wù)器要頂?shù)淖∫幌伦映晒?chuàng)建10w個(gè)分布式ID
我們平時(shí)的方案
UUID 、 數(shù)據(jù)庫(kù)自增主鍵 且改、基于Redis 生成全局ID策略
弊端
UUID 不能生成順序验烧,遞增的數(shù)據(jù),并且長(zhǎng)钾虐,不是很推薦
數(shù)據(jù)庫(kù)自增噪窘,集群多的情況下,擴(kuò)容簡(jiǎn)直就是噩夢(mèng)
Redis 使用Redis INCR 和 INCRBY 實(shí)現(xiàn)
snowflake(雪花算法)
Twitter的分布式自增ID算法:snowflake(雪花算法)
概述
最初 Twitter把存儲(chǔ)系統(tǒng)從Mysql 遷移到 Cassandra (由Facebook 開(kāi)發(fā)一套開(kāi)源分布式Nosql系統(tǒng)) 因?yàn)镃assandra沒(méi)有順序ID生成機(jī)制效扫,所以開(kāi)發(fā)成了這樣一套全局唯一 ID生成服務(wù)
Twitter 的分布式雪花算法SnowFlake 倔监, 經(jīng)測(cè)試 snowflake 每秒能產(chǎn)出26 萬(wàn)個(gè)自增可排序的ID
- twitter的SnowFlake生成ID能夠按照時(shí)間有序生成
- SnowFlake 算法生成id 的結(jié)果是一個(gè)64 bit 大小的整數(shù),為一個(gè)Long 型(轉(zhuǎn)換成字符后長(zhǎng)度19位)
- 分布式系統(tǒng)不會(huì)產(chǎn)生ID碰撞(由datacenter 和 workerld 區(qū)分)并且效率較高
結(jié)構(gòu)
號(hào)段解析:
1bit 菌仁,
- 不用浩习,因?yàn)槎M(jìn)制中最高位是符號(hào)位,毫秒級(jí)济丘,生成的id一般用整數(shù)谱秽,所以最高位 0
41bit - 時(shí)間戳,用來(lái)記錄時(shí)間戳摹迷,毫秒級(jí)疟赊,
- 41位可以表示 2 ^ {41}-1個(gè)數(shù)字
- 如果只用來(lái)表示正整數(shù)(計(jì)算機(jī)中正整數(shù)包含0)∠康铮可以表示數(shù)值范圍:0 至 2^{41}-1 , 減1 是因?yàn)楸硎镜臄?shù)值是從0開(kāi)始算的 近哟,而不是1.
- 也就是說(shuō) 41 位可以表示 2 ^ {41}-1 個(gè)毫秒的值,裝換成單位年則 (2^{41}-1)/ (1000 * 60 * 60 * 24 * 365)=69年
10bit- 工作機(jī)器ID鲫寄,用來(lái)記錄工作機(jī)器ID
- 可以部署在 2^{10} = 1024 個(gè)節(jié)點(diǎn)吉执,包括5位 datacenterId 和 5位的 workeId
- 5位(bit)可以表示的最大正整數(shù)是 2 ^ {5}-1 =31 , 即可以用0疯淫、1、2戳玫、3....31這32個(gè)數(shù)字熙掺,來(lái)表示不同的datacenterId 或者 workId
12 bit -序列號(hào),序列號(hào)咕宿,用來(lái)記錄同毫秒內(nèi)產(chǎn)生的不同的id币绩。
- 12位可以表示的最大正整數(shù)是2^{12}-1 = 4095 即可以用0、1荠列、2类浪、34094 這 4095個(gè)數(shù)字來(lái)表示同一機(jī)器同一時(shí)間(毫秒)產(chǎn)生的4095個(gè)ID序號(hào)
SnowFlake可以保證
- 所有生成的id 按時(shí)間趨勢(shì)遞增
- 整個(gè)分布式系統(tǒng)內(nèi)不會(huì)產(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==================
/** 開(kāi)始時(shí)間截 (2019-08-06) */
private final long twepoch = 1565020800000L;
/** 機(jī)器id所占的位數(shù) */
private final long workerIdBits = 5L;
/** 數(shù)據(jù)標(biāo)識(shí)id所占的位數(shù) */
private final long datacenterIdBits = 5L;
/** 支持的最大機(jī)器id,結(jié)果是31 (這個(gè)移位算法可以很快的計(jì)算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大數(shù)據(jù)標(biāo)識(shí)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)識(shí)id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 時(shí)間截向左移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的時(shí)間截 */
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=================================
/**
* 獲得下一個(gè)ID (該方法是線程安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果當(dāng)前時(shí)間小于上一次ID生成的時(shí)間戳,說(shuō)明系統(tǒng)時(shí)鐘回退過(guò)這個(gè)時(shí)候應(yīng)當(dāng)拋出異常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一時(shí)間生成的川队,則進(jìn)行毫秒內(nèi)序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒內(nèi)序列溢出
if (sequence == 0) {
//阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//時(shí)間戳改變力细,毫秒內(nèi)序列重置
else {
sequence = 0L;
}
//上次生成ID的時(shí)間截
lastTimestamp = timestamp;
//移位并通過(guò)或運(yùn)算拼到一起組成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一個(gè)毫秒,直到獲得新的時(shí)間戳
* @param lastTimestamp 上次生成ID的時(shí)間截
* @return 當(dāng)前時(shí)間戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒為單位的當(dāng)前時(shí)間
* @return 當(dāng)前時(shí)間(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
//==============================Test=============================================
/** 測(cè)試 */
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整合雪花算法
- 新建項(xiàng)目snowflake
- pom
<dependencies>
<!--hutool 引入糊涂工具包固额,測(cè)試雪花算法-->
<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 類(lèi)
@Slf4j
@Component
public class IdGeneratorSnowflake {
private long workerId = 0; //第幾號(hào)機(jī)房
private long datacenterId = 1; //第幾號(hào)機(jī)器
private Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
@PostConstruct //構(gòu)造后開(kāi)始執(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();
}
//測(cè)試
public static void main(String[] args) {
System.out.println(new IdGeneratorSnowflake().snowflakeId()); //1277896081711169536
}
}
- 新建service包 OrderService 接口 與 service.impl包 OrderServiceImpl 實(shí)現(xiàn)OrderService的接口
public interface OrderService {
String getIDBySnowFlake();
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private IdGeneratorSnowflake idGenerator;
public String getIDBySnowFlake() {
//新建線程池(5個(gè)線程)
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();
}
}
- 主啟動(dòng)類(lèi)
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}
啟動(dòng)項(xiàng)目 瀏覽器輸入http://localhost:7777/snowflake
優(yōu)缺點(diǎn)
解決時(shí)鐘回?fù)軉?wèn)題
百度開(kāi)源的分布式唯一ID生成器 UidGenerator
美團(tuán)開(kāi)源的分布式ID生成系統(tǒng) Leaf
- 個(gè)人博客:http://blog.yanxiaolong.cn/