性能测试中的唯一标识问题研究

在性能测试场景中,生成全局唯一标识符(GUID)是一个常见的需求,主要用于标识每个请求或者事务,以便于追踪和分析。这是因为在性能测试中,需要对系统的各个功能进行测试,而每个功能都需要有一个唯一的标识来区分。如果不使用全局唯一标识,则可能会出现重复标识的情况,导致测试结果不准确。

相信对于性能测试er来讲这些并不陌生,特别在并发场景中使用各类的解决方案。我最近在研究 Go 语言线程安全问题的时候也被其他人问到了。所以打算单独写一写唯一标识的主题,本来打算用一篇文章解决,但是在实践中方案概述、方案实践以及性能对比几个部分,内容着实有点多。所以分成了上下两篇,本篇讲述几种常见方案的概述和代码实践,下一期我会分享几种方案的性能。

UUID(Universally Unique Identifier)

UUID(通用唯一标识符)是一种标准化的用于标识信息的方法。通常用于分布式系统中的唯一标识,以防止不同系统中的数据重复或冲突。它在数据库记录、网络通信、消息队列等方面都有广泛的应用。它是由128位二进制数表示的唯一标识符,通常以32个十六进制数字的形式表示,每四个数字之间用连字符分隔。UUID的唯一性主要基于其随机性和长度,尽管在某些情况下可能会出现重复,但重复的概率非常低。具体有多低呢,我查到资料是这么说的:每秒产生10亿笔UUID,100年后只产生一次重复的机率是50%。如果地球上每个人都各有6亿笔GUID,发生一次重复的机率是50%。。我暂时还没遇到重复的情况,各位遇到请告诉我一下概率。

由于这是个自带的包,可以使用java.util.UUID类生成UUID,例如:

  1. UUID uuid = UUID.randomUUID();

  2. String id = uuid.toString();

AI写代码

大概长这样 245fee40-8b24-47d3-b5e1-09a5e48a08d1。查阅资料过程中,还有多种版本的 UUID,不知道是不是都这个格式。我用的 JDK17,如果又不一样格式的,兴许版本不同导致的。

UUID的优点包括:

1、全局唯一性:UUID基于其128位的长度和随机性,可以在全球范围内保证唯一性,极大地减少了数据冲突的可能性。

2、无序性:UUID是无序的,不受时间和空间的限制,可以在任何地方、任何时间生成,不需要中心化管理。

3、高性能:生成UUID的速度非常快,几乎可以瞬间完成,不会造成系统性能瓶颈。

4、不可推测性:UUID是随机生成的,不可预测,可以有效防止信息被猜测或破解。

5、可扩展性:UUID采用128位的长度,可以灵活地扩展应用范围,适用于各种场景。
然而,UUID也存在一些缺点:

1、长度较长:UUID通常由32个十六进制数字和四个连字符组成,总共36个字符,相比其他标识符(如自增ID)长度较长,占用存储空间较大。

2、不易读:UUID是一串十六进制数字,对人类来说不够友好,不如自增ID那样直观易读。

3、不连续性:由于UUID是随机生成的,所以其生成的顺序是不连续的,不适合作为连续递增的标识符。

4、碰撞概率:虽然UUID的碰撞概率非常低,但随着数据量的增加,碰撞的可能性也会增加,需要进行适当的处理和预防。

UUID适用于需要全局唯一标识且不依赖于中心化管理的场景,但在某些情况下可能会受到长度、可读性和碰撞概率等因素的限制,需要根据具体情况进行选择和权衡。如果我们在性能测试结束后清理数据的话,可以很大程序降低 UUID 重复的概率。

Redis/Zookeeper等分布式服务生成GUID

在分布式系统中,能够生成全局唯一ID是一个常见且重要的需求。全局唯一ID不仅可以用于标识分布在不同节点上的数据记录,还可以用于追踪分布式事务、消息队列等场景。传统的基于数据库自增序列或UUID等方式无法满足分布式环境下的需求,因此需要借助分布式服务来实现。

利用Redis的INCR命令可以实现一个简单的分布式ID生成器。Redis是一个高性能的内存数据库,它提供了原子操作命令INCR用于对键值进行自增操作。我们可以在Redis中设置一个全局的键,每次调用INCR命令即可获取一个唯一的ID值。由于Redis是单线程处理命令,因此可以确保获取到的ID是全局唯一的。这种方式实现简单,但需要注意Redis的可用性和性能问题。

另一种方式是利用 Zookeeper 的有序临时节点特性。Zookeeper是一个分布式协调服务,它允许客户端创建有序的临时节点,节点名称是一个递增的计数器。我们可以在Zookeeper上创建一个根节点,每个客户端在该节点下创建一个有序临时节点,临时节点的名称就是一个全局唯一的ID。这种方式相对复杂,但可靠性和可用性更高,适合于关键任务型系统。

这种方式最大的缺点就是需要N多次的网络通信,即使强如 Redis 也很难提供强大的性能,所以直接再次直接放弃了。对于性能要求不甚高的场景来说还是非常好用的。同样地我在查阅资料中发现也有使用 MySQL 递增主键实现的,性能就更差了,绝对不推荐。

雪花算法

雪花算法(Snowflake)是一种用于生成分布式系统中全局唯一的ID的算法。它由Twitter公司设计,采用了时间戳、机器ID和序列号等信息,结合位运算的方式生成64位的唯一ID。其中,时间戳部分用于保证ID的唯一性和递增性,机器ID部分用于标识不同的机器,序列号部分用于解决同一毫秒内并发生成ID时的冲突。雪花算法具有高效、高性能、高可用等特点,被广泛应用于分布式系统中的ID生成。

雪花算法很大程度上弥补了 UUID 的不足,而且使用非常灵活,几十行代码即可完成,还能够根据实际场景进行定制化,受到了越来越多码农的喜欢。这里我分享一个简单的例子:

  1. package com.funtester.utils;

  2. public class SnowflakeUtils {

  3. private static final long START_TIMESTAMP = 1616489534000L; // 起始时间戳,2021-03-23 00:00:00

  4. private long datacenterId; // 数据中心ID

  5. private long workerId; // 机器ID

  6. private long sequence = 0L; // 序列号

  7. private static final long MAX_WORKER_ID = 31L;// 机器ID最大值

  8. private static final long MAX_DATA_CENTER_ID = 31L;// 数据中心ID最大值

  9. private static final long SEQUENCE_BITS = 12L;// 序列号位数

  10. private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;// 机器ID左移位数

  11. private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_SHIFT;// 数据中心ID左移位数

  12. private static final long TIMESTAMP_LEFT_SHIFT = DATA_CENTER_ID_SHIFT + DATA_CENTER_ID_SHIFT;// 时间戳左移位数

  13. private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);// 序列号掩码

  14. private long lastTimestamp = -1L;

  15. public SnowflakeUtils(long datacenterId, long workerId) {

  16. if (datacenterId > MAX_DATA_CENTER_ID || datacenterId < 0) {

  17. throw new IllegalArgumentException("Datacenter ID can't be greater than " + MAX_DATA_CENTER_ID + " or less than 0");

  18. }

  19. if (workerId > MAX_WORKER_ID || workerId < 0) {

  20. throw new IllegalArgumentException("Worker ID can't be greater than " + MAX_WORKER_ID + " or less than 0");

  21. }

  22. this.datacenterId = datacenterId;

  23. this.workerId = workerId;

  24. }

  25. /**

  26. * 获取下一个ID

  27. * * @return

  28. */

  29. public synchronized long nextId() {

  30. long timestamp = System.currentTimeMillis();

  31. if (timestamp < lastTimestamp) {

  32. throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");

  33. }

  34. if (lastTimestamp == timestamp) {

  35. sequence = (sequence + 1) & SEQUENCE_MASK;

  36. if (sequence == 0) {

  37. timestamp = nextMillis(lastTimestamp);

  38. }

  39. } else {

  40. sequence = 0L;

  41. }

  42. lastTimestamp = timestamp;

  43. long l = ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT) | (datacenterId << DATA_CENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;

  44. return l & Long.MAX_VALUE;

  45. }

  46. /**

  47. * 获取下一个时间戳

  48. *

  49. * @param lastTimestamp

  50. * @return

  51. */

  52. private long nextMillis(long lastTimestamp) {

  53. long timestamp = System.currentTimeMillis();

  54. while (timestamp <= lastTimestamp) {

  55. timestamp = System.currentTimeMillis();

  56. }

  57. return timestamp;

  58. }

  59. }

  60. ``

  61. 使用的方法如下:

  62. ```go

  63. public static void main(String[] args) {

  64. SnowflakeUtils snowflake = new SnowflakeUtils(1, 1); // 创建雪花算法实例,数据中心ID为1,机器ID为1

  65. for (int i = 0; i < 5; i++) {

  66. System.out.println("Next ID: " + snowflake.nextId());

  67. }

  68. }

结果大概长这个样子:

  1. Next ID: 3282842653393162240

  2. Next ID: 3307893926320410624

  3. Next ID: 3307893926320410625

  4. Next ID: 3307893926320410626

  5. Next ID: 3307893926320410627

AI写代码

我在 com.funtester.utils.SnowflakeUtils#nextId 方法的最后一行,加上了 l & Long.MAX_VALUE 为了获取一个正的值。

线程独享变量

在非并发场景当中,我们要想获取一个全局唯一的标识符,最简单的就是来一个 i++ ,但这样并不能保障并发场景中的线程安全。尽管如此,我们依旧可以通过之前分享过的 将共享变独享 的思路改造一下,将每一个线程都分配一个 int i ,然后在线程内 i++ 保障数值的唯一性。然后再给每一个线程进行唯一性标记,这个在之前分享线程工厂类时候提到过。如果遇到分布式场景,抄袭一下前面成熟框架的方法,增加唯一的机器码标识即可。

下面是我使用的单机版本代码:

  1. // 创建threadlocal对象

  2. static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {

  3. @Override

  4. protected Integer initialValue() {

  5. return 0

  6. }

  7. }

  8. public static void main(String[] args) {

  9. setPoolMax(3)

  10. for (int i = 0; i < 10; i++) {

  11. fun {

  12. increase()// 增加1

  13. System.out.println(Thread.currentThread().getName() + " threadLocal.get() = " + threadLocal.get());// 打印threadLocal值

  14. }

  15. }

  16. }

  17. /**

  18. * 增加1

  19. * @return

  20. */

  21. static def increase() {

  22. threadLocal.set(threadLocal.get() + 1)

  23. }

输出结果长这个样子:

  1. F-3 threadLocal.get() = 1

  2. F-2 threadLocal.get() = 1

  3. F-1 threadLocal.get() = 1

  4. F-2 threadLocal.get() = 2

  5. F-1 threadLocal.get() = 2

  6. F-3 threadLocal.get() = 2

  7. F-2 threadLocal.get() = 3

  8. F-1 threadLocal.get() = 3

  9. F-3 threadLocal.get() = 3

  10. F-2 threadLocal.get() = 4

基本是实现了设计需求。缺点就是 java.lang.ThreadLocal 可能会导致内存溢出。这一点在性能测试当中可以忽略,因为用例执行完之后,JVM自然也是要关闭的,如果是单 JVM 的性能测试服务,可以将 java.lang.ThreadLocal 对象设计成类成员属性规避内存溢出的问题。

线程共享变量

这个思路就简单了:新建一个全局线程安全的变量,每次获取一个值之后,安全地递增1,这样一下子就解决了所有问题,是所有方案里面最简单使用的。方案的代码

演示代码如下:

  1. // 定义全局变量,用于线程安全递增计数

  2. static AtomicInteger index = new AtomicInteger(0)

  3. public static void main(String[] args) {

  4. setPoolMax(3)

  5. for (int i = 0; i < 10; i++) {

  6. fun {

  7. println "递增结果: ${index.incrementAndGet()}"

  8. }

  9. }

  10. }

输出结果:

  1. 递增结果: 2

  2. 递增结果: 3

  3. 递增结果: 1

  4. 递增结果: 4

  5. 递增结果: 5

  6. 递增结果: 6

  7. 递增结果: 7

  8. 递增结果: 8

  9. 递增结果: 9

  10. 递增结果: 10

相信个性化的方案不止一种,如果你也有一些有趣的方案,欢迎一起交流分享。

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

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

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

相关文章

2026预测精度实证:全球市场实证数据与预测精度驱动因素分析

站在2026年的时点回望,企业财务管理已彻底告别了“静态手工时代”。根据麦肯锡(McKinsey)发布的《2025全球AI预算系统市场报告》(报告编号:MKT-2025-001),2025年全球AI驱动型预算管理系统市场规模已达78亿美元,…

每日面试题分享152:什么是循环依赖?

循环依赖指的是两个或多个bean互相依赖&#xff0c;形成了一个闭环。举个例子&#xff1a;a要干活需要b的工具&#xff0c;b要干活需要a的工具&#xff0c;互相依赖&#xff0c;谁也不能干活。在Spring中就是创建a时依赖b&#xff0c;因此去创建b&#xff0c;创建b时又依赖a。

每日面试题分享153:JVM垃圾回收调优的目标是什么?

JVM垃圾回收调优的目标有两个&#xff0c;低延迟和高吞吐量。但通常这两个目标是互斥的&#xff0c;需要根据业务场景做取舍。低延迟指的是每次GC的停顿时间短&#xff0c;保证系统响应能力。比如在实时交易系统、游戏、即时通讯系统中&#xff0c;对系统响应能力要求很高&…

2026年福建水凝面膜公司权威推荐榜单:紧致面膜 /修护面膜 /敏感肌面膜 /水光面膜/祛痘面膜服务机构精选

在护肤品市场,水凝面膜正从基础的补水产品,升级为承载高浓度活性成分、靶向解决特定皮肤问题的专业护理载体。据行业分析,消费者对产品的功效验证、成分安全及配方科学性的关注度,已超越单纯的感官体验和营销概念。…

GB4599-2024 落地!汽车照明迎重大升级,自适应功能 + 辅助投射成安全新标配~

一、新规来袭&#xff1a;汽车照明行业进入标准化 2.0 时代2025 年 7 月 1 日&#xff0c;GB4599-2024《汽车道路照明装置及系统》将正式实施&#xff0c;标志着我国汽车照明领域告别分散旧标&#xff0c;迈入统一规范的新阶段。该标准整合替代了 GB4599-2007 等 6 项旧标准&am…

AutoCAD二次开发――参数化绘制带轮设计

第三章 三角带轮参数化绘图设计 带轮在机械传动系统中是一种非常常见的传动件&#xff0c;所以在产品开发设计中常常需要绘制带轮零件图。为了提高带轮的设计质量和效率&#xff0c;降低设计成本和减少工人劳动强度&#xff0c;其重要途径就是开发带轮参数化绘图软件。而且它在…

每日面试题分享154:为什么Vue中的date属性是函数而不是对象?

1、避免组件实例之间共享数据2、保证组件的独立性和可复用性3、符合单向数据流理念&#xff0c;数据是可预测的

导师推荐!10个AI论文网站测评,研究生科研写作必备

导师推荐&#xff01;10个AI论文网站测评&#xff0c;研究生科研写作必备 学术写作工具测评&#xff1a;如何选择适合你的AI论文网站 在当前科研环境日益激烈的背景下&#xff0c;研究生群体对高效、专业的学术写作工具需求愈发迫切。从文献检索到论文撰写&#xff0c;再到格…

60N04NF-ASEMI中低压MOS标杆60N04NF

60N04NF-ASEMI中低压MOS标杆60N04NF编辑:ll 60N04NF-ASEMI中低压MOS标杆60N04NF 型号:60N04NF 沟道:NPN 品牌:ASEMI 封装:DFN5*6 批号:最新 导通内阻:13.5mΩ 漏源电流:60A 漏源电压:40V 引脚数量:8 特性:N…

2026 年企业微信会议采购攻略 专属购买电话快速对接

在数字化转型加速的背景下,企业如何通过高效会议工具提升协作效率?面对市场上众多音视频会议解决方案,企业如何筛选出既满足功能需求又具备成本优势的产品?本文将结合企业微信的实际应用案例,为企业提供采购决策的…

救命神器9个一键生成论文工具,继续教育学生轻松搞定论文!

救命神器9个一键生成论文工具&#xff0c;继续教育学生轻松搞定论文&#xff01; AI 工具如何成为论文写作的得力助手 在当前继续教育学生面临论文写作压力日益增大的背景下&#xff0c;AI 工具逐渐成为不可或缺的辅助工具。这些工具不仅能够帮助用户快速生成内容&#xff0c;还…

FPGA实现数字噪声发生器:探索灵活的噪声生成世界

FPGA数字噪声发生器&#xff0c;数字噪声源&#xff0c;数字噪声产生&#xff0c;噪声源&#xff0c;宽带噪声源&#xff0c;AWGN加噪&#xff0c;噪声带宽可调&#xff0c;幅频特性可任意校正 在数字信号处理和通信等众多领域中&#xff0c;噪声源有着广泛的应用。今天咱们就…

2026年江苏律师事务所推荐:天听所、民商律师事务所、交通事故律师、劳动纠纷律师、工伤纠纷律师事务所、一站式法律服务新标杆

随着法治建设不断深化,企业合规需求升级与个人法律纠纷多元化趋势凸显,2026年江苏法律服务市场规模持续扩容。江苏省司法厅、省工信厅联合启动优化企业法治建设三年行动,推动法律服务机构向专业化、精准化转型。但市…

Java程序员必会SpringBoot进阶骚操作都在这里了!

相信从事Java开发的朋友都听说过SSM框架&#xff0c;老点的甚至经历过SSH&#xff0c;说起来有点恐怖&#xff0c;比如我就是经历过SSH那个时代未流。当然无论是SSM还是SSH都不是今天的重点&#xff0c;今天要说的是Spring Boot&#xff0c;一个令人眼前一亮的框架&#xff0c;…

真心建议大家去看Google《Al Agent》真的能颠覆你的认知!!

我被Google最新手册震撼到了&#xff01; 真心建议大家去看Google《Al Agent》真的能颠覆你的认知&#xff01;&#xff01;我已将其拆解整理成了飞书文档&#xff0c;从Agent理论知识—全流程搭建—企业落地应用&#xff0c;无论你是开发者、研究者&#xff0c;还是对AI充满好…

使用zigbee2mqtt模拟发送信息

1、创建 iot-network 网络root@DXP4800PRO-720C:/volume1/docker# docker network create iot-network 9a4b69c7eadb561fff3c2cf2d336a74ccf84d5a64d249bdb8b8427c3226a46b6 root@DXP4800PRO-720C:/volume1/docker# do…

区域代理机制:区县到市级代理如何分层管理?

当线上裂变覆盖全国后&#xff0c;线下区域的精细化运营就成为必然。本文将解读体系如何通过“区域代理机制”&#xff0c;实现从线上流量到线下深耕的完美结合。1. 为何需要区域代理&#xff1f;线上线下的互补纯线上模式难以解决本地化服务、深度信任和特定资源对接的问题。区…

2026全面预算管理系统公司:AI驱动实践指南

2025年全球市场实证数据与预测精度驱动因素分析 站在2026年的时点回望,企业财务管理已彻底告别了“静态手工时代”。根据麦肯锡(McKinsey)发布的《2025全球AI预算系统市场报告》(报告编号:MKT-2025-001),2025年…

传统行业Java程序员未来何去何从?

近两年&#xff0c;“大厂裁员”总是凭实力冲上各大媒体头条&#xff0c;身在局中的我们早已习以为常。国内的京东&#xff0c;阿里&#xff0c;腾讯&#xff0c;字节&#xff0c;快手&#xff0c;小米等互联网公司都以不同程度的裁员比例向社会输送人才。大量有大厂经验的卷王…

Java程序员简历加分项:高并发经验必不可少!

如何获得高并发经验&#xff1f; 这是我今天系统邀请我回答的一个问题&#xff0c;由此也引发了我的一些思考&#xff1a;为什么人人都想要获得高并发经验&#xff1b;想拥有高并发系统设计技能&#xff1f; 其原因LZ认为主要有以下三点&#xff1a; 涨薪&#xff1a;有高并发…