动态扩缩容下的全局流水号设计

关于全局流水号,业内用的比较多的就是雪花算法,一直没理解在动态扩缩容下其中的workId和

datacenterId如何设置,查到了几个方法:reidis中取,待后期实践下。

先简单的介绍一下雪花算法,雪花算法生成的Id由:1bit 不用 + 41bit时间戳+10bit工作机器id+12bit序列号,如下图:

不用:1bit,因为最高位是符号位,0表示正,1表示负,所以这里固定为0
时间戳:41bit,服务上线的时间毫秒级的时间戳(为当前时间-服务第一次上线时间),这里为(2^41-1)/1000/60/60/24/365 = 49.7年
工作机器id:10bit,表示工作机器id,用于处理分布式部署id不重复问题,可支持2^10 = 1024个节点
序列号:12bit,用于离散同一机器同一毫秒级别生成多条Id时,可允许同一毫秒生成2^12 = 4096个Id,则一秒就可生成4096*1000 = 400w个Id


说明:上面总体是64位,具体位数可自行配置,如想运行更久,需要增加时间戳位数;如想支持更多节点,可增加工作机器id位数;如想支持更高并发,增加序列号位数

公司使用的 k8s 容器化部署服务应用,所以需要支持动态增加节点,并且每次部署的机器不一定一样时,就会有问题。参考了 雪花算法snowflake生成Id重复问题 其中的思想:

在redis中存储一个当前workerId的最大值
每次生成workerId时,从redis中获取到当前workerId最大值,并+1作为当前workerId,并存入redis
如果workerId为1023,自增为1024,则重置0,作为当前workerId,并存入redis
然后优化成以下逻辑:

定义一个 redis 作为缓存 key,然后服务每次初始化的时候都 incr 这个 key。
上面得到的 incr 的结果然后与 1024 取模。取模可以优化为:result & 0x000003FF
所以最后的代码为下面:

首先我们先定义雪花算法生成分布式 ID 类:

SnowflakeIdWorker.java

public class SnowflakeIdWorker {/** 开始时间截 (建议用服务第一次上线的时间,到毫秒级的时间戳) */private final long twepoch = 687888001020L;/** 机器id所占的位数 */private final long workerIdBits = 10L;/** 支持的最大机器id,结果是1023 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 时间截向左移22位(10+12) */private final long timestampLeftShift = sequenceBits + workerIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)* <<为左移,每左移动1位,则扩大1倍* */private final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 工作机器ID(0~1024) */private long workerId;/** 毫秒内序列(0~4095) */private long sequence = 0L;/** 上次生成ID的时间截 */private long lastTimestamp = -1L;//==============================Constructors=====================================/*** 构造函数* @param workerId 工作ID (0~1023)*/public SnowflakeIdWorker(long workerId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));}this.workerId = workerId;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {//如果毫秒相同,则从0递增生成序列号sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //| (workerId << workerIdShift) //| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间,从1970-01-01 08:00:00算起* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}public static void main(String[] args) {SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(1);Set<Long> params = new HashSet<>();for (int i = 0; i < 3000_0000; i++) {params.add(snowflakeIdWorker.nextId());}System.out.println(params.size());}}


 

接着定义一个 ID 生成的接口以及实现类。

public interface IdManager {String getId();}下面是实现类@Slf4j
@Service("idManager")
public class IdManagerImpl implements IdManager {@Resource(name = "stringRedisTemplate")private StringRedisTemplate stringRedisTemplate;private SnowflakeIdWorker snowflakeIdWorker;@PostConstructpublic void init() {String cacheKey = KeyUtils.getKey("order", "snowflake", "workerId", "incr");Long increment = stringRedisTemplate.opsForValue().increment(cacheKey);long workerId = increment & 0x000003FF;log.info("IdManagerImpl.init snowflake worker id is {}", workerId);snowflakeIdWorker = new SnowflakeIdWorker(workerId);}@Overridepublic String getId() {long nextId = snowflakeIdWorker.nextId();return Long.toString(nextId);}
}

在服务每次上线的时候就会把之前的 incr 值加 1。然后与 1024 取模,最后 workerId 就会一直在 [0 ~ 1023] 范围内进行动态取值。


原文链接:https://blog.csdn.net/u012410733/article/details/121882691

还有的做法是依赖配置中心的数据,因为无论是扩缩容至少都要注册到注册中心上,那拿到注册中心上的ip和端口号来动态生成workId和datacenterId


import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.AbstractEventListener;
import com.alibaba.nacos.api.naming.listener.Event;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.text.DecimalFormat;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;/*** SnowflakeId + Nacos*/
@Component
public class SnowflakeIdGenerator {protected final Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprivate NacosServiceManager nacosServiceManager;@Autowiredprivate NacosDiscoveryProperties nacosDiscoveryProperties;private static SnowflakeIdWorker snowflakeIdWorker;private static int nodeId;@PostConstructpublic void run() throws Exception {init();}/*** 获取雪花Id** @return*/public static long nextId() {return snowflakeIdWorker.nextId();}/*** 获取当前节点Id** @return*/public static int nodeId() {return nodeId;}/*** 获取当前服务所有节点 + 增加服务监听** @throws NacosException*/private void init() throws NacosException {NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());namingService.subscribe(nacosDiscoveryProperties.getService(), new AbstractEventListener() {@Overridepublic void onEvent(Event event) {if (-1 == nacosDiscoveryProperties.getPort()) {return;}nodeId = calcNodeId(((NamingEvent) event).getInstances());if (nodeId > 1024) {throw new IllegalArgumentException("Worker & Datacenter Id calc results exceed 1024");}long workerId = nodeId % 31;long datacenterId = (long) Math.floor((float) nodeId / 31);logger.info("nodeId:" + nodeId + " workerId:" + workerId + " datacenterId:" + datacenterId);snowflakeIdWorker = new SnowflakeIdWorker(workerId, datacenterId);}});}/*** 用ip+port计算服务列表的索引** @param instanceList* @return*/private int calcNodeId(List<Instance> instanceList) {List<Long> ipPosrList = instanceList.stream().map(x -> dealIpPort(x.getIp(), x.getPort())).sorted(Comparator.naturalOrder()).collect(Collectors.toList());return ipPosrList.indexOf(dealIpPort(nacosDiscoveryProperties.getIp(), nacosDiscoveryProperties.getPort()));}/*** ip补0 + 端口号** @param ip* @param port* @return*/private static Long dealIpPort(String ip, int port) {String[] ips = ip.split("\\.");StringBuilder sbr = new StringBuilder();for (int i = 0; i < ips.length; i++) {sbr.append(new DecimalFormat("000").format(Integer.parseInt(ips[i])));}return Long.parseLong(sbr.toString() + port);}}

代码在https://gitee.com/JiaXiaohei/snowflake-nacos

还有一种方法是这样获取的

@Configuration
public class SnowFlakeIdConfig {@Beanpublic SnowFlakeIdUtil propertyConfigurer() {return new SnowFlakeIdUtil(getWorkId(), getDataCenterId(), 10);}/*** workId使用IP生成* @return workId*/private static Long getWorkId() {try {String hostAddress = Inet4Address.getLocalHost().getHostAddress();int[] ints = StringUtils.toCodePoints(hostAddress);int sums = 0;for (int b : ints) {sums = sums + b;}return (long) (sums % 32);}catch (UnknownHostException e) {// 失败就随机return RandomUtils.nextLong(0, 31);}}/*** dataCenterId使用hostName生成* @return dataCenterId*/private static Long getDataCenterId() {try {String hostName = SystemUtils.getHostName();int[] ints = StringUtils.toCodePoints(hostName);int sums = 0;for (int i: ints) {sums = sums + i;}return (long) (sums % 32);}catch (Exception e) {// 失败就随机return RandomUtils.nextLong(0, 31);}}}

有参考:雪花算法(snowflake)容器化部署支持动态增加节点_k8s雪花id重复-CSDN博客

用Nacos分配Snowflake的Worker ID_nacos workid-CSDN博客 雪花算法的原理和实现Java-CSDN博客

Leaf——美团点评分布式ID生成系统 - 美团技术团队 (meituan.com)

java 雪花算法 动态生成workId与dataCenterId - 胡子就不刮 - 博客园 (cnblogs.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/252653.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录Day42 | 背包问题理论 416. 分割等和子集

代码随想录Day42 | 背包问题理论 416. 分割等和子集 二维dp解决背包问题一维dp求解背包问题46.携带研究材料二维dp一维dp 416.分割等和子集 二维dp解决背包问题 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a; 带你学透0-1背包问题 状态 利用五部曲来分析二维dp是如何解…

CF1404BTree Tag/ BZOJ0487. 树上追逐详解

1.题目 传送门:Tree Tag - 洛谷 2.思路 我们考虑什么情况下Alice可以获胜. 如果​ ≤ da&#xff0c;则Alice可以一步就追上Bob. 如果Alice处在一个能覆盖整棵树的点&#xff0c;即2da 1≥树的直径&#xff0c;那么Bob也无论走到哪里Alice都能追到,Alice获胜. 其它情况下…

string容器

#include<iostream> using namespace std; //string的构造函数 void test01() {string s1;//默认构造const char *str"holle word";string s2(str);cout<<"s2"<<s2<<endl;string s3(s2);cout<<"s3"<<s3<…

【Vitis】基于C++函数开发组件的步骤

目录 基本步骤 关键领域 • 硬件接口&#xff1a; 任务级并行度&#xff1a; 存储器架构&#xff1a; 微观级别的最优化&#xff1a; 基本步骤 1. 基于 设计原则 建立算法架构。 2. &#xff08;C 语言仿真&#xff09; 利用 C/C 语言测试激励文件验证 C/C 代码的逻辑。…

六轴机器人奇异点

1 奇异点说明 有着6个自由度的KUKA机器人具有3个不同的奇点位置。即便在给定状态和步骤顺序的情况下,也无法通过逆向变换(将笛卡尔坐标转换成极坐标值)得出唯一数值时,即可认为是一个奇点位置。这种情况下,或者当最小的笛卡尔变化也能导致非常大的轴角度变化时,即为奇点位置…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之MenuItem组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之MenuItem组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、MenuItem组件 用来展示菜单Menu中具体的item菜单项。 子组件 无 接口 Menu…

C语言贪吃蛇详解

个人简介&#xff1a;双非大二学生 个人博客&#xff1a;Monodye 今日鸡汤&#xff1a;人生就像一盒巧克力&#xff0c;你永远不知道下一块是什么味的 C语言基础刷题&#xff1a;牛客网在线编程_语法篇_基础语法 (nowcoder.com) 一.贪吃蛇游戏背景 贪吃蛇是久负盛名的游戏&…

如何决定K8S Pod的剔除优先级

在Kubernetes&#xff08;k8s&#xff09;中&#xff0c;当节点资源面临压力时&#xff0c;如何决定Pod的优先级是一个关键问题。在Kubernetes 1.8版本之后&#xff0c;引入了基于Pod优先级的调度策略&#xff0c;即Pod Priority Preemption。这种策略允许在资源不足的情况下&a…

6-2、T型加减速计算简化【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;本节介绍简化T型加减速计算过程&#xff0c;使其适用于单片机数据处理。简化内容包括浮点数转整型数计算、加减速对称处理、预处理计算 一、浮点数转整型数计算 根据上一节内容已知 常用的晶振大小…

3dmatch-toolbox详细安装教程-Ubuntu14.04

3dmatch-toolbox详细安装教程-Ubuntu14.04 前言docker搭建Ubuntu14.04安装第三方库安装cuda/cundnn安装OpenCV安装Matlab 安装以及运行3dmatch-toolbox1.安装测试3dmatch-toolbox(对齐两个点云) 总结 前言 paper:3DMatch: Learning Local Geometric Descriptors from RGB-D Re…

基于单片机的智能寻光小车设计

摘 要&#xff1a;随着物联网技术的飞速发展和逐渐成熟&#xff0c;以单片机为主的智能小车在巡查、仓储、探险及国防等领域得到广泛应用。本文设计了一种基于单片机的智能寻光小车&#xff0c;该小车以STC89C52RC 芯片为设计核心&#xff0c;结合光敏传感器和超声波传感器等多…

初识C语言·编译与链接

1 翻译环境和运行环境 C语言标准ANSI C 实现C语言代码的时候 一般需要经过两种环境&#xff0c;一是翻译环境&#xff0c;二是运行环境&#xff0c;计算机能识别的是二进制的指令&#xff0c;人写完代码后通过翻译环境&#xff0c;使代码变成计算机能读懂的可执行的机器指令&a…

MySQL组复制的介绍

前言 本文介绍关于MySQL组复制的背景信息和基本原理。包括&#xff0c;介绍MySQL传统复制方法的原理和隐患、介绍组复制的原理&#xff0c;单主模式和多主模式等等。通过结合原理图学习这些概念&#xff0c;可以很好的帮助我们理解组复制技术这一MySQL高可用方案&#xff0c;有…

ChatGPT 4.0 升级指南, ChatGPT Plus(GPT 4.0) 有何优势?

1.ChatGPT 是什么&#xff1f; ChatGPT 是由 OpenAI 开发的一种基于人工智能的聊天机器人&#xff0c;它基于强大的语言处理模型 GPT&#xff08;Generative Pre-trained Transformer&#xff09;构建。它能够理解人类语言&#xff0c;可以为我们解决实际的问题。 ChatGPT 4.…

关于网络面试题汇总

什么是TCP/IP五层模型&#xff1f;它们的作用是啥&#xff1f;基于TCP/IP实现的应用&#xff08;层协议&#xff09;有哪些&#xff1f; TCP/IP五层模型&#xff0c;从上向下分别是&#xff1a; 应用层&#xff1a;应用程序本身&#xff0c;应用层的作用是负责应用程序之间的…

python使用fabric库

目录 一&#xff1a;介绍 二&#xff1a;远程命令执行 三&#xff1a;文件上传&#xff0c;下载 四&#xff1a;执行多台服务器命令 一&#xff1a;介绍 Fabric是一个Python库&#xff0c;用于简化SSH连接和自动化任务。它提供了一个简单的API来执行远程命令、上传和下载文…

京东广告算法架构体系建设--大规模稀疏场景高性能训练方案演变

一、前言 京东广告训练框架随着广告算法业务发展的特点也在快速迭代升级&#xff0c;回顾近几年大致经历了两次大版本的方案架构演变。第一阶段&#xff0c;随着2016年Tensorflow训练框架的开源&#xff0c;业界开始基于Tensorflow开源框架训练更复杂的模型。模型对特征规模和…

ctfshow——文件包含

文章目录 web 78——php伪协议第一种方法——php://input第二种方法——data://text/plain第三种方法——远程包含&#xff08;http://协议&#xff09; web 78——str_replace过滤字符php第一种方法——远程包含&#xff08;http://协议&#xff09;第二种方法——data://&…

Visual Studio 2010+C#实现信源和信息熵

1. 设计要求 以图形界面的方式设计一套程序&#xff0c;该程序可以实现以下功能&#xff1a; 从输入框输入单个或多个概率&#xff0c;然后使用者可以通过相关按钮的点击求解相应的对数&#xff0c;自信息以及信息熵程序要能够实现马尔可夫信源转移概率矩阵的输入并且可以计算…