自定义全局自增ID生成器(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 自定义全局自增ID生成器

看了网上很多生成自增ID的策略,最终给出的都是雪花算法,leaf算法。但是却没有满足咱们对于自定义生成规则的需求。

在业务上有一部分ID往往是有规则的,比如某个产品的订单号往往是“产品标志+时间+n位流水”,类似这样的订单规则,使用雪花算法是满足不了业务需求的,所以我们得设计一套自己的自定义ID生成器。

“产品标志+时间+n位流水”规则中,难点无非在于n位流水号的生成,因为这个流水号需要保证在多次请求中不会产生重复的订单号。

首先,咱们根据业务需求,先制定对应的规则表达式:

id:
  generator:
    expressions:
      # 产品标志
      TEST:
        # 表达式,pid指的是产品标志
        exp: "$(pid)$(yearMonthDayHms)$(id:6:0)"
        # 字段名:初始值:最大值:最小数量:扩容数量:初始数量:增长步长
        initFields: ["id"]
复制代码

表达式对应的实体类:

@Data
@Configuration
@ConfigurationProperties(prefix = "id.generator")
public class IDExpressionProperties {
    private Map<String, SerialIdConfig> expressions;
    @Data
    public static class SerialIdConfig {
        // 表达式
        private String exp;
        // 初始化字段
        private String[] initFields;
    }
}
复制代码

通过spring解析获得对应的IDExpressionProperties实体类,拿到咱们自定义的配置

通过规则表达式可以看出,类似“$(pid)”这样的表达式,咱们可以抽象成接口自定义生成。比如咱们定义一个VariableGenerator接口或者抽象类,遇到pid就从spring ioc容器中调用pidVariableGenerator这个Bean的生成方法获取pid的值。遇到yearMonthDayHms就调用yearMonthDayHmsVariableGenerator这个Bean的生成方法获取yearMonthDayHms指定的值。

@Configuration
public class SerialConfig {
    @Autowired private IDExpressionProperties idExpressionProperties;
    /**
    ** 咱们要创建一个ID工厂类,专门用来生成ID的类
    ** 使用方法:
    ** @Autowired
    ** private IDFactory idFactory;
    ** String id = idFactory.get("产品标志");
    */
    @Bean(initMethod = "init")
    public IDFactory serialIdFactory() {
        return new IDFactory(idExpressionProperties.getExpressions());
    }
}
复制代码

变量生成器

import org.apache.commons.lang3.StringUtils;
/**
 * 变量生成器
 * @className VariableGenerator
 * @date: 2021/2/18 下午2:53
 * @description:
 */
public abstract class VariableGenerator {
    public static final String COLON = ":";
   /**
    * apply是生成目标字符串的方法
    */
    protected abstract String apply(ExpressionElement e, Expression expression);
   /**
    * apply的后置处理方法,默认处理字符串不足的情况下,补足对应的填充数据
    */
    public String andThen(ExpressionElement e, Expression expression) {
        String variableValue = apply(e, expression);
        int count = e.getCount();
        String fillStringValue = e.getFillStringValue();
        if (StringUtils.isNotBlank(variableValue)) {
            if (count > 0) {
                variableValue = StringUtils.leftPad(variableValue, count, fillStringValue);
            } else {
                variableValue =
                        StringUtils.rightPad(variableValue, Math.abs(count), fillStringValue);
            }
        }
        return variableValue;
    }
}
复制代码

ID生成器

  • 初始化的时候根据表达式配置确定是哪些字段需要初始化,根据初始化字段调用指定的Bean执行初始化
  • 通过get方法传入的参数key获取指定的规则表达式,根据指定的表达式调用对应的生成器Bean实例,调用指定的方法生成目标值,最后拼接出最终的ID
public class IDFactory {
    // 变量生成器
    @Autowired(required = false)
    private Map<String, VariableGenerator> variableGeneratorMap;
    // 字段初始化生成器
    @Autowired(required = false)
    private Map<String, InitFieldGenerator> initFieldGeneratorMap;
    // 构造函数,参数是生成规则
    public IDFactory(Map<String, IDExpressionProperties.SerialIdConfig> expressionMap) {
        this.expressionMap = expressionMap;
    }
    // 实例化后执行
    public void init() {
        // 如果没有规则表达式,那么直接就结束
        if (CollectionUtils.isEmpty(this.expressionMap)) {
            return;
        }
        for (Map.Entry<String, IDExpressionProperties.SerialIdConfig> e :
                this.expressionMap.entrySet()) {
            String key = e.getKey();
            // 规则表达式
            IDExpressionProperties.SerialIdConfig config = e.getValue();
            // 初始化字段参数
            String[] initFields = config.getInitFields();
            // 如果没有初始化字段生成器,直接结束
            if (CollectionUtils.isEmpty(initFieldGeneratorMap)) {
                return;
            }
            // 根据初始化规则,执行初始化操作
            for (String initField : initFields) {
                String fieldName = initField;
                // 获取初始化字段名称
                if (StringUtils.contains(initField, VariableGenerator.COLON)) {
                    fieldName = StringUtils.substringBefore(initField, VariableGenerator.COLON);
                }
                // 根据字段名称获取对应的初始化生成器的Bean实例
                InitFieldGenerator initFieldGenerator =
                        initFieldGeneratorMap.get(
                                fieldName + InitFieldGenerator.INIT_FIELD_GENERATOR);
                if (Objects.nonNull(initFieldGenerator)) {
                    // 执行字段初始化操作
                    initFieldGenerator.generator(key, initField);
                }
            }
        }
    }
    /**
     * 表达式
     *
     * <p>pid:expression格式
     */
    private Map<String, IDExpressionProperties.SerialIdConfig> expressionMap;
    /**
     * 根据指定的key规则生成id
     *
     * @param key
     * @return
     */
    public String get(String key) {
        // key为空直接抛异常
        if (StringUtils.isBlank(key)) {
            throw new IllegalArgumentException("无效的参数值:" + key);
        }
        // 获取规则表达式
        IDExpressionProperties.SerialIdConfig serialIdConfig = expressionMap.get(key);
        // 表达式字符串
        String expressionString = serialIdConfig.getExp();
        // 为空直接抛异常
        if (StringUtils.isBlank(expressionString)) {
            throw new IllegalArgumentException("没有找到对应的表达式");
        }
        // 解析指定的表达式
        Expression expression = parse(key, expressionString);
        // 匹配得出最终结果
        return matchExpression(expression);
    }
    // 生成器名称后缀
    private static final String VARIABLE_GENERATOR = "VariableGenerator";
    // 循环遍历表达式中所有的自定义变量,获取指定Bean实例,执行目标方法后得出最终ID
    private String matchExpression(Expression expression) {
        // 获取变量列表,例如pid,yearMonthDayHms等
        List<ExpressionElement> elements = expression.getElements();
        // 如果没有任何变量,那么直接返回原表达式,说明表达式是一个常量
        if (CollectionUtils.isEmpty(elements)) {
            return expression.getExpression();
        }
        // 获取原表达式,用来替换变量,生成最终的ID
        String expressionString = expression.getExpression();
        // 循环遍历变量列表
        for (ExpressionElement e : elements) {
            // 拼接Bean的名称
            String beanName = e.getVariableName() + VARIABLE_GENERATOR;
            // 从map中取出指定的Bean
            VariableGenerator variableGenerator = variableGeneratorMap.get(beanName);
            // 如果没有取到,那么直接忽略,说明没有创建该表达式对应的生成器
            if (Objects.isNull(variableGenerator)) {
                continue;
            }
            // 调用目标方法生成字符串
            String variableValue = variableGenerator.andThen(e, expression);
            // 如果不为空,就替换掉原表达式中的变量;就是用具体生成的值替换变量表达式
            // “$(pid)$(yearMonthDayHms)$(id:6:0)”会被替换成“TEST$(yearMonthDayHms)$(id:6:0)”
            if (StringUtils.isNotBlank(variableValue)) {
                expressionString =
                        StringUtils.replace(expressionString, e.getOriginString(), variableValue);
            }
        }
        // 返回最终结果
        return expressionString;
    }
    // 正则表达式,用来解析$(pid)$(yearMonthDayHms)$(id:6:0)表达式
    private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\((.+?)\\)");
    private static final Map<String, Expression> EXPRESSION_MAP = Maps.newConcurrentMap();
    /**
     * 解析$(pid)$(yearMonthDayHms)$(id:6:0)
     *
     * @param expressionString
     * @return
     */
    private Expression parse(String key, String expressionString) {
        // 检查一下缓存中是否有解析号的表达式
        Expression expression = EXPRESSION_MAP.get(key);
        // 缓存不为空的话,直接返回
        if (Objects.nonNull(expression)) {
            return expression;
        }
        // 否则,直接解析
        synchronized (EXPRESSION_MAP) {
            // 双重检查,避免重复解析
            expression = EXPRESSION_MAP.get(key);
            if (Objects.nonNull(expression)) {
                return expression;
            }
            // 生成表达式对象
            expression = new Expression();
            expression.setKey(key);
            expression.setExpression(expressionString);
            List<ExpressionElement> expressionElements = Lists.newArrayList();
            Matcher matcher = EXPRESSION_PATTERN.matcher(expressionString);
            while (matcher.find()) {
                // 正则表达式,找出$()变量表达式,类似id:6:0
                String expressionVariable = matcher.group(1);
                // 表达式切割,分离出冒号分隔的参数
                String[] expressionVariables =
                        StringUtils.splitByWholeSeparatorPreserveAllTokens(
                                expressionVariable, VariableGenerator.COLON);
                ExpressionElement expre = new ExpressionElement();
                // 变量名称id
                expre.setVariableName(expressionVariables[0]);
                // 原生表达式$(id:6:0),便于后面直接替换
                expre.setOriginString(matcher.group());
                if (expressionVariables.length > 1) {
                    // 获取填充的最终长度
                    expre.setCount(CastUtil.castInt(expressionVariables[1]));
                }
                if (expressionVariables.length > 2) {
                    // 获取填充值
                    expre.setFillStringValue(expressionVariables[2]);
                }
                expressionElements.add(expre);
            }
            expression.setElements(expressionElements);
            // 放入本地缓存
            EXPRESSION_MAP.put(key, expression);
        }
        // 返回解析出来的表达式
        return expression;
    }
}
复制代码
import lombok.Data;
import java.util.List;
/**
 * @className Expression
 * @date: 2021/2/18 下午2:53
 * @description: 解析$(pid)$(year)$(month)$(day)$(id:6:0)这种类型的表达式
 */
@Data
public class Expression {
    /** pid */
    private String key;
    /** 表达式 */
    private String expression;
    /** 解析结果 */
    private List<ExpressionElement> elements;
}
/**
 * @author zouwei
 * @className ExpressionElement
 * @date: 2021/2/18 下午2:56
 * @description: 解析${id:6:0}这种类型的标记
 */
@Data
public class ExpressionElement {
    // 原生变量表达式
    private String originString;
    // 变量名称
    private String variableName;
    // 总长度
    private int count;
    // 填充值,默认是空字符
    private String fillStringValue = StringUtils.SPACE;



相关文章
|
7月前
|
存储 NoSQL 数据库
全局id生成方式
全局id生成方式
|
2月前
|
存储 算法 Java
作为Collection接口的子接口,Set不支持重复元素,也不保证元素顺序,适用于需要唯一性约束的场景。
【10月更文挑战第16天】Java的Set接口因其独特的“不重复性”而备受关注。作为Collection接口的子接口,Set不支持重复元素,也不保证元素顺序,适用于需要唯一性约束的场景。其背后的实现机制依赖于哈希表或红黑树等数据结构,通过哈希算法和equals()方法确保元素的唯一性。例如,使用HashSet可以轻松过滤重复的字符串。这种设计使Set在处理唯一数据时高效便捷。
30 3
阿里云RPA这个继续循环的组件是不是会自动自增一呢
阿里云RPA这个继续循环的组件是不是会自动自增一呢
86 1
|
7月前
|
SQL 存储 Java
MyBatis【付诸实践 02】 mapper文件未编译+statementType使用+返回结果字段顺序不一致+获取自增ID+一个update标签批量更新记录
MyBatis【付诸实践 02】 mapper文件未编译+statementType使用+返回结果字段顺序不一致+获取自增ID+一个update标签批量更新记录
86 0
|
存储 算法 安全
场景应用:id全局唯一且自增,如何实现?
场景应用:id全局唯一且自增,如何实现?
205 0
|
存储 Rust 算法
有关'全局唯一id'
有关'全局唯一id'
83 0
|
存储 算法 安全
全局唯一ID(自增ID、UUID、雪花算法)
一、介绍 系统唯一id是我们在设计阶段常常遇到的问题。在复杂的分布式系统中,几乎都需要对大量的数据和消息进行唯一标识。在设计初期,我们需要考虑日后数据量的级别,如果可能会对数据进行分库分表,那么就需要有一个全局唯一id来标识一条数据或记录。生成唯一id的策略有多种,但是每种策略都有它的适用场景、优点以及局限性。
|
Java 数据库连接 数据库
Mybatis使用generatedKey在插入数据时返回自增id始终为1,自增id实际返回到原对象当中的问题排查...
Mybatis使用generatedKey在插入数据时返回自增id始终为1,自增id实际返回到原对象当中的问题排查...
180 0
|
NoSQL Redis
自定义全局自增ID生成器(下)
自定义全局自增ID生成器
246 0