SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。
程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发

1 项目准备

SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】本文章 基于此

本文章是系列文章 ,每节文章都有对应的代码,每节的源码都是在上一节的基础上配置而来,对应的视频讲解课程正在火速录制中。

订单系统,用户下单,即要保存即时性,也要保证流畅性,同时还要防止超卖,本文章是基于 RabbitMQ 消息队列 + Redis 实现的下单,当然后续还会的秒杀系统设计 以及后续的微服务以及熔断控制等等。

在目前高并发分布式情境下,生成唯一标识(如这里的订单 sn)是重中之重,目前业界也有很多算法可以实现,比较有名的就是雪花算法(SnowFlake)!!!

1 分布式系统中雪花算法优化

首先在配置文件 application.yml 添加添加 workId 与 datacenterId

#开发环境配置
server:workId: 2datacenterId: 5

微服务下 最好用bootstrap.yml 而不是 application.yml 原因是因为优先级高,防止被覆盖或者无法生效 。
在分布式系统,不同服务器使用不同workId,datacenterId。
然后在微服务启动的时候,workId和datacenterId作为参数传入,来做为 雪花算法 数据标识Id与 机器标识ID

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class SnowFlakeCompone {@Value("${server.workId}")private long workId;@Value("${server.datacenterId}")private long datacenterId;private static volatile SnowFlake instance;/*** 获取实例* @return*/public SnowFlake getInstance(){if(instance == null){synchronized (SnowFlake.class){if(instance == null){instance = new SnowFlake(workId, datacenterId);}}}return instance;}
}

2 雪花算法 SnowFlake

public class SnowFlake {/*** 起始的时间戳*/private final static long START_STMP = 1480166465631L;/*** 每一部分占用的位数*/private final static long SEQUENCE_BIT = 12; //序列号占用的位数private final static long MACHINE_BIT = 5;   //机器标识占用的位数private final static long DATACENTER_BIT = 5;//数据中心占用的位数/*** 每一部分的最大值*///支持的最大数据标识id,结果是31private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);//支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);// 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);/*** 每一部分向左的位移*///机器ID向左移12位private final static long MACHINE_LEFT = SEQUENCE_BIT;//数据标识id向左移17位(12+5)private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;//时间截向左移22位(5+5+12)private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;private long datacenterId;  //数据中心private long machineId;     //机器标识private long sequence = 0L; //序列号private long lastStmp = -1L;//上一次时间戳/*** 构造函数* @param datacenterId  数据标识Id(0-31)* @param machineId     //机器标识Id(0-31)*/public SnowFlake(long datacenterId, long machineId) {if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");}if (machineId > MAX_MACHINE_NUM || machineId < 0) {throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");}this.datacenterId = datacenterId;this.machineId = machineId;}/*** 产生下一个ID** @return*/public synchronized long nextId() {//获取当前时间戳long currStmp = getNewstmp();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (currStmp < lastStmp) {throw new RuntimeException("Clock moved backwards.  Refusing to generate id");}//如果是同一时间生成的,则进行毫秒内序列递增if (currStmp == lastStmp) {//相同毫秒内,序列号自增sequence = (sequence + 1) & MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if (sequence == 0L) {currStmp = getNextMill();}} else {//不同毫秒内,序列号置为0sequence = 0L;}lastStmp = currStmp;return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分| datacenterId << DATACENTER_LEFT       //数据中心部分| machineId << MACHINE_LEFT             //机器标识部分| sequence;                             //序列号部分}private long getNextMill() {long mill = getNewstmp();while (mill <= lastStmp) {mill = getNewstmp();}return mill;}private long getNewstmp() {return System.currentTimeMillis();}public static void main(String[] args) {SnowFlake snowFlake = new SnowFlake(2, 5);long start = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {System.out.println(snowFlake.nextId());}System.out.println((System.currentTimeMillis() - start)/1000 + "秒");}
}

3 商品下单时 生成订单号

基本实现思路是 用户下单,有基本库存时,生成订单号,发送队列消息,将生成的订单号返回到前端

    @AutowiredSnowFlakeCompone snowFlakeCompone;@Autowiredprivate OrderMQSender mqSender;@Overridepublic R createPreOrder(Long goodsId, Long userId) {log.info("预下单处理 userId:{} goodsId:{} ", userId, goodsId);//获取redis中的商品库存 先判断商品是否有库存Boolean aBoolean = redisTemplate.hasKey("goodStock:" + goodsId);if (Boolean.FALSE.equals(aBoolean)) {return R.error("下单失败 商品库存不足");}//Redis 缓存获取商品库存int goodsStock = Integer.valueOf(redisTemplate.opsForValue().get("goodStock:" + goodsId).toString());if (goodsStock == 0) {return R.error("下单失败 商品库存不足");}//生成订单号long sn = snowFlakeCompone.getInstance().nextId(); //保存到redis中 状态 doing 正在处理中 //过期时间30分钟redisTemplate.opsForValue().set("sn:" + sn, "doing",30, TimeUnit.MINUTES);//发送下单消息SecKillMessage message = new SecKillMessage(userId, goodsId, sn);mqSender.sendCommonOrderMessage(JsonUtils.toJson(message));//把商品订单号返回到前端return R.okData(sn);}

然后 前端根据这个预下单的 订单号轮循查询订单详情,根据不同的状态码来实现不同的页面显示

@Api(tags="订单模块")
@RestController()
@RequestMapping("/orders")
@Slf4j
public class OrderController {@Autowiredprivate OrderService orderService;@Autowiredprivate RedisTemplate redisTemplate;/*** 查询订单状态与详情* 商品-下单入口调用* @param sn* @return*/@GetMapping("/statues/detail/{sn}")public R detailAndStatue(@PathVariable("sn") Long sn) {//redis 中查询状态Boolean aBoolean = redisTemplate.hasKey("sn:" + sn);if(Boolean.FALSE.equals(aBoolean)){return R.error("下单失败");}String snStatues = redisTemplate.opsForValue().get("sn:" +sn).toString();if(snStatues.equals("doing")){return R.error(202,"处理中");}if(!snStatues.equals("ok")){return R.error(203,snStatues);}//下单成功 返回订单信息OrderVo orderVo = orderService.detailFromSn(sn);return R.okData(orderVo);}}

然后启动项目 使用 apache-jmeter-5.5 调试 20000 的并发量

商品库存只有10个,然后查看生成的订单,订单号未重复

然后 postman 查询未下单成功的订单


再查询一下 下单成功的订单

查询到未支付状态的订单 ,前端再去调用支付代码。

redisTemplate.opsForValue().set("2","早起的年轻人",2, TimeUnit.SECONDS);//过期时间2秒
redisTemplate.opsForValue().set("2","早起的年轻人",2, TimeUnit.MINUTES);//过期时间2分钟
redisTemplate.opsForValue().set("2","早起的年轻人",2, TimeUnit.HOURS);//过期时间2小时
redisTemplate.opsForValue().set("2","早起的年轻人",2, TimeUnit.DAYS);//过期时间2天

时间类型:TimeUnit

TimeUnit.SECONDS:秒
TimeUnit.MINUTES:分
TimeUnit.HOURS:时
TimeUnit.DAYS:日
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MILLISECONDS:微秒
TimeUnit.NANOSECONDS:纳秒

本文章是系列文章 ,每节文章都有对应的代码,每节的源码都是在上一节的基础上配置而来,对应的视频讲解课程正在火速录制中。

本文章只有核心代码,全部代码请查看对应源码
项目源码在这里 :https://gitee.com/android.long/spring-boot-study/tree/master/biglead-api-11-snow_flake
有兴趣可以关注一下公众号:biglead


  1. 创建SpringBoot基础项目
  2. SpringBoot项目集成mybatis
  3. SpringBoot 集成 Druid 数据源【SpringBoot系列3】
  4. SpringBoot MyBatis 实现分页查询数据【SpringBoot系列4】
  5. SpringBoot MyBatis-Plus 集成 【SpringBoot系列5】
  6. SpringBoot mybatis-plus-generator 代码生成器 【SpringBoot系列6】
  7. SpringBoot MyBatis-Plus 分页查询 【SpringBoot系列7】
  8. SpringBoot 集成Redis缓存 以及实现基本的数据缓存【SpringBoot系列8】
  9. SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】
  10. SpringBoot Security认证 Redis缓存用户信息【SpringBoot系列10】
    11 . SpringBoot 整合 RabbitMQ 消息队列【SpringBoot系列11】
    12 . SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】

SpringBoot 雪花算法生成商品订单号【SpringBoot系列13】相关推荐

  1. Java如何生成序列号/订单号

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 每天 14:00 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java ...

  2. 根据时间戳生成编号_使用雪花算法生成流水号!

    前言"在分布式系统中常见的问题就是如何生成流水号,一般情况下会有专门的流水号系统,不过在开发过程中或者开发早期不一定会有专门流水号系统,在这里介绍下我所使用的流水号生成器--雪花算法&quo ...

  3. 线上使用雪花算法生成id重复问题

    项目中使用的是hutool工具类库提供的雪花算法生成id方式,版本使用的是5.3.1 <dependency><groupId>cn.hutool</groupId> ...

  4. 生成唯一订单号_人人皆知却暗藏玄机的“数据库唯一标识符”

    Hello 大家好,今天给大家讲一个几乎所有数据库都支持的特性:生成唯一标识符. 知乎视频​www.zhihu.com 详细内容: 唯一标识符应用场景非常多,比如网站注册时自动给新用户一个唯一 ID, ...

  5. java生成唯一订单号

    /*** 生成唯一订单号* 规则:四位随机数+"M"+格式化到秒的时间+"R"+六位随机数*/public static String getBillCode( ...

  6. mybatis-plus雪花算法生成Id使用详解

    文章目录 前言 一.mybatis-plus官网 二.雪花算法实战 1.建表 2.新建测试工程 3.单元测试 三.实现分析 四.为什么默认就是雪花算法 五.主动设置Id生成策略 六.内置的雪花算法工具 ...

  7. MySQL高并发生成唯一订单号的方法

    高并发下生成唯一订单号的存储过程 这个是用mysql写的存储过程,搭配里面一张数据表使用,达到高并发情况下获得唯一订单号的目的:原理:按照一定规则生成订单号后,把订单号插入数据表后,再返回给用户,由于 ...

  8. 雪花算法及运用PHP,雪花算法生成全局唯一ID,参考了下网上雪花算法生成规则,机器ID和序列号自动获取 理论上毫秒可生成 1024*4096个唯一ID

    任务要求毫秒生成10000个唯一ID 研究了下twitter/snowflake的算法思想: 参考了下网上雪花算法生成规则,把数据中心和机器编号整合一起,变成10位机器ID, 机器ID和序列号自动获取 ...

  9. 生成唯一订单号 工具类

    package com.jsy.basic.util.utils;import java.text.SimpleDateFormat; import java.util.Date;/*** @prog ...

最新文章

  1. android 抓取native层奔溃
  2. 异常详细信息: System.ArgumentException: 不支持关键字: “metadata”。
  3. opencv 将图片合成为视频流(AVI格式)
  4. 5月第2周编辑部标题训练:寻找读者最关注的新闻点
  5. 3、绘制E-R图:数据库概要设计阶段
  6. java解析lrc_java中用正则表达式解析LRC文件
  7. 异想-天开 python---while、for、if-else 循环学习
  8. 分布式系统CAP定理
  9. 按应用领域来划分,电话光端机主要分为哪几类
  10. 美团点评移动网络优化实践
  11. java jar包 配置文件_java 导入jar包中配置文件
  12. 计算机应用基础1,计算机应用基础1
  13. python中scrapy的middleware是干嘛的_Python之爬虫(十九) Scrapy框架中Download Middleware用法...
  14. iPhone 12系列起售价又要涨了?128GB起步...
  15. VB:将数字转换为大写中文
  16. Java的15种锁总结
  17. microbiomeViz:绘制lefse结果中Cladogram
  18. 16个Javascript的Web UI库、框架及工具包
  19. 生死看淡,不服就GAN
  20. 南昌大学航天杯第二届程序设计竞赛校赛网络同步赛 部分题解

热门文章

  1. 特大通知!!!CSDN和简书博客以后同步更新
  2. 前端解决图片404的问题
  3. 【pytorch】BN层计算
  4. 【征集反馈】工作中让你印象最深刻、最想吐槽的一件事是什么?
  5. 运维常用工具命令/知识总结
  6. 手机IMEI串码获取
  7. 《算法笔记》学习笔记(1)
  8. Unreal - Environment Query System(EQS)
  9. SynthWave '84 - VS Code theme小白安装教程
  10. 《数据挖掘》读书笔记2、3章节