Springboot3利用redis生成唯一订单号的实现示例

 更新时间:2025年03月20日 09:34:07   作者:jolly_xu  
本文主要介绍了Springboot3利用redis生成唯一订单号的实现示例,包括UUID、雪花算法和数据库约束,具有一定的参考价值,感兴趣的可以了解一下

生成订单号

生成唯一订单号的方法有很多种,包括uuid,雪花算法等等,还可以利用数据库的约束生成唯一的id,比如自增,但是数据库的性能比较低,如果使用数据库来生成订单号效率比较低。我们可以考虑使用redis来生成唯一的订单号。redis天然单线程,又支持lua脚本原子性操作。所以很好实现这些功能。

代码实现

import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Collections;

@RestController
public class distr_lock {
    @Resource
    private RedisTemplate<String,String> redisTemplate;


    @GetMapping("/order")
    public void order() {
        for (int i = 0; i < 50; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        String s = generateOrderId();
                        System.out.println(s);
                    }
                }
            }).start();
        }
    }

    /**
     * 我们可以采用这样一种策略,
     * 时间精确到秒+7位数的自增数字
     * 这样可以实现同一个用户在同一秒下单的不超过1000万单
     * 然后使用时间在redis创建一个key,进行自增,然后30秒过期
     */
    public String generateOrderId(){
        DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                .appendValue(ChronoField.YEAR, 4) // 年份,4位数字
                .appendValue(ChronoField.MONTH_OF_YEAR, 2) // 月份,2位数字
                .appendValue(ChronoField.DAY_OF_MONTH, 2) // 日期,2位数字
                .appendValue(ChronoField.HOUR_OF_DAY, 2) // 小时,2位数字
                .appendValue(ChronoField.MINUTE_OF_HOUR, 2) // 分钟,2位数字
                .appendValue(ChronoField.SECOND_OF_MINUTE, 2) // 秒,2位数字
                .toFormatter();

        // 获取当前时间的字符串表示,不使用-或:作为分隔符
        String currentDateTime = LocalDateTime.now().format(formatter);
//        Long count = getAndExpireCounter(currentDateTime);
        Long count = getAndExpireCounterByLua(currentDateTime);
        String format = String.format("%07d", count);
        return currentDateTime + format;
    }



    public Long getAndExpireCounter(String key) {
        // 获取Redis连接,以便执行事务
        Long increment = 0L;
        increment = redisTemplate.opsForValue().increment(key);
        if (increment!=null && increment == 1) {
            // 设置过期时间,这里并没有使用事务或者lua脚本
            // 因为我觉得上面的自增操作是原子性的,也就是肯定会得到一个值为1
            // 如果说redis服务崩了,到这一步没有设置好过期时间,那么我们整个
            // 服务也会崩溃,这个key就会一直存在于服务器,会出现这个问题,为了严谨
            // 最好使用lua脚本编写,项目上线会保证更可靠的效果
            redisTemplate.expire(key, 30, java.util.concurrent.TimeUnit.SECONDS);
        }
        // 获取自增后的值
        return increment;
    }
	
	/**
     * 整个脚本的作用是:
     * 1.检查一个键是否存在于Redis中。
     * 2.如果键不存在,则设置该键的值为1,并为其设置60秒的过期时间,然后返回1。
     * 3.如果键已经存在,则将其值增加1,并返回增加后的新值。
     * Redis执行Lua脚本时是原子的,这意味着在执行这个脚本的过程中,
     * 不会有其他脚本或命令同时修改这个键,从而保证了操作的原子性和一致性。
     * @param key
     * @return
     */
    public Long getAndExpireCounterByLua(String key) {
        // 定义Lua脚本
        String script =
                "if redis.call('EXISTS', KEYS[1]) == 0 then " +
                        "   redis.call('SET', KEYS[1], 1) " +
                        "   redis.call('EXPIRE', KEYS[1], 30) " +
                        "   return 1 " +
                        "else " +
                        "   return redis.call('INCR', KEYS[1]) " +
                        "end";

        // 使用Redis的脚本执行功能
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);

        // 执行脚本
        return redisTemplate.execute(redisScript, Collections.singletonList(key));
    }

到此这篇关于Springboot3利用redis生成唯一订单号的实现示例的文章就介绍到这了,更多相关Springboot3 redis生成唯一订单号内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • SpringBoot使用JSch操作Linux的方法

    SpringBoot使用JSch操作Linux的方法

    JSch是一个Java库,它提供了SSH(Secure Shell)的Java实现,允许Java程序通过SSH协议连接到远程系统(如Linux),这篇文章主要介绍了SpringBoot使用JSch操作Linux,需要的朋友可以参考下
    2023-11-11
  • SpringBoot简单实现文件上传

    SpringBoot简单实现文件上传

    这篇文章主要介绍了SpringBoot简单实现文件上传,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-09-09
  • Gradle使用Maven仓库的方法

    Gradle使用Maven仓库的方法

    本篇文章主要介绍了Gradle使用Maven仓库的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • IntelliJ IDEA 2020 安装和常用配置(推荐)

    IntelliJ IDEA 2020 安装和常用配置(推荐)

    这篇文章主要介绍了IntelliJ IDEA 2020 安装和常用配置(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • sharding-jdbc 兼容 MybatisPlus动态数据源的配置方法

    sharding-jdbc 兼容 MybatisPlus动态数据源的配置方法

    这篇文章主要介绍了sharding-jdbc 兼容 MybatisPlus动态数据源的配置方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-07-07
  • java分布式面试降级组件Hystrix的功能特性

    java分布式面试降级组件Hystrix的功能特性

    这篇文章主要为大家介绍了java分布式面试关于降级组件Hystrix的功能特性回答,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • 详解基于JWT的springboot权限验证技术实现

    详解基于JWT的springboot权限验证技术实现

    这篇文章主要介绍了详解基于JWT的springboot权限验证技术实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 成功解决IDEA2020 Plugins 连不上、打不开的方法

    成功解决IDEA2020 Plugins 连不上、打不开的方法

    这篇文章主要介绍了成功解决IDEA2020 Plugins 连不上、打不开的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 教你如何把Eclipse创建的Web项目(非Maven)导入Idea

    教你如何把Eclipse创建的Web项目(非Maven)导入Idea

    这篇文章主要介绍了教你如何把Eclipse创建的Web项目(非Maven)导入Idea,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • java关键字final使用方法详解

    java关键字final使用方法详解

    在程序设计中,我们有时可能希望某些数据是不能够改变的,这个时候final就有用武之地了。final是java的关键字,本文就详细说明一下他的使用方法
    2013-11-11

最新评论