java 自定义自增_自定义全局自增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 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 variableGeneratorMap;
// 字段初始化生成器
@Autowired(required = false)
private Map initFieldGeneratorMap;
// 构造函数,参数是生成规则
public IDFactory(Map expressionMap) {
this.expressionMap = expressionMap;
}
// 实例化后执行
public void init() {
// 如果没有规则表达式,那么直接就结束
if (CollectionUtils.isEmpty(this.expressionMap)) {
return;
}
for (Map.Entry 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);
}
}
}
}
/**
* 表达式
*
*
pid:expression格式
*/
private Map 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 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 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 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 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;
}
初始化字段生成器
public abstract class InitFieldGenerator {
public static final String INIT_FIELD_GENERATOR = "InitFieldGenerator";
// 执行初始化操作
public abstract String generator(String key, String initField);
}
以上代码咱们已经把整体的初始化、ID生成逻辑全部搞定,剩下的就是需要把对应的接口填充完毕就行。
针对表达式
$(pid)$(yearMonthDayHms)$(id:6:0)
咱们分别需要实现pidVariableGenerator、yearMonthDayHmsVariableGenerator、idVariableGenerator
@Bean
public VariableGenerator pidVariableGenerator() {
return new VariableGenerator() {
@Override
public String apply(ExpressionElement e, Expression expression) {
return expression.getKey();
}
};
}
private static final String YEAR_MONTH_DAY_HOUR_MINUTE_SECOND_FORMAT = "yyyyMMddHHmmss";
@Bean
public VariableGenerator yearMonthDayHmsVariableGenerator() {
return new VariableGenerator() {
@Override
public String apply(ExpressionElement e, Expression expression) {
return DateUtil.format(new DateTime(), YEAR_MONTH_DAY_HOUR_MINUTE_SECOND_FORMAT);
}
};
}
因为咱们的自增id是使用的redis的lua脚本实现的,所以会依赖redis。利用了redis执行lua脚本的原子性。
@Bean
public SerialIDVariableGenerator idVariableGenerator() {
return new SerialIDVariableGenerator();
}
public class SerialIDVariableGenerator extends VariableGenerator {
@Autowired private RedisTemplate redisTemplate;
private InitParams initParams;
// 构造函数
public void initParams(String key, String initFields) {
this.initParams = parse(key, initFields);
}
/**
* 解析表达式 字段名:初始值:最大值:最小数量:扩容数量:初始数量:增长步长
*
* @param initField
*/
private InitParams parse(String key, String initField) {
InitParams initParams = new InitParams();
if (StringUtils.contains(initField, COLON)) {
String[] params = StringUtils.splitByWholeSeparatorPreserveAllTokens(initField, COLON);
initParams.setFieldName(key + COLON + params[0]);
initParams.setField(params);
} else {
initParams.setFieldName(key + COLON + initField);
initParams.updateFields();
}
return initParams;
}
// 执行lua脚本,生成对应的自增id
public String generate() {
String fieldName = this.initParams.getFieldName();
return executeLua(
fieldName,
initParams.getInitValue(),
initParams.getMaxValue(),
initParams.getMinCount(),
initParams.getInitCount(),
initParams.getExpansionStep(),
initParams.getIncrStep());
}
// 执行生成函数
@Override
protected String apply(ExpressionElement e, Expression expression) {
return generate();
}
// 执行lua脚本
private String executeLua(
String key,
int initValue,
int maxValue,
int minCount,
int initCount,
int expansionStep,
int incrStep) {
// 执行lua脚本
DefaultRedisScript defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(String.class);
defaultRedisScript.setScriptText(LUA_SCRIPT);
RedisSerializer serializer = redisTemplate.getStringSerializer();
String result =
CastUtil.castString(
redisTemplate.execute(
defaultRedisScript,
serializer,
serializer,
Lists.newArrayList(key),
CastUtil.castString(initValue),
CastUtil.castString(maxValue),
CastUtil.castString(minCount),
CastUtil.castString(initCount),
CastUtil.castString(expansionStep),
CastUtil.castString(incrStep)));
return result;
}
@Data
private static class InitParams {
/** 默认初始值 */
private static final int DEFAULT_INIT_VALUE = 1;
/** 默认最大值 */
private static final int DEFAULT_MAX_VALUE = 9999;
/** 默认最小数量 */
private static final int DEFAULT_MIN_COUNT = 30;
/** 默认初始数量 */
private static final int DEFAULT_INIT_COUNT = 100;
/** 默认扩容数量 */
private static final int DEFAULT_EXPANSION_STEP = 50;
/** 默认自增步长 */
private static final int DEFAULT_INCR_STEP = 1;
private final int[] params = {
0,
DEFAULT_INIT_VALUE,
DEFAULT_MAX_VALUE,
DEFAULT_MIN_COUNT,
DEFAULT_EXPANSION_STEP,
DEFAULT_INIT_COUNT,
DEFAULT_INCR_STEP
};
/** 字段名称,其实就是key */
private String fieldName;
/** 初始值 */
private int initValue;
/** 最大值 */
private int maxValue;
/** 最小数量 */
private int minCount;
/** 扩容步长 */
private int expansionStep;
/** 初始数量 */
private int initCount;
/** 自增步长 */
private int incrStep;
public void setField(Object[] objects) {
if (ArrayUtils.isEmpty(objects) || ArrayUtils.getLength(objects) < 2) {
return;
}
for (int i = 1; i < objects.length; i++) {
Object obj = objects[i];
params[i] = CastUtil.castInt(obj);
}
updateFields();
}
public void updateFields() {
this.initValue = params[1];
this.maxValue = params[2];
this.minCount = params[3];
this.expansionStep = params[4];
this.initCount = params[5];
this.incrStep = params[6];
}
}
// 该脚本的执行逻辑
// 在redis中生成一个队列,指定初始化长度,第一个初始值,最大值,队列最小数量,每次扩容的数量,自增的步长
// 1.如果队列不存在,就初始化队列,按照给定的初始化长度,初始值,自增步长,最大值等参数创建一个队列
// 2.如果队列中值的数量超过队列最小数量,那么直接pop出一个值
// 3.如果小于最小数量,那么直接循环生成指定步长的自增ID
// 4.最终会pop出第一个数值
// 5.如果是初始化的话,会返回success,否则就直接pop出第一个ID
private static final String LUA_SCRIPT =
"local key=KEYS[1]\nlocal initValue=tonumber(ARGV[1])\nlocal maxValue=tonumber(ARGV[2])\nlocal minCount=tonumber(ARGV[3])\nlocal initCount=tonumber(ARGV[4])\nlocal expansionStep=tonumber(ARGV[5])\nlocal incrStep=tonumber(ARGV[6])\nlocal len=redis.call('llen',key)\nlocal isInit=true\nlocal loop=initCount\nlocal nextValue=initValue\nif len>minCount\nthen\nreturn redis.call('lpop',key)\nend\nif len>0\nthen\nisInit=false\nloop=len+expansionStep\nnextValue=tonumber(redis.call('rpop',key))\nend\nwhile(lenmaxValue\nthen\nnextValue=initValue\nend\nredis.call('rpush',key,nextValue)\nnextValue=nextValue+incrStep\nlen=len+1\nend\nif isInit\nthen\nreturn 'success'\nend\nreturn redis.call('lpop',key)";
}
根据配置中的初始化字段的配置规则,咱们还需要一个idInitFieldGenerator初始化字段生成器
@Bean("idInitFieldGenerator")
public SerialIdInitFieldGenerator serialIdInitFieldGenerator() {
return new SerialIdInitFieldGenerator(idVariableGenerator());
}
public class SerialIdInitFieldGenerator extends InitFieldGenerator {
private SerialIDVariableGenerator serialIDVariableGenerator;
public SerialIdInitFieldGenerator(SerialIDVariableGenerator serialIDVariableGenerator) {
this.serialIDVariableGenerator = serialIDVariableGenerator;
}
// 利用了SerialIDVariableGenerator变量生成器的方法初始化
@Override
public String generator(String key, String initField) {
serialIDVariableGenerator.initParams(key, initField);
return serialIDVariableGenerator.generate();
}
}
总结
1.核心生成逻辑还是利用了redis执行lua脚本的原子性
2.把表达式的生成逻辑拆分到具体的接口实现中去,方便规则的自定义扩展
目前粗略测试下来,线程并发的情况下大概1000个/s的生成速率。还有比较大的优化空间。
java 自定义自增_自定义全局自增ID生成器相关推荐
- java自定义标签遍历_自定义标签 - CarlDing的个人页面 - OSCHINA - 中文开源技术交流社区...
EL的不足,由JSTL来加强 -> 自定义标签来实现. 1:自定义标签 1:自定义标签也是类. 2:让用户在JSP页面使用,不引用Java代码的情况下,调用Java代码. 2:标签开的类的继承 ...
- mybatis可以生成不重复的id吗_分布式全局不重复ID生成算法
在分布式系统中经常会使用到生成全局唯一不重复ID的情况.本篇博客介绍生成的一些方法. 常见的一些方式: 1.通过DB做全局自增操作 优点:简单.高效 缺点:大并发.分布式情况下性能比较低 有些同学可能 ...
- java redis id生成器_基于redis的分布式ID生成器
项目地址 基于redis的分布式ID生成器. 准备 首先,要知道redis的EVAL,EVALSHA命令: 原理 利用redis的lua脚本执行功能,在每个节点上通过lua脚本生成唯一ID. 生成的I ...
- java切面类整合_自定义注解+面向切面整合的日志记录模块(一)
java中的常见注解 jdk的自带注解 @Override:告诉编译器我重写了接口方法 @Deprecated:告诉编译器这个方法过时了,不建议使用,Ide会在方法上划横线 @SuppressWarn ...
- java自定义分页标签_自定义分页标签--仿javaeye分页效果
效果如图: 1.JSP规范1.1版本后增加了自定义标签库.实现自定义标签的步骤 (1)开发自定义标签处理类. (2)建立*.tld文件. (3)在web.xml中增加自定义标签的定义. (4)在jsp ...
- java tld 方法重载_自定义标签
1.创建自定义标签的步骤 a)创建标签实现类 b)创建 .tbl 文件,在该文件中配置标签实现类 c)在 jsp 页面中使用指定标签实现类的配置文件 .tbl a) 创建标签实现类 1)实现Simpl ...
- java中readline函数_自定义BufferedReader中read和readLine方法
BufferedReader中read和readLine方法总结 实例如下所示: package day0208; import java.io.FileReader; import java.io. ...
- java自定义maven插件_自定义Maven插件
第一.自定义Maven插件概述 Mojo:Maven plain Old Java Object.每一个 Mojo 就是 Maven 中的一个执行目标(executable goal),而插件则是对单 ...
- java自定义日志级别_自定义log4j日志级别
因为项目中需要输出一些特别的日志来做数据统计.如果开启log4j提供的INFO日志级别,每天生成的日志文件就会变得越来越大.这样就得写个定 时任务来删除这个文件.为了只输出所需的日志级别,唯有自己定义 ...
最新文章
- Windows~KinectV2开发
- WINCE的内存配置
- idea2018.3.5集成scala开发环境
- python语言与c语言相比在分支结构上有什么不同_C语言顺序结构和分支结构总结...
- 获取ip地址 域名获取与解析
- allegro设置快捷键
- qq空间登录参数详细分析及密码加密最新版
- 大局观有多重要?如何拥有大局观?
- js每日一题(12)
- 发一个笔筒式的摄像头驱动(台湾原相科技)
- 1-1 Java基础和编程概述
- Mozi僵尸网络(P2P僵尸网络Mozi)
- 超微x9dai 跳线_秒变MacPro!至强E5双路CPU,超微X9DAi主板,Quadro K5000黑苹果
- CCD相机模数转换芯片时序简介
- app分发源码完整|免签封装
- Python网络数据采集9(译者:哈雷)
- 游戏体验之穿越火线手游
- UVM:解决phase的执行顺序
- 腾讯技术团队整理,万字长文轻松彻底入门 Flutter,秒变大前端
- 2021年JS解决那些有趣的数学题