为什么需要分布式全局唯一ID以及分布式ID的业务需求?
在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。
如在美团点评的今日、支付、餐饮、酒店;
猫眼电影等产品的系统数据中日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息
特别一点的如订单、骑手、优惠券也需要有唯一ID做标识。
此时一个能够生成全局唯一ID的系统是非常重要的。
即软件要求
即硬件要求
高可用
发一个获取分布式ID的请求,服务器就要保证99.999%的情况下给我创建一个唯一的分布式ID
低延迟
发一个请获取分布式ID的请求,服务器要快,极速
高QPS
假如并发一口气10万个创建分布式ID请求同时过来,服务器要顶得住并且成功创建10万个分布式ID
是什么
UUID(Universally Unique Identifier)的标准式包含32个16进制数字,以连字号分为五段,形式为a-b-c-d-e的36个字符
例子: de999f9a-baed-47c4-b06f-aaf3ac81a363
性能非常高,本地生成,没有网络消耗
如果只是考虑唯一性是可以的,但是它入数据库性能差
为什么无序的UUID会导致入库性能差?
单机
在分布式里面,数据库自增ID的机制主要原理是:数据库自增ID和mysql数据库的replace into实现的。
这里的replace into和insert功能类似
不同点在于:replace into首先尝试插入数据库列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入,否则直接插入新数据。
REPLACE INTO的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据
CREATE TABLE t_test(id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,stub CHAR(1) NOT NULL DEFAULT '',UNIQUE KEY stub(stub)
)SELECT * FROM t_test;REPLACE INTO t_test(stub) VALUES('b');
SELECT LAST_INSERT_ID();
第一次执行REPLACE INTO
第二次执行REPLACE INTO
uuid:只有唯一性,趋势递增
mysql:唯一性,递增
集群分布式
数据库的自增ID机制适合作分布式ID吗?不适合
注意:在Redis集群情况下,同样和MySQL一样需要设置不同的增长步长
,同时key一定要设置有效期
可以使用Redis集群来获取更高的吞吐量。
假如一个集群中有5台Redis,可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。
各个Redis的生成ID为:
A.1,6,11,16,21
B.2,7,12,17,22
C.3,8,13,18,23
D.4,9,14,19,24
E.5,10,15,20,25
Twitter的分布式自增ID算法snowflake(雪花算法)
twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra(由Facebook开发一套分布式NoSQL数据库系统)因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务
Twitter的分布式雪花算法SnowFlake,经测试snowflake每秒能够产生26万个自增可排序的ID
分布式系统中,有一些需要使用全局唯一ID的场景,生成ID的基本要求
号段解析:
1bit.
不用,因为二进制中最高位是符号位,1表示负数,0表示正数。
生成的id一般都是用整数,所以最高位固定为0。
41bit——时间戳,用来记录时间戳,毫秒级。
减1是因为可表示的数值范围是从0开始算的,而不是1
10bit——工作机器id,用来记录工作机器id
12bit——序列号,序列号用来记录同毫秒内
产生的不同id
SnowFlake可以保证:
所有生成的id按时间趋势递增
整个分布式系统内不会产生重复id(因为datacenterId和workerId来做区分)
/*** twitter的snowflake算法 -- java实现* * @author beyond* @date 2016/11/26*/
public class SnowFlake {/*** 起始的时间戳*/private final static long START_STMP = 1480166465631L;/*** 每一部分占用的位数*/private final static long SEQUENCE_BIT = 12; //序列号占用的位数private final static long MACHINE_BIT = 5; //机器标识占用的位数private final static long DATACENTER_BIT = 5;//数据中心占用的位数/*** 每一部分的最大值*/private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);/*** 每一部分向左的位移*/private final static long MACHINE_LEFT = SEQUENCE_BIT;private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;private long datacenterId; //数据中心private long machineId; //机器标识private long sequence = 0L; //序列号private long lastStmp = -1L;//上一次时间戳public SnowFlake(long datacenterId, long machineId) {if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");}if (machineId > MAX_MACHINE_NUM || machineId < 0) {throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");}this.datacenterId = datacenterId;this.machineId = machineId;}/*** 产生下一个ID** @return*/public synchronized long nextId() {long currStmp = getNewstmp();if (currStmp < lastStmp) {throw new RuntimeException("Clock moved backwards. Refusing to generate id");}if (currStmp == lastStmp) {//相同毫秒内,序列号自增sequence = (sequence + 1) & MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if (sequence == 0L) {currStmp = getNextMill();}} else {//不同毫秒内,序列号置为0sequence = 0L;}lastStmp = currStmp;return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分| datacenterId << DATACENTER_LEFT //数据中心部分| machineId << MACHINE_LEFT //机器标识部分| sequence; //序列号部分}private long getNextMill() {long mill = getNewstmp();while (mill <= lastStmp) {mill = getNewstmp();}return mill;}private long getNewstmp() {return System.currentTimeMillis();}public static void main(String[] args) {SnowFlake snowFlake = new SnowFlake(2, 3);for (int i = 0; i < (1 << 12); i++) {System.out.println(snowFlake.nextId());}}
}
官网
cn.hutool hutool-all 5.8.11
package com.asule.hutusnowflake.service;/*** 简要描述** @Author: ASuLe* @Date: 2023/1/24 13:42* @Version: 1.0* @Description: 文件作用详细描述....*/
public interface OrderService {String getIDBySnowFlake();
}
package com.asule.hutusnowflake.service.impl;import com.asule.hutusnowflake.service.OrderService;
import com.asule.hutusnowflake.util.IdGeneratorSnowFlake;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 简要描述** @Author: ASuLe* @Date: 2023/1/24 13:44* @Version: 1.0* @Description: 文件作用详细描述....*/
@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate IdGeneratorSnowFlake idGenerator;@Overridepublic String getIDBySnowFlake() {ExecutorService threadPool = Executors.newFixedThreadPool(5);for (int i = 1; i <= 20; i++) {threadPool.submit(()->{System.out.println(idGenerator.snowflakeId());});}threadPool.shutdown();return "hello snowflake";}
}
package com.asule.hutusnowflake.controller;import com.asule.hutusnowflake.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 简要描述** @Author: ASuLe* @Date: 2023/1/24 13:42* @Version: 1.0* @Description: 文件作用详细描述....*/
@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@RequestMapping("/snowflake")public String index(){return orderService.getIDBySnowFlake();}
}
package com.asule.hutusnowflake.util;import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;/*** 简要描述** @Author: ASuLe* @Date: 2023/1/24 13:45* @Version: 1.0* @Description: 文件作用详细描述....*/
@Component
@Slf4j
public class IdGeneratorSnowFlake {private long workerId = 0;private long datacenterId = 1;private Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId);//post构造完了开始执行,加载初始化工作@PostConstructpublic void init() {try {workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());log.info("当前机器的workerId:{}" , workerId);} catch (Exception e) {e.printStackTrace();log.warn("当前机器的workerId失败" , workerId);workerId = NetUtil.getLocalhostStr().hashCode();}}public synchronized long snowflakeId(){return snowflake.nextId();}public synchronized long snowflakeId(long workerId,long datacenterId){Snowflake snowflake = IdUtil.getSnowflake(workerId,datacenterId);return snowflake.nextId();}public static void main(String[] args) {System.out.println(new IdGeneratorSnowFlake().snowflakeId());}
}
package com.asule.hutusnowflake;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class HutuSnowFlakeApplication {public static void main(String[] args) {SpringApplication.run(HutuSnowFlakeApplication.class, args);}}
server:port: 7777
优点:
缺点:
解决时钟回拨问题