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生成唯一订单号内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Java实战之简单的文件管理器

    Java实战之简单的文件管理器

    这篇文章主要介绍了Java实战之简单的文件管理器,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • java实现数字炸弹

    java实现数字炸弹

    这篇文章主要为大家详细介绍了java实现数字炸弹,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • java FileOutputStream输出流的使用解读

    java FileOutputStream输出流的使用解读

    这篇文章主要介绍了java FileOutputStream输出流的使用解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • SpringBoot中对应2.0.x版本的Redis配置详解

    SpringBoot中对应2.0.x版本的Redis配置详解

    这篇文章主要为大家介绍了SpringBoot中对应2.0.x版本的Redis配置详解,文中的实现步骤讲解详细,感兴趣的小伙伴们可以了解一下
    2022-06-06
  • Java利用Stream API提高代码的简洁度和效率的操作方法

    Java利用Stream API提高代码的简洁度和效率的操作方法

    Stream API是Java 8引入的一项新特性,它提供了一种声明性、函数式的编程方式来处理集合(例如List、Set)等数据,那么Stream API到底能带来什么好处呢?今天,我就结合我多年的开发经验,带你深入了解如何使用Stream API优化代码,提高效率,减少冗余
    2025-08-08
  • Spring Boot结合ECharts案例演示示例

    Spring Boot结合ECharts案例演示示例

    本文主要主要介绍了Spring Boot结合ECharts案例演示示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Java字节码中jvm实例用法

    Java字节码中jvm实例用法

    在本篇文章里小编给大家整理的是一篇关于Java字节码中jvm实例用法内容,有兴趣的朋友们可以学习参考下。
    2021-02-02
  • Springboot错误处理机制实现原理解析

    Springboot错误处理机制实现原理解析

    这篇文章主要介绍了springboot错误处理机制实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • java锁升级过程过程详解

    java锁升级过程过程详解

    这篇文章主要介绍了Java锁升级的实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • SpringBoot实战之SSL配置详解

    SpringBoot实战之SSL配置详解

    今天小编就为大家分享一篇关于SpringBoot实战之SSL配置详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02

最新评论