分布式系列之ID生成器

背景

在分布式系统中,当数据库数据量达到一定量级后,需要进行数据拆分、分库分表操作,传统使用方式的数据库自有的自增特性产生的主键ID已不能满足拆分的需求,它只能保证在单个表中唯一,所以需要一个在分布式环境下都能使用的全局唯一ID。

应用场景

  • 用户ID、图片ID等各种业务场景
  • 分库分表情况下的订单号
  • 分布式链路追踪系统中的TraceId

需求分析:

  • 可靠性:全局唯一性,不能生成重复的ID,最基本的要求
  • 安全性:保证数据安全,防止恶意用户分析出ID生成规则,进而获取到系统业务信息
  • 递增性:下一个时间点产生的ID大于前一个时间点的ID
  • 时间有序:以时间为序,或ID里包含时间。表设计时可以不用考虑再添加一个时间字段,也方便冷热数据分离
  • 可读性:生成的ID应该有业务意义
  • 长度适中:不要太长,太长则数据表存储不便,可使用Long类型(String也行)
  • 分片支持:可以控制ShardingId。如某一个用户的文章要放在同一个分片内,这样查询效率高,修改也容易
  • 高可用:不能出现单点故障
  • 高性能:响应速度快,毫秒内生成的ID数量要满足海量用户请求
  • 扩展性:ID生成器服务集群发生节点宕机,加入新节点是否便捷
  • 可维护性:实现方案不能太复杂,方便后期维护

上面列举出11个需求点,已经足够多,当然也可以再增加。安全性和递增性之间存在一定的互斥,需要做取舍。递增性不强求绝对严格递增,即不需要满足+1递增。在做方案设计时,需要具体情况具体分析,前面几个是必须要满足。大体而言,首先确保满足前面几个需求点,即优先级更高,再考虑后面几个。能满足的需求点越多,则方案越复杂,需要加以权衡和取舍,不强求完全实现所有的需求点。

实现

实现方案有很多,不是每种方案都能完美实现上面提到的各个需求点:

  • 数据库
  • UUID
  • Snowflake
  • Redis
  • ZooKeeper
  • Snowflake-like

数据库

基于数据表auto increment规则来生成全局唯一递增ID。

优点:简单,可保证唯一性、递增性,步长固定

缺点:

  • 可用性:不高,数据库常见架构是一主多从+读写分离,生成自增ID是写请求,主库宕机,则服务不可用。
  • 扩展性:较差,性能有限,写入是单点,主库的写性能决定ID生成性能上限,且难以扩展
  • 兼容性:不同数据库语法和实现不同,数据库迁移时或多数据库版本支持时需要特殊处理

改进方法:冗余主库,避免写入单点;数据水平切分,保证各主库生成的ID不重复。

具体来说,比如可将1个写库变成N个写库,每个写库设置不同的auto increment初始值,和相同的步长,以保证每个数据库生成的ID是不同的。

改进后方案可提高可用性,但拓展性差的问题依旧存在。数据库写压力有所缓解,但写压力依旧存在;可考虑一次性从DB里取出多个ID放在Redis缓存里。

UUID

Universally Unique Identifier,标准型式包含32个16进制字符,以连字号分为五段,其形式为8-4-4-4-12,到目前为止业界一共有5种方式生成UUID,参考IETF发布的UUID规范:

  • 版本1 - 根据时间和节点 ID(通常是MAC地址)生成;
  • 版本2 - 根据标识符(通常是组或用户ID)、时间和节点ID生成;
  • 版本3、版本5 - 确定性UUID,通过散列名字空间标识符和名称生成;版本5和3的区别在于使用不同的散列算法;
  • 版本4 - 使用随机性或伪随机性生成。

v1

UUID-v1是通过使用主机MAC地址和当前日期和时间的组合生成的。之外还引入另一个随机组件,以确保其唯一性。但是如果使用同一台机器、同时时间生成UUID,会有很小的几率重复。

UUID-v1存在的问题是:

  • 存在重复几率
  • 根据ID能推算出创建时的相对时间
  • 根据ID能推算出创建的机器唯一标识

v2

UUID-v2和v1很类似,是根据标识符(通常是组或用户ID)、时间和节点ID生成,区别在于v2将v1中的部分时间信息换成主机名, 存在隐私风险,未大规模使用。

v3

UUID-v3通过MD5散列算法基于命名空间标识符和名称生成UUID。和v1、v2不同,v3不依赖与机器信息和时间信息,但v3要求输入命名空间+名称,命名空间本身也是一个UUID,用来标识应用环境,名称通常是用户账号、用户名之类的内容,通过命名空间+名称+三列算法算出UUID。

UUID-v5和v3类似,区别在于使用sha1散列算法。

v4

基于随机数的算法。用SecureRandom生成16个随机的Byte,用2个long来存储。记得加-Djava.security.egd=file:/dev/./urandom

JDK里UUID.randomUUID静态方法使用加密强度高的伪随机数生成器生成v4伪随机UUID:

public static UUID randomUUID() {SecureRandom ng = Holder.numberGenerator;byte[] randomBytes = new byte[16];ng.nextBytes(randomBytes);randomBytes[6]  &= 0x0f;  /* clear version        */randomBytes[6]  |= 0x40;  /* set to version 4     */randomBytes[8]  &= 0x3f;  /* clear variant        */randomBytes[8]  |= (byte) 0x80;  /* set to IETF variant  */return new UUID(randomBytes);
}

JDK提供UUID.randomUUID静态方法从字节数组生成基于名称的v3版本的UUID:

public static UUID nameUUIDFromBytes(byte[] name) {MessageDigest md;try {md = MessageDigest.getInstance("MD5");} catch (NoSuchAlgorithmException nsae) {throw new InternalError("MD5 not supported", nsae);}byte[] md5Bytes = md.digest(name);md5Bytes[6]  &= 0x0f;  /* clear version        */md5Bytes[6]  |= 0x30;  /* set to version 3     */md5Bytes[8]  &= 0x3f;  /* clear variant        */md5Bytes[8]  |= (byte) 0x80;  /* set to IETF variant  */return new UUID(md5Bytes);
}

v1变种

Hibernate

基于hibernate-core-6.4.4.Final版本的CustomVersionOneStrategy源码如下:

public class CustomVersionOneStrategy implements UUIDGenerationStrategy, UuidGenerator.ValueGenerator {private final long mostSignificantBits;public int getGeneratedVersion() {return 1;}public CustomVersionOneStrategy() {byte[] hiBits = new byte[8];System.arraycopy(Helper.getAddressBytes(), 0, hiBits, 0, 4);System.arraycopy(Helper.getJvmIdentifierBytes(), 0, hiBits, 4, 4);hiBits[6] = (byte)(hiBits[6] & 15);hiBits[6] = (byte)(hiBits[6] | 16);this.mostSignificantBits = BytesHelper.asLong(hiBits);}public UUID generateUuid(SharedSessionContractImplementor session) {long leastSignificantBits = generateLeastSignificantBits(System.currentTimeMillis());return new UUID(this.mostSignificantBits, leastSignificantBits);}public UUID generateUUID(SharedSessionContractImplementor session) {return this.generateUuid(session);}public long getMostSignificantBits() {return this.mostSignificantBits;}public static long generateLeastSignificantBits(long seed) {byte[] loBits = new byte[8];short hiTime = (short)((int)(seed >>> 32));int loTime = (int)seed;System.arraycopy(BytesHelper.fromShort(hiTime), 0, loBits, 0, 2);System.arraycopy(BytesHelper.fromInt(loTime), 0, loBits, 2, 4);System.arraycopy(Helper.getCountBytes(), 0, loBits, 6, 2);loBits[0] = (byte)(loBits[0] & 63);loBits[0] = (byte)(loBits[0] | 128);return BytesHelper.asLong(loBits);}
}

解读:

MongoDB

MongoDB的bson-4.11.1版本下ObjectId的源码:

public final class ObjectId implements Comparable<ObjectId>, Serializable {private static final AtomicInteger NEXT_COUNTER = new AtomicInteger((new SecureRandom()).nextInt());public ObjectId() {this(new Date());}public ObjectId(Date date) {this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & 16777215, false);}private static int dateToTimestampSeconds(Date time) {return (int)(time.getTime() / 1000L);}
}

解读:

  • 16777215:即LOW_ORDER_THREE_BYTES,一个24位(3字节)的整数,其十六进制表示为0xFFFFFF。机器标识符是一个3字节的值,而16777215是3字节整数的最大值。这意味着机器标识符的范围是0到16777215,确保可以使用一个唯一的标识符来表示每台机器。
  • SecureRandom:和JDK UUID一样使用SecureRandom来生成强随机数。

ObjectId使用12字节的存储空间:

  • 前4个字节表示时间戳,秒级别
  • 随后3个字节是机器标识码
  • 随后2个字节由进程id组成,同一台机器上可能会运行多个mongod实例,因此也需要加入进程标识符PID
  • 最后3个字节是随机数

前9个字节保证同一秒钟不同机器不同进程产生的ObjectId的唯一性。后三个字节是一个自动增加的计数器(一个mongod进程需要一个全局的计数器),保证同一秒的ObjectId是唯一的。同一秒钟最多允许每个进程拥有(256^3=16777216)个不同的ObjectId。

ObjectId用于文档的主键_id字段,_id可在服务端、客户端生成,在客户端生成可以降低服务器端的压力。

总结

缺点:

  • 不易于存储:UUID太长,以36个字符串(加上4个连字符)表示;不适合作为数据表主键,不利于建索引,UUID的无序性可能会引起数据位置频繁变动,严重影响性能
  • 没有排序:无法保证趋势递增
  • 可读性不好
  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置

优点:性能非常高,本地生成,没有远程调用等网络消耗,时延低。

标准的UUID算法使用场景不多,改进版如MongoDB的ObjectId,可用于生产实践中。

Snowflake

参考GitHub。Twitter在把存储系统从MySQL迁移到Cassandra的过程中,由于Cassandra没有顺序ID生成机制,于是自己开发一套全局唯一ID生成服务。

共64位二进制:

  • 第1位固定为0,没有业务含义,符号位
  • 第2~42位,共41位,为时间戳位,用于存入精确到毫秒数的时间
  • 第43~52位,共10位,为机器ID位,其中高位5bit是数据中心ID(dataCenterId),低位5bit是工作节点ID(workerId)
  • 第53~64位,共12位,代表1ms内可以产生的序列号,取值区间为[0,4095],也就是说在数据中心ID和机器ID相同的情况下,1ms最多可以生成4096个序列号

如果序列号超过最大值,则会将程序阻塞到下一毫秒,然后序列号归零,继续生成ID。要想Snowflake生成全局唯一的ID,则ID生成器必须也是全局单例。

Snowflake对ZooKeeper的依赖性:集群节点启动时,从一个ZooKeeper集群获取,保证所有节点不会有重复的机器号

代码:

public class SnowflakeIdWorker {/*** 机器id所占的位数*/private final long workerIdBits = 5L;/*** 数据标识id所占的位数*/private final long datacenterIdBits = 5L;/*** 工作机器ID(0~31)*/private final long workerId;/*** 数据中心ID(0~31)*/private final long datacenterId;/*** 序列ID位数*/private static final long sequenceBits = 12L;/*** 毫秒内序列(0~4095)*/private long sequence = 0L;/*** 上次生成ID的时间截*/private long lastTimestamp = -1L;/*** 构造函数*/public SnowflakeIdWorker(long workerId, long datacenterId) {// 支持的最大机器id,结果是31 (这个移位算法可以很快地计算出几位二进制数所能表示的最大十进制数)long maxWorkerId = ~(-1L << workerIdBits);if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}// 支持的最大数据标识id,结果是31long maxDatacenterId = ~(-1L << datacenterIdBits);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;}/*** 获得下一个ID(线程安全)*/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) {// 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)long sequenceMask = ~(-1L << sequenceBits);sequence = (sequence + 1) & sequenceMask;// 毫秒内序列溢出if (sequence == 0) {// 阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}} else {// 时间戳改变,毫秒内序列重置sequence = 0L;}// 上次生成ID的时间截lastTimestamp = timestamp;// 移位并通过或运算拼到一起组成64位的ID// 开始时间截(2024-01-01)long startEpoch = 1704038400000L;// 数据标识id向左移17位(12+5)long datacenterIdShift = sequenceBits + workerIdBits;// 时间截向左移22位(5+5+12)long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;return ((timestamp - startEpoch) << timestampLeftShift)| (datacenterId << datacenterIdShift)| (workerId << sequenceBits)| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳** @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间*/protected long timeGen() {return System.currentTimeMillis();}
}

如何分配datacenterId和workerId呢?

  • 写死 : 单机部署,然后写死两个值,不可取
  • 读配置文件 : 将值放在配置中心,应用启动时读取
  • 动态分配 :

存在的问题:

  • 时间戳只存在41位二进制,只能使用69年,69年后就可能产生重复ID
  • 如果机器性能足够好,每秒可以产生超过400万个ID,但是对于大部分企业来说,只需要每秒满足数万个ID即可。这种高性能浪费的主要是序号的二进制位,实际上,二进制位达到9位,就可以产生512个序号,如果机器性能足够,就可以每秒产生超过50万的ID,能满足绝大部分企业的需要
  • 从机器位来说,因为去中心化是分布式和微服务的趋势,所以在实现时,并未考虑受理机器编号,这样就会造成机器位数有10位二进制,可以表达区间[0, 1023]的整数。如果数据中心预估总共只有几十台机器,显然也会造成二进制位的浪费。

时钟回拨问题
获取到的当前Timestamp比前一个已生成ID的Timestamp还要小。做法是while循环,继续获取当前机器的时间,直到获取到更大的Timestamp才能继续工作,在这个等待过程中不能分配出新的ID。

解决方法
使用NTP(Network Time Protocol)确保系统时钟是准确的。最好把NTP配置成不会向后调整的模式。即NTP纠正时间时,不会向后回拨机器时钟。

Redis

基于Redis的原子操作INCR和INCRBY来实现,与数据库改进版类似,Redis采用集群化部署方案,每个节点设置一个不同的初始值,步长保持一致。且可以预生成一批ID,提高性能。

ZooKeeper

Snowflake改进

业界最常用的解决方案是基于Snowflake的改进版。

Boundary flake

GitHub:

Flake: A decentralized, k-ordered id generation service in Erlang

几点变化:

  • ID长度扩展到128位
  • 最高64位时间戳
  • 48位的Worker ID,和Mac地址一样长,启动时无需和ZooKeeper通讯获取Worker ID,做到完全去中心化
  • 16位的Seq Number
  • 基于Erlang

目的是用更多的位实现更小的冲突概率,且能支持更多的Worker同时工作,每毫秒能分配出更多的ID。

Instagram

Instagram的分布式存储方案:

  • 先把每个Table划分为多个逻辑分片(Logic Shard,简称LS)
  • 制定规则,每个LS被存储到哪个数据库实例上,数据库实例不需要很多。例如有2个PostgreSQL实例的系统,可将奇数逻辑分片存放到第一个数据库实例,偶数放到第二个
  • 每个Table指定一个字段作为分片字段,如用户表可指定uid作为分片字段
  • 插入一个新的数据时,先根据分片字段的值,决定数据被分配到哪个逻辑分片
  • 然后再根据逻辑分片和PostgreSQL实例的对应关系,确定这条数据应该被存放到哪台PostgreSQL实例上

Instagram unique ID组成:

  • 41位: 精确到毫秒的Timestamp,和Snowflake类似
  • 13位: 每个Logic Shard的代号,最大支持 2 13 2^{13} 213个LS
  • 10位: Sequence Number,简称SN,每个Shard每毫秒最多可以生成1024个ID

SN利用PostgreSQL表的自增序列(sequence)来生成:如果当前表上已经有5000条记录,则这个表的下一个自增序列就是5001(直接调用PG提供的方法可以获取到),然后把这个5001对1024取模就得到10位的SN。

优势在于:

  • 利用LS号来替换Snowflake使用的Worker号,就不需要到中心节点获取Worker号,做到完全去中心化
  • 通过ID可直接知道这条记录被存放在哪个LS上;数据迁移时,也是按LS为单位做数据迁移

开源

Leaf

美团-点评开源的Leaf。

UidGenerator

百度开源的UidGenerator。

Flyway

Flyway是一款开源的数据库版本管理工具,其源码里有对Snowflake的支持:
在这里插入图片描述
有待进一步研究。

其他

https://github.com/zhuzhong/idleaf

参考

  • 分布式系统Unique ID生成方法
  • UUID版本指南

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

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

相关文章

昇思25天学习打卡营第23天 | 基于MindSpore的红酒分类实验

学习心得&#xff1a;基于MindSpore的红酒分类实验 在机器学习的学习路径中&#xff0c;理解和实践经典算法是非常重要的一步。最近我进行了一个有趣的实验&#xff0c;使用MindSpore框架实现了K近邻&#xff08;KNN&#xff09;算法进行红酒分类。这个实验不仅加深了我对KNN算…

idea如何让包结构分层

文章目录 前言1.选中前项目包结构2.取消后项目包结构3.情况二 前言 在大型项目中&#xff0c;代码的分层管理至关重要。IDEA编辑器提供了强大的package分层结构功能&#xff0c;帮助开发者更好地组织和管理代码。通过合理配置&#xff0c;我们可以清晰地看到各个package之间的…

stm32平台为例的软件模拟时间,代替RTC调试

stm32平台为例的软件模拟时间&#xff0c;代替RTC调试 我们在开发项目的时候&#xff0c;如果用到RTC&#xff0c;如果真正等待RTC到达指定的时间&#xff0c;那调试时间就太长了。 比如每隔半个小时&#xff0c;存储一次数据&#xff0c;如果要观察10次存储的效果&#xff0…

在服务器调用api操作rabbitmq

不同的rabbitmq版本可能api不同&#xff0c;仅做参考&#xff0c;RabbitMQ 3.7.18。同时&#xff0c;我基本没看官方api文档&#xff0c;根据rabbitmq客户端控制台调用接口参数来决定需要什么参数。例如&#xff1a; 1、添加用户 curl -u 用户名:密码 -H “Content-Type: a…

蓝屏死机不再怕!CrowdStrike故障修复指南中心上线!

系统之家于7月22日发出最新报道&#xff0c;安全公司CrowdStrike因其Windows更新引发全球 850 万台电脑蓝屏死机问题后&#xff0c;上线了全新的“修复和指南中心”&#xff08;Remediation and Guidance Hub&#xff09;&#xff0c;该中心汇集了与其错误更新相关的详细信息&a…

Android音视频—OpenGL 与OpenGL ES简述,渲染视频到界面基本流程

文章目录 OpenGL 简述特点和功能主要组件OpenGL ES当前状态 OpenGL ES 在 Android 上进行视频帧渲染总体流程 OpenGL 简述 OpenGL&#xff08;Open Graphics Library&#xff09;是一个跨平台的、语言无关的应用程序编程接口&#xff08;API&#xff09;&#xff0c;用于开发生…

基于FPGA的数字信号处理(18)--半加器和全加器

前言 在数字系统中&#xff0c;加法运算是最常见的算术运算&#xff0c;同时它也是进行各种复杂运算的基础。 半加器 最简单的加法器叫做 半加器&#xff08;Half Adder&#xff09;&#xff0c;它将2个输入1bit的数据相加&#xff0c;输出一个2bits的和&#xff0c;和的范围为…

航片转GIS数据自动化管线

近年来&#xff0c;计算机视觉领域的进步已显著改善了物体检测和分割任务。一种流行的方法是 YOLO&#xff08;You Only Look Once&#xff09;系列模型。YOLOv8 是 YOLO 架构的演进&#xff0c;兼具准确性和效率&#xff0c;是各种应用的绝佳选择&#xff0c;包括分割卫星航拍…

抖音短视频seo矩阵系统源码开发技术分享(二)--SaaS开源

目录 市场背景分析 一、抖音短视频seo矩阵系统开发部署流程 二、 源码开发功能构思 三、 抖音短视频seo源码开发部署注意事项 四、 部分开发代码展示 市场背景分析 抖音短视频seo矩阵系统是通过不同平台不同账号之间建立联系&#xff0c;通过将同一品牌下不同平台不同账号…

currentTarget和target

*.wxml *.js 点击按钮 发现 currentTarget 获取的是事件绑定者的参数 target 获取的是事件触发者的参数

ZYNQ 入门笔记(零):概述

文章目录 引言产品线Zynq™ 7000 SoCZynq UltraScale™ MPSoCZynq UltraScale RFSoCVersal™ Adaptive SoC 开发环境 引言 Xilinx FPGA 产品线从经济型的 Spartan、Artix 系列到高性能的 Kintex、Virtex、Versal 系列&#xff0c;可以说涵盖了 FPGA 的绝大部分应用场景&#x…

【iOS】内存对齐

内存对齐 OC基本数据类型所占字节数对比 注1&#xff1a;BOOL在32位机器被定义为char、在64位机器被定义为bool boolean_t在32位机器被定义为unsigned int、在64位机器被定义为int NSInteger在32位机器被定义为int、在64位机器被定义为long NSUInteger在32位机器被定义为unsig…

公司技术栈用到了RocketMQ,我对此块知识进行了回顾(初始RocketMQ)

前言 作为24届的校招生&#xff0c;不知道大伙儿们是否都已经到了工作岗位上。为了以后更方便的接触到公司的业务&#xff0c;我司为我们安排了将近一个月的实操。虽然不用敲代码&#xff0c;但是… 了解到我司使用到的技术栈&#xff0c;在空闲时间正好对RocketMQ这块技术做个…

服务器配置两个默认网关必须配置路由优先级

背景 对于具备多网口的服务器来说&#xff0c;启用多个网口很正常&#xff0c;正常情况下应该只有一个默认网关&#xff0c;其他网口配置明细路由&#xff0c;如果将服务器做为软路由&#xff0c;并且有两个外网网络&#xff0c;1主1备&#xff0c;则会需要配置网关默认网关&am…

C++笔试强训7

文章目录 一、选择题1-5题6-10题 二、编程题题目一题目二 一、选择题 1-5题 基础知识&#xff0c;函数代码少&#xff0c;频繁调用的时候才适合定义内联函数。 故选C。 在C中&#xff0c;inline关键字是用来向编译器建议将函数体在每个调用点“内联展开”的。这意味着编译器会…

前端表格解析方法

工具类文件 // fileUtils.tsimport { ref } from vue; import * as xlsx from xlsx;interface RowData {[key: string]: any; }export const tableData ref<RowData[]>([]);export async function handleFileSelect(url: string): Promise<void> {try {const res…

《无线互联科技》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《无线互联科技》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《无线互联科技》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;江苏省科学技术厅 主办单位&#xff1a…

浅谈断言之MD5Hex断言

浅谈断言之MD5Hex断言 “MD5Hex断言”是一种特殊类型的断言&#xff0c;主要用于验证返回数据的完整性和一致性。本文将详细介绍MD5Hex断言的用途、配置方法及应用场景。 MD5Hex断言概述 MD5Hex断言基于MD5&#xff08;Message-Digest Algorithm 5&#xff09;算法&#xff…

Nexus3 批量上传 jar 包、pom文件

Nexus3 Maven 私服搭建及各种使用 详见**Maven私服搭建及各种使用汇总2020** Maven 配置 Nexus 私服 在 Maven 项目中配置 Nexus 私服&#xff0c;需要在项目的 pom.xml 或 maven 的 settings.xml 文件中添加 Nexus 仓库的配置。 示例&#xff1a; 以下是一个项目的 pom.xml…

JDK8升级到JDK17,报错Error:java:错误:不支持的发行版本5

1 问题描述&#xff1a; 我原来用到是JDK8,后来重新安装了JDK17后&#xff0c;并更换了JAVA_HOME的配置&#xff0c;在CDM上面查看JAVA版本确认安装无误。 当我打开IDEA运行代码时&#xff0c;就报错java&#xff1a;错误&#xff1a;不支持的发行版本5&#xff0c;至始至终我都…