Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. 架构方案
  3. 正文
                           

【转载】系统设计 | 业务编号生成

2023-10-29 3087点热度 0人点赞 0条评论

系统设计 | 业务编号生成

问题

假设,业务上需要生成订单编号,其要求为:

  1. 不能太长,固定 10 位。
  2. 生成规则为:“订单类型字母缩写” + “年月日” + 步长为 1 的递增序列。
  3. 单号每天重新开始。

性能要求一般,日均单量一万左右,订单均匀分布,并发不高。

经过分析,考虑如下特点:

  1. 不能使用 UUID,因为过长且有顺序问题。
  2. 不能直接使用数据库的自增 ID 主键,可以考虑通过自增主键进行复合处理。
  3. 并发量不高,无需考虑分布式生成,可以集中生成。
  4. 业务上是否需要做到严格连续?如果不需要严格连续,可以提高生成性能。

方案分析

在方案阶段,我们通过头脑风暴枚举出所有可行的方案和考虑点(这是一条做方案的经验,可能有些方案看起来有些愚蠢,但是往往后面真的有用)。

  1. 直接使用时间戳,如果被占用就重试一次。虽然脑洞有点大,但是确实可行。不过在这个场景中有长度限制,加上前缀后可能超长。
  2. 直接使用数据库的 sequence 来实现,取决于是使用数据库是否支持,也需要手动重置序列或者为每个 key 生成一个序列。
  3. 使用自增 ID 组合实现,使用另外一张表的自增 ID 作为计数器实现,并通过定时任务每天晚上重置。
  4. 使用表锁,直接在订单表中操作,每次 select max(number) + 1 然后 insert 一条数据到数据库。
  5. 使用行锁,在数据库中将前缀作为 key,这样每天一行数据,通过 set number=number+1 where key=‘当天的 key 值’。
  6. 在内存中的原子类实现。
  7. 使用发号服务,从服务器申请规划号段,在应用服务实例领取号段并在内存中使用。
  8. 使用 Redis 结合单号前缀作为 Key 这样可以天然的支持当日自增,用后即可丢弃。
  9. 在数据库中冗余一个序号字段,通过这个字段拼接前缀作为编码。
  10. 使用自定义的存储过程实现。
  11. 为每个服务器实例编号,在内存中各自实现,类似于雪花算法。

将方案枚举后,经过分析:

  • 方案 1 确实脑洞过大,不太实用。
  • 如果数据库支持 方案 2 是最佳方案,无需维护额外的配置信息,非常简洁。
  • 方案 3、4 在很多项目中确实存在,但是可维护性和性能都不佳。
  • 方案 5 看似很奇怪,但是除了额外多一个编码表外,没有过多的维护逻辑,在没有 Redis 的情况下可以使用。
  • 方案 6 在现代系统基本不可行。
  • 方案 7 在并发要求极高的情况下才需要使用,一般项目不至于做的这么复杂。
  • 方案 8 无需引入额外的数据库表,根据日期作为 Key 生成序号,但是需要使用 Redis 作为发号器,且 Redis 数据可能丢失。
  • 方案 9 需要冗余一个额外的字段。
  • 方案 10、11 虽然可行,但是维护性太差,且方案太冷门,出问题不好解决。

推荐方案

根据上面的分析,最终选择方案需要考虑这几个点:

  • 是否满足业务需要
  • 基础设施是否满足
  • 可维护性
  • 性能
  • 可靠性

假定使用了 Oracle、PostgreSQL 可以直接使用方案 2 的 sequence 实现。

假定我们使用了 Mysql,对性能没有过高要求,且具有高可用的哨兵 Redis 也容易获得,那么可以考虑方案 8。

假定使用 Mysql 且没有趁手的 Redis 基础设施,也可以额外通过一张数据库表每行一个 key 值来实现不同场景下的计数需求。

假定需要极高性能,则通过方案 7 的发号服务来实现。

其它注意事项

编号往往通过前端调用后端提前生成,因为需要在受理业务的时候(还没持久化)用户就能看到,如果前端取消数据提交可能浪费掉部分编号,所以需要提前和业务方沟通。

避免通过 select count(0) 来获取最大的值这类方案,效率低下且需要锁表。

如果 Redis 中的 Key 丢失,解决方案是手动的提取业务单号的最大值,或者系统启动时候检查,自动修复一次。

使用一个公共服务或者使用一个 Common 包来实现此类功能。

参考实现代码(ChatGPT 实现)

在实现代码上可以利用 AI 快速实现 Demo,这恰好是 AI 擅长的工作领域。

提问 1:请使用 Oracle、PostgreSQL 的 sequence 实现一个按日生成的业务单号,单号包含“D”作为前缀,单号序列每日重置。

回复如下:

使用 Oracle 的 sequence 实现,首先创建一个序列(sequence):

CREATE SEQUENCE daily_sequence
    INCREMENT BY 1
    START WITH 1
    MAXVALUE 999999999
    MINVALUE 1
    CYCLE
    NO CACHE;

 

然后,可以在插入数据时使用序列来生成单号:

INSERT INTO your_table (id, name, create_date)
VALUES ('D' || TO_CHAR(SYSDATE, 'YYYYMMDD') || lpad(daily_sequence.nextval, 6, '0'), 'your name', SYSDATE);

 

该语句将在插入数据时将 “D” 作为前缀,然后使用 SYSDATE 函数获取当前日期,并将其格式化为 YYYYMMDD 的字符串。然后,使用 lpad 函数将序列号左侧补齐 0,确保单号长度为 10 位。最后,将前缀、日期和序列号连接在一起,作为该行的主键。

为了每日重置序列,可以使用 DBMS_SCHEDULER 包中的定时任务来在每日零点重新设置序列的起始值。例如:

BEGIN
      DBMS_SCHEDULER.CREATE_JOB (
        job_name => 'reset_daily_sequence',
        job_type => 'PLSQL_BLOCK',
        job_action => 'BEGIN daily_sequence.NEXTVAL; END;',
        start_date => TRUNC(SYSDATE) + 1,
        repeat_interval => 'FREQ=DAILY',
        enabled => TRUE,
        comments => 'Reset the daily_sequence every day at midnight.');
    END;
    /

 

该语句将创建一个名为 reset_daily_sequence 的定时任务,该任务将在每日零点之后的第一秒开始运行,然后每日重复执行,即每天将序列的起始值重置为 1。

PostgreSQL 的生成方式略过,可以自行询问 ChatGPT 或其他 AI。

提问 2:请使用 Java 和 Redis 实现一个按日生成的业务单号,单号包含“D”作为前缀,单号序列每日重置。

回复如下:

可以使用 Java 和 Redis 实现按日生成业务单号,并实现每日重置序列的功能。下面是一个示例实现:

import redis.clients.jedis.Jedis;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    
    public class DailySequenceGenerator {
        
        private final String prefix = "D";
        private final String dateFormat = "yyyyMMdd";
        private final String sequenceKey = "daily_sequence";
        private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(dateFormat);
        private final Jedis jedis = new Jedis("localhost", 6379); // Redis 连接配置
    
        public String generate() {
            // 获取当天日期并格式化为字符串
            String dateStr = LocalDate.now().format(dateFormatter);
    
            // 生成单号,格式为 "DYYYYMMDD000001"
            String key = prefix + dateStr;
            Long sequence = jedis.incr(sequenceKey);
            String sequenceStr = String.format("%06d", sequence);
            String id = key + sequenceStr;
    
            return id;
        }
    
        public void reset() {
            // 重置序列
            jedis.del(sequenceKey);
        }
    
        public static void main(String[] args) {
            DailySequenceGenerator generator = new DailySequenceGenerator();
    
            // 生成单号并打印
            String id = generator.generate();
            System.out.println(id);
    
            // 重置序列并生成新的单号
            generator.reset();
            id = generator.generate();
            System.out.println(id);
        }
    }

 

该类中包含了生成和重置序列的方法。在 generate 方法中,首先获取当天日期并将其格式化为字符串。然后,使用 Redis 的 incr 命令递增名为 daily_sequence 的键的值,生成一个序列号。接下来,将前缀、日期和序列号连接在一起,作为单号的主键。

在 reset 方法中,只需要删除 Redis 中存储的序列键即可实现每日重置序列的功能。

最后,在 main 方法中,可以通过调用 generate 方法来生成单号,并通过调用 reset 方法来重置序列。

总结

技术方案分析是富有经验的软件工程师的强项,经过探索技术方案做合适的系统设计,并通过 AI 给出 Demo,提高开发效率。

充分系统设计也可以避免软件项目后期的问题,另外出具的系统设计方案也需要经过评审。

在技术决策上,坚持“先澄清问题-枚举方案-权衡选择”的过程,一定要避免直接先入为主进入解决方案。

在系统设计的过程中,大部分问题基本都是对问题没有充分分析,以及过早进入解决方案,先入为主的通过过往经验直接进行决策。

参考资料

  • https://blog.51cto.com/u_11576068/4795982
  • http://www.yufumoju.com/post/87594.html

文 | 少个分号 (转载请注明出处)

关注公众号:DDD和微服务

微信号:shaogefenhao

同名知乎:少个分号

本文仅供学习!所有权归属原作者。侵删!文章来源: DDD和微服务 -少个分号 :http://mp.weixin.qq.com/s?__biz=MzA4Mzc2MzcyMQ==&mid=2247484515&idx=1&sn=ffb2d3200b0a8129bb729fe6596ba098&chksm=9ff03305a887ba13f53fc328e892ef93be2be28273402c06c5d89e2f1e892df2e81fa6394395&scene=21#wechat_redirect

更多文章:

  1. 一次访问Redis延时高问题排查与总结
  2. 全链路压测之影子库及ShardingSphere实现影子库源码剖析
  3. 6种限流实现,附代码![通俗易懂]
  4. 殷浩详解DDD 第四讲:领域层设计规范
  5. 一次 Redis 事务使用不当引发的生产事故
  6. 分布式唯一 ID 生成方案浅谈
  7. 【进阶玩法】策略+责任链+组合实现合同签章
  8. 殷浩详解DDD系列 第一讲 - Domain Primitive
  9. 浅析Redis大Key
  10. 浅析设计模式3 —— 装饰者模式
标签: 转载 系统设计 业务编号 方案设计 订单号
最后更新:2023-10-29

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 问题
  • 方案分析
  • 推荐方案
  • 其它注意事项
  • 参考实现代码(ChatGPT 实现)
  • 总结
  • 参考资料
最新 热点 推荐
最新 热点 推荐
Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代 视频笔记-微服务架构P4:必懂5种设计模式 视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务?
基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性视频笔记-微服务架构P4:必懂5种设计模式Anthropic Code with Claude 开发者大会:开启 AI Agent 新时代
浅析设计模式2 —— 策略模式 为啥不建议用BeanUtils.copyProperties拷贝数据 ChatGPT之帮我找出Bug! JVM 内存分析神器 MAT: Incoming Vs Outgoing References 你真的了解吗? Redis的BigKey(大key)、HotKey(热key)又引发了线上事故 仅使用set属性值就把数据库数据给改了 接口优化的常见方案实战总结 FSM-COLA无状态状态机

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (20) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2