使用redis生成唯一编号及原理示例详解


Posted in Redis onSeptember 15, 2021

在系统开发中,保证数据的唯一性是至关重要的一件事,目前开发中常用的方式有使用数据库的自增序列、UUID生成唯一编号、时间戳或者时间戳+随机数等。

在某些特定业务场景中,可能会要求我们使用特定格式的唯一编号,比如我有一张订单表(t_order),我需要生成“yewu(ORDER)+日期(yyyyMMdd)+序列号(00000000)”格式的订单编号,比如今天的日期是20200716,那我今天第一个订单号就是ORDER2020071600000001、第二个订单号就是ORDER2020071600000002,明天的日期是20200717,那么明天的第一个订单号就是ORDER2020071700000001、第二个订单号就是ORDER2020071700000002,以此类推。

今天介绍下如何使用redis生成唯一的序列号,其实主要思想还是利用redis单线程的特性,可以保证操作的原子性,使读写同一个key时不会出现不同的数据。以SpringBoot项目为例,添加以下依赖。

<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>

application.properties中配置redis,我本地redis没有设置密码,所以注释了密码这一行

server.port=9091
server.servlet.context-path=/

spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=1234
spring.redis.database=0

创建SequenceService类用于生成特定业务编号

package com.xiaochun.service;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

@Service
public class SequenceService {

    private static Logger logger = LoggerFactory.getLogger(SequenceService.class);

    @Resource
    private RedisTemplate redisTemplate;

    //用作存放redis中的key
    private static String ORDER_KEY = "order_key";
    
    //生成特定的业务编号,prefix为特定的业务代码
    public String getOrderNo(String prefix){
         return getSeqNo(ORDER_KEY, prefix);
    }
    
    //SequenceService类中公用部分,传入制定的key和prefix
    private String getSeqNo(String key, String prefix)
    {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        //设置过期时间,这里设置为当天的23:59:59
        Date expireDate = calendar.getTime();
        //返回当前redis中的key的最大值
        Long seq = generate(redisTemplate, key, expireDate);
        //获取当天的日期,格式为yyyyMMdd
        String date = new SimpleDateFormat("yyyyMMdd").format(expireDate);
        //生成八为的序列号,如果seq不够八位,seq前面补0,
        //如果seq位数超过了八位,那么无需补0直接返回当前的seq
        String sequence = StringUtils.leftPad(seq.toString(), 8, "0");
        if (prefix == null)
        {
            prefix = "";
        }
        //拼接业务编号
        String seqNo = prefix + date + sequence;
        logger.info("KEY:{}, 序列号生成:{}, 过期时间:{}", key, seqNo, String.format("%tF %tT ", expireDate, expireDate));
        return seqNo;
    }

    /**
     * @param key
     * @param expireTime <i>过期时间</i>
     * @return
     */
    public static long generate(RedisTemplate<?,?> redisTemplate,String key,Date expireTime) {
        //RedisAtomicLong为原子类,根据传入的key和redis链接工厂创建原子类
        RedisAtomicLong counter = new RedisAtomicLong(key,redisTemplate.getConnectionFactory());
        //设置过期时间
        counter.expireAt(expireTime);
        //返回redis中key的值,内部实现下面详细说明
        return counter.incrementAndGet();
    }

}

接下来,启动项目,使用接口的形式访问,或者写Test方法执行,就可以得到诸如ORDER2020071600000001、ORDER2020071600000002的编号,而且在高并发环境中也不会出现数据重复的情况。实现原理:上面生成特定业务编号主要分为三部分,如下图

使用redis生成唯一编号及原理示例详解

前缀和日期部分,没什么需要解释的,主要是redis中的生成的序列号,而这需要依靠RedisAtomicLong来实现,先看下上面生成redis序列过程中发生了什么

  • 获取redis中对应业务的key,生成过期时间expireTime
  • 获取了RedisTemplate对象,通过该对象获取RedisConnectionFactory对象
  • 将key,RedisConnectionFactory对象作为构造参数生成RedisAtomicLong对象,并设置过期时间
  • 调用RedisAtomicLong的incrementAndGet()方法

看下RedisAtomicLong源码,当然只放一部分源码,不会放全部,RedisAtomicLong的结构,主要构造函数,和上面提到过的incrementAndGet()方法

public class RedisAtomicLong extends Number implements Serializable, BoundKeyOperations<String> {
    private static final long serialVersionUID = 1L;
    //redis中的key,用volatile修饰,获得原子性
    private volatile String key;
    //当前的key-value对象,根据传入的key获取value值
    private ValueOperations<String, Long> operations;
    //传入当前redisTemplate对象,为RedisTemplate对象的顶级接口
    private RedisOperations<String, Long> generalOps;

    public RedisAtomicLong(String redisCounter, RedisConnectionFactory factory) {
        this(redisCounter, (RedisConnectionFactory)factory, (Long)null);
    }
    private RedisAtomicLong(String redisCounter, RedisConnectionFactory factory, Long initialValue) {
        Assert.hasText(redisCounter, "a valid counter name is required");
        Assert.notNull(factory, "a valid factory is required");
        //初始化一个RedisTemplate对象
        RedisTemplate<String, Long> redisTemplate = new RedisTemplate();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericToStringSerializer(Long.class));
        redisTemplate.setExposeConnection(true);
        //设置当前的redis连接工厂
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.afterPropertiesSet();
        //设置传入的key
        this.key = redisCounter;
        //设置当前的redisTemplate
        this.generalOps = redisTemplate;
        //获取当前的key-value集合
        this.operations = this.generalOps.opsForValue();
        //设置默认值,如果传入为null,则key获取operations中的value,如果value为空,设置默认值为0
        if (initialValue == null) {
            if (this.operations.get(redisCounter) == null) {
                this.set(0L);
            }
        //不为空则设置为传入的值
        } else {
            this.set(initialValue);
        }
    }
    //将传入key的value+1并返回
    public long incrementAndGet() {
        return this.operations.increment(this.key, 1L);
    }

其实主要还是通过redis的自增序列来实现

到此这篇关于如何使用redis生成唯一编号及原理的文章就介绍到这了,更多相关redis生成唯一编号内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
详解RedisTemplate下Redis分布式锁引发的系列问题
Apr 27 Redis
详解Redis主从复制实践
May 19 Redis
redis实现共同好友的思路详解
May 26 Redis
Redis Cluster 字段模糊匹配及删除
May 27 Redis
解析高可用Redis服务架构分析与搭建方案
Jun 20 Redis
Redis入门教程详解
Aug 30 Redis
Redis RDB技术底层原理详解
Sep 04 Redis
为什么RedisCluster设计成16384个槽
Sep 25 Redis
Redis中有序集合的内部实现方式的详细介绍
Mar 16 Redis
Redis调用Lua脚本及使用场景快速掌握
Mar 16 Redis
Redis基本数据类型List常用操作命令
Jun 01 Redis
Redis入门基础常用操作命令整理
Jun 01 Redis
Redis读写分离搭建的完整步骤
Sep 14 #Redis
在项目中使用redis做缓存的一些思路
Redis RDB技术底层原理详解
Sep 04 #Redis
使用redis实现延迟通知功能(Redis过期键通知)
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 #Redis
Redis字典实现、Hash键冲突及渐进式rehash详解
Sep 04 #Redis
基于Redis的List实现特价商品列表功能
Aug 30 #Redis
You might like
PHP面向对象三大特点学习(充分理解抽象、封装、继承、多态)
2012/05/07 PHP
PHP数组函数array_multisort()用法实例分析
2016/04/02 PHP
php实现的网页版剪刀石头布游戏示例
2016/11/25 PHP
php使用gd2绘制基本图形示例(直线、圆、正方形)
2017/02/15 PHP
脚本吧 - 幻宇工作室用到js,超强推荐share.js
2006/12/23 Javascript
JavaScript 编程引入命名空间的方法
2007/06/29 Javascript
jQuery一步一步实现跨浏览器的可编辑表格,支持IE、Firefox、Safari、Chrome、Opera
2009/08/28 Javascript
jQuery子窗体取得父窗体元素的方法
2015/05/11 Javascript
javascript中基本类型和引用类型的区别分析
2015/05/12 Javascript
javascript鼠标滑动评分控件完整实例
2015/05/13 Javascript
基于jQuery实现点击列表加载更多效果
2016/05/31 Javascript
Bootstrap模态框水平垂直居中与增加拖拽功能
2016/11/09 Javascript
js实现背景图自适应窗口大小
2017/01/10 Javascript
详解.vue文件解析的实现
2018/06/11 Javascript
VUE 配置vue-devtools调试工具及安装方法
2018/09/30 Javascript
使用Vue父子组件通信实现todolist的功能示例代码
2019/04/11 Javascript
vue组件之间的数据传递方法详解
2019/04/19 Javascript
es6 super关键字的理解与应用实例分析
2020/02/15 Javascript
[02:27]2018DOTA2亚洲邀请赛赛前采访-OpTic
2018/04/03 DOTA
Python CSV模块使用实例
2015/04/09 Python
python对指定目录下文件进行批量重命名的方法
2015/04/18 Python
Python中用altzone()方法处理时区的教程
2015/05/22 Python
Python 字符串大小写转换的简单实例
2017/01/21 Python
Python判断文件或文件夹是否存在的三种方法
2017/07/27 Python
Sanic框架基于类的视图用法示例
2018/07/18 Python
详解Python装饰器
2019/03/25 Python
搞清楚 Python traceback的具体使用方法
2019/05/13 Python
python实现beta分布概率密度函数的方法
2019/07/08 Python
python同时遍历两个list用法说明
2020/05/02 Python
使用已经得到的keras模型识别自己手写的数字方式
2020/06/29 Python
HTML5操作WebSQL数据库的实例代码
2017/08/26 HTML / CSS
英国书籍、CD、DVD和游戏的第一道德零售商:Awesome Books
2020/02/22 全球购物
SQL Server的固定数据库角色都有哪些?对应的服务器权限有哪些?
2013/05/18 面试题
办公室前台岗位职责范本
2013/12/10 职场文书
退休教师欢送会主持词
2014/03/31 职场文书
四风批评与自我批评发言稿
2014/10/14 职场文书