300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > java 自定义自增_自定义全局自增ID生成器

java 自定义自增_自定义全局自增ID生成器

时间:2019-07-14 20:01:43

相关推荐

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 mons.lang3.StringUtils;

/**

* 变量生成器

* @className VariableGenerator

* @date: /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 = pile("\\$\\((.+?)\\)");

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: /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: /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的生成速率。还有比较大的优化空间。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。