欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

分布式UUID的生成

程序员文章站 2022-12-23 17:32:54
1.通过JDK标准API?UUID会重复 要生成UUID,大多会直接使用下面这句: 在多数情况,这样的处理是没问题的,毕竟是JDK标准接口。但是在某些情况下,会出现重复。搜素 uuid 重复 ,就会发现有人踩到了雷 先看UUID各版本的实现原理:Universally unique identifi ......

1.通过jdk标准api?uuid会重复

要生成uuid,大多会直接使用下面这句:

uuid.randomuuid().tostring().replace("-", "");

在多数情况,这样的处理是没问题的,毕竟是jdk标准接口。但是在某些情况下,会出现重复。搜素 uuid 重复,就会发现有人踩到了雷

先看uuid各版本的实现原理:universally unique identifier

再看jdk的实现(只实现了uuid的1,3,4版本)java.util.uuid

会发现在分布式场景下jdk自带的这个工具类并不好用。原因:

  • 会存在多台web容器在同1个物理/云主机上,mac地址相同。因此,版本1的uuid,不合适
  • randomuuid实现的是uuid的版本4,产生重复的概率是可以计算出来的,海量存储时,重复不可避免。这也是有人踩雷的原因
  • nameuuidfrombytes实现的是uuid的版本3,保证种子的唯一性才能确保生成的uuid唯一。在分布式的场景下,如果我们每次都能获取到唯一的种子,那也就不必用这个方法生成uuid了

2.数据库获取uuid?性能不能保证

通过这种消耗大量性能来获取uuid,当然可行,但在高并发的场景下你真的会去考虑吗?

3.分布式uuid的生成

分布式?多台web容器(我们可以称之为实例)在同1个机器(mac地址相同)下?不依赖第3方工具?最好在jvm解决?

思路

  • 确保每台实例具有唯一的名字(我们可以称之为实例名)

  • 确保某台实例生成的uuid不会重复: 当前系统时间 + 递增的数值(避免高并发的影响)

因此,算法如下:

uuid = 实例名 + 当前系统时间毫秒数 + 递增的int数

方法

  1. 对每台web容器的java_options配置不一样的实例名

    以tomcat(8.0.53)为例,在startup.bat里配置:

    rem to set java_opts
    set "java_opts=%java_opts% -dinstance.name=cico-mba"

    这样,上文的instance.name,就变成了jvm里的1个参数了

  2. 代码实现

    package java.main;
    
    import java.util.concurrent.atomic.atomicinteger;
    
    public class uuidutil {
    
        /* 从当前web容器的java_options中,获取jvm的配置:当前实例名 */
        private static final string instance_name = system.getproperty("instance.name");
        /* 实例名脱敏后的值 */
        private static string instance_name_by_num = null;
        /* 计数器。atomicinteger是java.util.concurrent下的类,jdk的算法工程师会避免并发问题 */
        private static atomicinteger cnt = new atomicinteger(0);
    
        /**
         * 初始化instance_name_by_num。需考虑并发
         */
        private synchronized static void initinstancenamebynum() {
            if (null != instance_name_by_num) {
                return;
            }
            char[] chars = instance_name.tochararray();
            stringbuilder sb = new stringbuilder();
            for (char c : chars) {
                sb.append((int) c);
            }
            instance_name_by_num = sb.tostring();
        }
    
        /**
         * 生成分布式的uuid
         * 
         * @return
         */
        public static string getconcurrentuuid() {
            if (null == instance_name) {
                return "the jvm option is null, named 'instance.name'";
            }
            if (null == instance_name_by_num) {
                initinstancenamebynum();
            }
            stringbuilder uuid = new stringbuilder();
            uuid.append(instance_name_by_num);
            uuid.append(system.currenttimemillis());
            uuid.append(cnt.incrementandget());
            return uuid.tostring();
        }
    }   

说明

通过上文的方法可在jvm内快速生成支持分布式的uuid。这个uuid的长度,由下面3部分组成:

  • 13: system.currenttimemillis()的长度是13位
  • 11: integer.min_value的长度。int值从0开始递增,达到int的上限后,会从负数开始重新计数,因此长度最大是11位
  • 2 * 实例名的字符数: 实例名一般由字母、数字、小数点、减号、下划线组成,这些字符的ascii码值是2位

如果这个uuid需要持久化,持久化的字段可定义成varchar2(255),其中实例名的字符长度最大可以是115 = ( 255 - 13 - 11 ) / 2