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