当前位置: 首页 > news >正文

实战-通过Hutool实现双ID链法

前置内容:双ID链法实现MQ消费者幂等-CSDN博客

简易先看完双ID链法的理论基础再来看本文的实战环节


(一)通过Hutool实现雪花算法工具类

hutool有自带的雪花算法类来实现雪花算法-Snowflake

雪花算法根据 工作机器ID和数据中心ID 生成ID


main函数基本测试

有两个重要参数

workerId-工作机器ID(第几个服务器),统一数据中心的服务器实例唯一标识

datacenterId-数据中心ID(第几个机房),数据中心的唯一标识,也就是机房的唯一标识

一个机房可以有多个服务器,如果是单机房单服务器,则workerId为0,datacenterId为0

单机房多服务器的话就是

workerd 0,1,2,3,4

datacenterId统一为0

package com.kira.scaffoldmvc.SnowFlake;import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;public class HutoolSnowflakeUsage {public static void main(String[] args) {// 定义工作机器ID和数据中心ID,取值范围根据算法设计,一般是0 - 31// 这里示例设置为1和1,实际使用中根据部署情况调整//示例:在数据中心 A 中,有 3 台机器,它们的 Worker ID 可以分别设为 0、1、2//标识不同的地理位置或集群(如云服务中的 Region),作用:确保不同数据中心的机器生成的 ID 不重复long workerId = 1;long datacenterId = 1;// 创建Snowflake实例Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);// 生成唯一IDlong id = snowflake.nextId();System.out.println("使用Hutool雪花算法生成的ID: " + id);// 也可以直接使用IdUtil的静态方法生成ID(使用默认的workerId和datacenterId配置)//Hutool 默认配置下,workerId(工作机器 ID )和datacenterId(数据中心 ID)都为 0long anotherId = IdUtil.getSnowflake().nextId();System.out.println("使用默认配置生成的ID: " + anotherId);}}


封装雪花算法工具类-Util

自定义工具类-SnowflakeUtils

中小公司一般都是单机房,所以这里将数据中心id datacenterid写死成0了

单机房-多实例

对本机IP取模得出 工作机器ID

使用staic静态块,在类加载的时候初始化Snowflake雪花实例

将一些基本的方法进行封装,如得到雪花ID,解析雪花ID生成的时间,通过本地IP计算出workerId

package com.kira.scaffoldmvc.SnowFlake;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;import java.util.Date;
import java.util.concurrent.TimeUnit;/*** 基于Hutool的雪花ID生成工具(优化版)* 功能特性:* 1. 自动绑定workerId(避免集群冲突)* 2. 时钟回拨检测与容错(通过Hutool内置机制)* 3. 提供简洁易用的API*/
@Slf4j  // 使用Lombok的日志注解
public class SnowflakeUtils {// 静态的Snowflake实例,全局唯一private static final Snowflake SNOWFLAKE;// 单机房固定为0(或通过配置读取)private static final long DATACENTER_ID = 0;// 静态初始化块,在类加载时执行static {// 初始化workerId(默认取本机IP的末段作为workerId)// workerId用于分布式环境下区分不同节点long workerId = getWorkerId();// 初始化datacenterId(随机生成,通常单机部署无需关心)// datacenterId用于扩展数据中心场景long datacenterId = DATACENTER_ID;// 通过Hutool的IdUtil创建Snowflake实例SNOWFLAKE = IdUtil.getSnowflake(workerId, datacenterId);// 记录初始化日志log.info("Snowflake inited. WorkerId={}, DatacenterId={}", workerId, datacenterId);}/*** 生成雪花ID(long格式)* @return 64位长度的唯一ID*/public static long nextId() {return SNOWFLAKE.nextId();}/*** 生成雪花ID(字符串格式)* @return 字符串形式的唯一ID*/public static String nextIdStr() {return SNOWFLAKE.nextIdStr();}/*** 解析雪花ID的生成时间* @param snowflakeId 雪花ID* @return 时间戳(毫秒级)*/public static long parseTimestamp(long snowflakeId) {return SNOWFLAKE.getGenerateDateTime(snowflakeId);}/*** 解析雪花ID生成时间,返回精确到秒的时间字符串(格式:yy-MM-dd HH:mm:ss)* @param snowflakeId 雪花ID* @return 格式化的日期时间字符串(例如:"23-12-31 23:59:59")*/public static String parseTimestampWithSeconds(long snowflakeId) {// 获取雪花ID中的时间戳(毫秒级)long timestamp = SNOWFLAKE.getGenerateDateTime(snowflakeId);// 格式化为 yy-MM-dd HH:mm:ssreturn DateUtil.format(new Date(timestamp), "yy-MM-dd HH:mm:ss");}// ========================= 私有方法 =========================/*** 自动获取workerId(基于本机IP末段)* 实现逻辑:* 1. 获取本机IP地址* 2. 提取IP最后一段数字* 3. 取模32保证workerId在有效范围内(0-31)* @return workerId*/private static long getWorkerId() {try {// 获取本机IP地址String hostIp = NetUtil.getLocalhostStr();// 提取IP最后一段并转为整数int lastSegment = Integer.parseInt(hostIp.substring(hostIp.lastIndexOf('.') + 1));// 取模32保证workerId在0-31范围内return lastSegment % 32;} catch (Exception e) {// 异常时降级为随机生成log.warn("Get workerId from IP failed, fallback to random. Error: {}", e.getMessage());return RandomUtil.randomLong(0, 31);}}/*** 时钟回拨检测(扩展点)* 说明:* 1. Hutool的Snowflake已内置时钟回拨处理* 2. 如需自定义处理逻辑(如告警),可在此扩展*/private static void checkClockBackwards() {// 默认空实现,Hutool已处理回拨// 可扩展功能:// 1. 触发告警通知// 2. 等待时钟同步// 3. 记录详细日志}
}
测试结果
package com.kira.scaffoldmvc.SnowFlake;public class Test {public static void main(String[] args) {long id = SnowflakeUtils.nextId();String time = SnowflakeUtils.parseTimestampWithSeconds(id);System.out.println(id);System.out.println(time);}}


存在问题

对IP取模后,可能会出现这种情况

A,B,C,D,E恰好取模后 他们的workId一样

如 192.168.1.100 和 192.168.2.100 取模后均为 100 % 32 = 4

这种时候雪花算法ID就会出现碰撞了

而双ID链法可以解决这个问题


(二)双ID链法-模拟实战

简介

主ID是雪花算法

副ID是自增序列

实际开发中是要将生成的双ID放到Kafka的消息体中

下面是用AutoInteger来模拟双ID链的副ID,实际开发中要使用Redis这种分布式统一管理数据库的来实现自增

用Map来模拟Redis存储双ID,以及用Map模拟Ridis对key的分区存储


实战

雪花算法工具类
package com.kira.scaffoldmvc.SnowFlake;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;import java.util.Date;
import java.util.concurrent.TimeUnit;/*** 基于Hutool的雪花ID生成工具(优化版)* 功能特性:* 1. 自动绑定workerId(避免集群冲突)* 2. 时钟回拨检测与容错(通过Hutool内置机制)* 3. 提供简洁易用的API*/
@Slf4j  // 使用Lombok的日志注解
public class SnowflakeUtils {// 静态的Snowflake实例,全局唯一private static final Snowflake SNOWFLAKE;// 单机房固定为0(或通过配置读取)private static final long DATACENTER_ID = 0;// 静态初始化块,在类加载时执行static {// 初始化workerId(默认取本机IP的末段作为workerId)// workerId用于分布式环境下区分不同节点long workerId = getWorkerId();// 初始化datacenterId(随机生成,通常单机部署无需关心)// datacenterId用于扩展数据中心场景long datacenterId = DATACENTER_ID;// 通过Hutool的IdUtil创建Snowflake实例SNOWFLAKE = IdUtil.getSnowflake(workerId, datacenterId);// 记录初始化日志log.info("Snowflake inited. WorkerId={}, DatacenterId={}", workerId, datacenterId);}/*** 生成雪花ID(long格式)* @return 64位长度的唯一ID*/public static long nextId() {return SNOWFLAKE.nextId();}/*** 生成雪花ID(字符串格式)* @return 字符串形式的唯一ID*/public static String nextIdStr() {return SNOWFLAKE.nextIdStr();}/*** 解析雪花ID的生成时间* @param snowflakeId 雪花ID* @return 时间戳(毫秒级)*/public static long parseTimestamp(long snowflakeId) {return SNOWFLAKE.getGenerateDateTime(snowflakeId);}/*** 解析雪花ID生成时间,返回精确到秒的时间字符串(格式:yy-MM-dd HH:mm:ss)* @param snowflakeId 雪花ID* @return 格式化的日期时间字符串(例如:"23-12-31 23:59:59")*/public static String parseTimestampWithSeconds(long snowflakeId) {// 获取雪花ID中的时间戳(毫秒级)long timestamp = SNOWFLAKE.getGenerateDateTime(snowflakeId);// 格式化为 yy-MM-dd HH:mm:ssreturn DateUtil.format(new Date(timestamp), "yy-MM-dd HH:mm:ss");}// ========================= 私有方法 =========================/*** 自动获取workerId(基于本机IP末段)* 实现逻辑:* 1. 获取本机IP地址* 2. 提取IP最后一段数字* 3. 取模32保证workerId在有效范围内(0-31)* @return workerId*/private static long getWorkerId() {try {// 获取本机IP地址String hostIp = NetUtil.getLocalhostStr();// 提取IP最后一段并转为整数int lastSegment = Integer.parseInt(hostIp.substring(hostIp.lastIndexOf('.') + 1));// 取模32保证workerId在0-31范围内return lastSegment % 32;} catch (Exception e) {// 异常时降级为随机生成log.warn("Get workerId from IP failed, fallback to random. Error: {}", e.getMessage());return RandomUtil.randomLong(0, 31);}}/*** 时钟回拨检测(扩展点)* 说明:* 1. Hutool的Snowflake已内置时钟回拨处理* 2. 如需自定义处理逻辑(如告警),可在此扩展*/private static void checkClockBackwards() {// 默认空实现,Hutool已处理回拨// 可扩展功能:// 1. 触发告警通知// 2. 等待时钟同步// 3. 记录详细日志}
}

双ID链法实现类-DoubleIdChainUtil

通过这个类来模拟将Key存储到Redis

ConcurrentHashMap模拟Redis存储,因为是分区存储

所以Key是分区号,Value是List<String>

Map<Integer, List<String>> map = new ConcurrentHashMap<>();

AutomicInteger模拟ID的原子自增

private AtomicInteger count = new AtomicInteger(0);

有三个方法

  1. 生成双ID
  2. 双ID以分区作为Key存入Map(分区号为副ID / 10),分区思想优化大Key问题
  3. 查看Map里的键值对信息
package com.kira.scaffoldmvc.SnowFlake;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;@Slf4j
@Component
public class DoubleIdChainUtil {//用Map模拟Redis分区存储双IDMap<Integer, List<String>> map = new ConcurrentHashMap<>();private AtomicInteger count = new AtomicInteger(0);public String createDoubleIdChain(){//生成主idlong mainId = SnowflakeUtils.nextId();int secondaryId = count.incrementAndGet();return mainId + "_" + secondaryId;}//通过存入Map来模仿存入Redis//doubleIdChain 格式如 "1256892300152455168_42"public void pushRedis(String doubleIdChain){//判断这个Key属于哪个分区,并放入分区Integer count= Integer.parseInt(doubleIdChain.split("_")[1]);Integer partition = count / 10;//放入分区map.computeIfAbsent(partition, k -> new ArrayList<>()).add(doubleIdChain);}//遍历Map中的所有键值对public void checkMapWithEntrySet() {for (Map.Entry<Integer, List<String>> entry : map.entrySet()) {Integer partition = entry.getKey();List<String> idChains = entry.getValue();log.info("分区{}: 值:{}", partition, idChains);}}}

测试类-测试双ID实现类
package com.kira.scaffoldmvc;import com.kira.scaffoldmvc.SnowFlake.DoubleIdChainUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class DoubleIdChainTest {@AutowiredDoubleIdChainUtil doubleIdChainUtil;//测试生成双ID@Testvoid test(){System.out.println("双ID为:"+doubleIdChainUtil.createDoubleIdChain());}//测试生成双ID并将双ID放到Map中,通过Map存储模拟Redis的分区存储@Testvoid test1(){for(int i=0;i<100;i++){//生成双IDString doubleIdChain = doubleIdChainUtil.createDoubleIdChain();//存入RedisdoubleIdChainUtil.pushRedis(doubleIdChain);}//遍历Map中的所有键值对doubleIdChainUtil.checkMapWithEntrySet();}}

测试类运行结果如下:

  1. test-生成双ID

  1. test1-将双ID存入Redis并且查看

100个ID分别存入了10个分区


双ID链法优点总结

双ID链的结构:

雪花ID_自增序列ID
  1. 小公司是单机房多节点,雪花算法工具类是根据ip计算出workId,可能会出现多个java服务workId相同的情况,从而导致雪花算法算出的雪花ID出现碰撞,但自增序列是全局管理(Redis原子自增incr),所以就算雪花算法算出的雪花ID相同,但自增序列ID仍然不相同
  2. 时钟回拨问题导致雪花ID重复,但自增序列是Redis全局管理的,所以最后形成的双ID仍然是不同的,不会出现ID碰撞的情况
  3. Redis单点故障主从切换会出现数字漏洞,之前的Incr递增的值在从节点没有同步,从而导致副ID(自增序列ID)出现碰撞,但是雪花算法是随着时间戳自增的,所以此时自增ID冲突了但是雪花ID不会冲突
  4. 雪花ID和自增序列ID是互补的
  5. 分区思想管理Key,对自增序列ID/1000来控制ID存的分区,避免了大Key问题的出现

相关文章:

  • (二)yolov5——模型检测
  • 华为云 Flexus+DeepSeek 实战:华为云单机部署 Dify-LLM 开发平台全流程指南【服务部署、模型配置、知识库构建全流程】
  • Mac电脑-触摸板增强工具-BetterTouchTool
  • ZZNU大一下 英语选填期末复习
  • 深入解析ID3算法:信息熵驱动的决策树构建基石
  • Python元组及字符串
  • 微处理器原理与应用篇---计算机系统的结构、组织与实现
  • 七、Redis的持久化策略
  • 前端实现截图的几种方法
  • aardio 并行任务处理
  • 对接支付宝,阿里云沙箱服务
  • AWS VPC 子网划分实战指南:从基础到进阶
  • Z-Ant开源程序是简化了微处理器上神经网络的部署和优化
  • Rust自动化测试的框架
  • C++ - vector 的使用
  • Python的6万张图像数据集CIFAR-10和CIFAR-100说明
  • 软件工程概述:核心概念、模型与方法全解析
  • Linux编程:5、进程通信-命名管道
  • 从流量为王到留量为王:开源链动2+1模式、AI智能名片与S2B2C商城小程序的协同创新路径
  • Skrill是什么?中国用户能用吗?安全吗?完整指南
  • 国外网站搭建/国产搜什么关键词最好看
  • 400元做网站送网推/中国疾控卫生应急服装
  • 网站建设的风险/收录优美图片topit
  • 什么网站算是h5做的/最大的推广平台
  • 重庆最便宜的网站建设/百度产品推广怎么收费
  • 营销型网站建设的关键特点/网络舆情应急预案