首页 > 其他分享 >分布式下获取单号的一个方式

分布式下获取单号的一个方式

时间:2022-10-30 14:24:45浏览次数:41  
标签:String sequence 单号 private 获取 Long 序列号 seqType 分布式

分布式下,获取单号有多种方式。
1.UUID 乱序,且很长,不利于数据库做索引查询 和 空间浪费
2.数据库自增序列,每次都要访问数据库,IO开销大,高并发时几乎不可用
3.Redis 自增,优点是速度快,缺点是持久化不可靠,有可能造成重复单号

本文介绍获取单号的特点是:
1.局部自增,前缀可添加业务属性的字符串
2.利用数据库持久化序列号值,保证可靠性
3.利用Redis 的 List 结构进行批量缓存序列号池 , 保证取号效率
优点: 兼顾了持久化和支持高并发
缺点: 当Redis序列号耗尽,需要加同步锁控制生成新的序列号缓存池,这里有一定的性能损失

  • 数据库表结构
CREATE TABLE `my_sequence` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `seq_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '序列名字',
  `current_val` bigint(20) unsigned NOT NULL COMMENT '当前值',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='序列表';
  • 持久化序列号的JAVA实体
/**
 * 序列表
 *
 */
@Data
@TableName("my_sequence")
public class MySequence {
	private static final long serialVersionUID = 1L;

	/**
	 * 自增主键
	 */
	@TableId
	private Long id;
	/**
	 * 序列名字
	 */
	private String seqName;
	/**
	 * 当前值
	 */
	private Long currentVal;
}
  • 工具类 (spring + RedisTemplate + Redission + mybatis(本例子用了mybatisPlus))
/**
 * 序列号生成器
 */
@Component
@AllArgsConstructor
public class SequenceUtils {

	private static final String PLATFORM_NO_LOCK_KEY = "PLATFORM_NO_LOCK_";

	private static final String PLATFORM_NO_STACK_KEY = "PLATFORM_NO_STACK_KEY";
    // 每次存进Redis 缓存池的序列号数量,根据业务量和单号长度设计进行合理安排
	private static final Long SEQ_VAL_STEP_SIZE = 500L;
    // 用于 操作 redis - 存取 序列号
	private final RedisTemplate redisTemplate;
    // 操作记录序列号数据库的 service
	private final SequenceService sequenceService;
    // Redission - 用于 分布式锁
	private final DistributedLock distributedLock;

	/**
	 * 从redis获取已经生成好的序号
	 * 不加入业务的事务,避免随业务回滚,序列号只增加,不回滚
	 *
	 * @param seqType 业务类型标志 - 数据库存了多个业务类型的序列号记录
	 * @return
	 */
	@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
	public String getPlatformNoRedis(String seqType) {
		Long nextSeqVal = (Long) redisTemplate.opsForList().leftPop(PLATFORM_NO_STACK_KEY);
		if (null == nextSeqVal) {
			nextSeqVal = pushAndGetSeqVal(seqType);
		}
		// 指定业务类型前缀,这里 D 为例子
		return formatStrNo("D", String.valueOf(nextSeqVal), null);
	}

	/**
	 * 如果 redis 序号已经用完,则查库追加 指定步长数量的序号
	 *
	 * @param seqType 业务类型标志 - 数据库存了多个业务类型的序列号记录
	 * @return {@link Long}
	 */
	private Long pushAndGetSeqVal(String seqType) {
		Long nextSeqVal;
        // 第一个本地同步锁,防止分布式锁过多的争抢
		synchronized (SequenceUtils.class) {
			nextSeqVal = (Long) redisTemplate.opsForList().leftPop(PLATFORM_NO_STACK_KEY);
			if (null == nextSeqVal) {
				nextSeqVal = distributedLock.locked(PLATFORM_NO_LOCK_KEY + seqType, () -> {
					Long nextPlatformNoTemp = (Long) redisTemplate.opsForList().leftPop(PLATFORM_NO_STACK_KEY);
					if (null == nextPlatformNoTemp) {
                        // 取出指定的序列号持久化数据
                        // 这里用了 mybatisPlus 的模板代码,可换成自己的ORM取数据方式
						MySequence sequence = sequenceService
						.getOne(new LambdaQueryWrapper<MySequence>().eq(MySequence::getSeqName, seqType));
                        // 如果序列号还没有初始化,则进行初始化
						MySequence lSequence = Optional.ofNullable(sequence).orElseGet(() -> {
							MySequence mySequence = new MySequence();
							mySequence.setSeqName(seqType);
							mySequence.setCurrentVal(0L);
							sequenceService.save(mySequence);
							return mySequence;
						});

						Long oldVal = lSequence.getCurrentVal();
						lSequence.setCurrentVal(oldVal + SEQ_VAL_STEP_SIZE);
                        // 先持久化性新的序列号值 - 这里必须能接受redis上传序列号失败造成的部分序列号浪费,
                        // 所以 SEQ_VAL_STEP_SIZE 步长不要太大,不然可能提前耗尽序列号
						sequenceService.updateById(lSequence);
                        // 利用 redis 的管道批量上传序列号
						redisTemplate.executePipelined(new SessionCallback<Long>() {
							@Override
							public <K, V> Long execute(RedisOperations<K, V> operations) throws DataAccessException {
								RedisTemplate<String, Long> thisRedisTemplate = (RedisTemplate<String, Long>) operations;
								// 在步长范围内,递增+1
								for (int i = 1; i <= SEQ_VAL_STEP_SIZE; i++) {
									thisRedisTemplate.opsForList().rightPush(PLATFORM_NO_STACK_KEY, oldVal + i);
								}
								return null;
							}
						});
                        // 上传之后,取出序列号
						nextPlatformNoTemp = (Long) redisTemplate.opsForList().leftPop(PLATFORM_NO_STACK_KEY);
					}
					return nextPlatformNoTemp;
				});
			}
		}
		return nextSeqVal;
	}

	/**
	 * 格式化序列号
	 *
	 * @param type     业务类型
	 * @param sequence 自然数序列
	 * @param str      有值就是前缀+str+自然数五位,没有就是str用日期
	 * @return
	 */
	public String formatStrNo(String type, String sequence, String str) {
		String pregfix = type.toUpperCase();
		// 这里取了序列的指定长度 ,按照需求设置合理的值,不然有可能提前耗尽序列号
		sequence = String.format("%05d", Integer.valueOf(sequence));
		if (sequence.length() > 5) {
			sequence = StrUtil.sub(sequence, sequence.length() - 5, sequence.length());
		}
		String result = LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
		if (StrUtil.isNotEmpty(str)) {
			result = str;
		}
		String template = "{}" + result + "{}";
		return StrUtil.format(template, pregfix, sequence);
	}
}

标签:String,sequence,单号,private,获取,Long,序列号,seqType,分布式
From: https://www.cnblogs.com/jicheng999/p/16841197.html

相关文章

  • Redis实现分布式缓存
    单机的Redis存在四大问题:数据丢失、并发能力弱、故障恢复问题、存储能力1、Redis持久化(解决数据丢失问题)有两种持久化方案:RDB/AOF★RDB(数据备份文件):把内存......
  • 【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分
    分布式事务分布式事务的场景什么场景下会出现分布式事务?TX协议⼀种分布式事务协议,包含⼆阶段提交(2PC),三阶段提交(3PC)两种实现。二阶段提交方案:强一致性事务的发起者称协调者,事......
  • 11.校验token和解析token获取数据代码优化
    校验token和解析token获取数据代码优化解决方案基于ThreadLocal+拦截器的形式统一处理一、使用拦截器进行统一身份鉴权1.1定义拦截器packagecom.tanhua.server.i......
  • redisson分布式限流[RRateLimiter]源码分析
    接下来在讲一讲平时用的比较多的限流模块--RRateLimiter1.简单使用publicstaticvoidmain(String[]args)throwsInterruptedException{RRateLimiterrateLimit......
  • js获取当前日期的前七天
    //获取当前日期的前7天getDays(){letoneDay=24*60*60*1000letendTime=newDate(Date.now()-oneDay)endTime=this.formatterDate(en......
  • Chrome扩展插件的开发--获取网页Cookies
    Chrome扩展插件的开发--获取网页CookiesChrome浏览器在浏览器类应用软件中一直居于榜首,很多人选择Chrome浏览器不仅仅是因为它的稳定,还有它丰富的可拓展性。那么有没有想......
  • java new Date() 获取的时间存到库里少了八个小时?
    javanewDate()获取的时间存到库里少了八个小时?javanewDate()获取的时间存到mysql库里少了八个小时? 在application.yml修改数据库配置为url:jdbc:mysql://localhost......
  • 一次获取明文
    一次获取明文昨天进入c盘拿到flag后,发现了第六关的猫腻,我重新登录远程桌面。发现在c盘文件下但是打不开,因为有密码保护,并且根据提示知道,文件的密码是管理员administrator的......
  • SoringCloud(四) - 微信获取用户信息
    1、项目介绍2、微信公众平台和微信开放文档2.1微信公众平台2.1.1网址链接https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index......
  • DOM概述和DOM的Document对象获取Element方法
    DOM概述W3C DOM 标准被分为3个不同的部分:核心DOM-针对任何结构化文档的标准模型Document:文档对象Element:元素对象Attribute:属性......