【分布式专题】分布式唯一ID方案之UUID

74 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

UUID(Universally Unique IDentifier) 是128位的全局唯一标识符,通常由32字节的字符串表示。它可以保证时间和空间的唯一性,也称为 GUID(Globally Unique IDentifier)。

它通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生成ID的唯一性。

不一定分布式唯一ID一定要使用雪花算法或者其变种来生成,UUID也有其应用场景。比如在数据量不大的多个数据库之间,例如100w数据以内,那么可以考虑使用UUID进行分布式ID生成。虽然会导致像传统关系型数据库MySQL的InnoDB引擎对于自增主键的分页查询失效,但是基于整体分布式ID维护的成本降低和UUID本身生成的高效率而言,其实是可以接受的。其实像Linux系统中磁盘序列号就是采用UUID生成的,这证明UUID本身并没有问题,但是得用在正确的场景。

而引入雪花算法,就不得不考虑时钟回拨问题;有采用NTP时钟同步的方案,还有采用ZK来维护唯一ID生成服务的,这些只会让系统变得更为复杂。因此,随着技术年龄变长以后,慢慢发现:其实任何技术的引入的都是有优缺点和代价的,因此才会有《人月神话》中没有"银弹"的说法,可惜的是很多技术人到了专家级别,去主动接触架构和系统设计时才意识到这一点。

每一个技术栈的使用过程中,都需要有自己的思考,跟风使用网络上的各种"踩一捧一"教程只会丧失自己对于技术的独立思考。

下面我们来介绍UIID的使用教程:

直接使用

package com.deepinsea.common.utils;
​
import java.util.UUID;
/**
 * uuid唯一主键生成工具
 * @author deepinsea
 */
public class UUIDTools {
    public static void main(String[] args) {
        UUID uuid = UUID.randomUUID();
        System.out.println(uuid);//e7375574-d87f-43b0-93f5-5915d58d960f
        String dxmbid = uuid.toString().replace("-", "");
        System.out.println(dxmbid);//e7375574d87f43b093f55915d58d960f
        System.out.println(dxmbid.length());//32
    }
​
}

还有其他生成方式

public class IDGenerator {
   
  private static long num=0; 
   
  /**
   * 随机生成UUID
   * @return
   */
  public static synchronized String getUUID(){
    UUID uuid=UUID.randomUUID();
    String str = uuid.toString(); 
    String uuidStr=str.replace("-", "");
    return uuidStr;
  }
  /**
   * 根据字符串生成固定UUID
   * @param name
   * @return
   */
  public static synchronized String getUUID(String name){
    UUID uuid=UUID.nameUUIDFromBytes(name.getBytes());
    String str = uuid.toString(); 
    String uuidStr=str.replace("-", "");
    return uuidStr;
  }
  /**
   * 根据日期生成长整型id
   * @param args
   */
  public static synchronized long getLongId(){
    String date=DateUtil.getDate2FormatString(new Date(), "yyyyMMddHHmmssS");
    System.out.println("原始id="+date);
    if(num>=99) num=0l;
    ++num;
    if(num<10) {
      date=date+00+num;
    }else{
      date+=num;
    }
    return Long.valueOf(date);
  }
}

生成方式

用户ID首先生成,订单ID的生成可依赖用户ID。

下面代码前六位是日期,后八位是随机数,用于生成用户ID。

public String getNewUserId() {
    String ipAddress = "";
        try {
           //获取服务器IP地址
           ipAddress = InetAddress.getLocalHost().getHostAddress();
             } catch (Exception e) {
           logger.error("getNewUserId=" + e.getMessage());
         }
      //获取UUID
        String uuid = ipAddress + "$" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
       //生成后缀
     long suffix = Math.abs(uuid.hashCode() % 100000000);
     SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
     String time = sdf.format(new Date(System.currentTimeMillis()));
      //生成前缀
     long prefix = Long.parseLong(time) * 100000000;
     String userId = String.valueOf(prefix + suffix);
       return userId;
  }

接下来的订单ID就可以随意点了,可添加自定义前缀等。

public String buildOrderIdByUserId(String userId) {
    SimpleDateFormat df = new SimpleDateFormat("yyMMddHHmmss");
    String time = df.format(new Date());
    Random random = new Random();
    int randomNum = random.nextInt(999) % 900 + 100;
    StringBuilder sb = new StringBuilder("ZMM");
    return sb.append(time).append(userId).append(randomNum).toString();
}

如果还有相应的月份分表,之后就可以根据用户ID得到该用户所在月表,根据该订单得到该订单所在月表,直接截取相应ID的固定位置即可

比如:

public String getMonthByOrderId(String orderId) {
        return "20" + orderId.substring(3, 7);
    }

时间字符串可以使用JDK8新增的线程安全的时间格式化类来生成:

        LocalDateTime localDateTime = LocalDateTime.now();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        String dateStr = dateTimeFormatter.format(localDateTime);
        System.out.println(dateStr);

下面是实际项目中使用到的代码:

    /**
     * 根据指定前缀生成ID
     */
    public static String generateId(String preStr){
        if (StringUtils.isEmpty(preStr)){
            logger.error("buildId preStr is error, preStr = " + preStr);
            return null;
        }
        //获取UUID
        String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
        //生成后缀
        long suffix = Math.abs(uuid.hashCode() % 100000000);
        SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
        String time = sdf.format(new Date(System.currentTimeMillis()));
        //生成前缀
        long prefix = Long.parseLong(time) * 100000000;
        String userId = preStr + String.valueOf(prefix + suffix);
        return userId;
    }

欢迎关注白羊🐏,感谢观看ヾ(◍°∇°◍)ノ゙