Java技术干货分享:浅谈订单号生成设计方案

简单的技术计方方式
基于数据库 auto_increment_increment 来获取 ID。首先在数据库中创建一张 sequence 表,干货其中 seq_name 用以区分不同业务标识,分享从而实现支持多种业务场景下的浅谈自增 ID, current_value 为当前值,订单 _increment 为步长,号生可支持分布式数据库的成设哈希策略。
CREATE TABLE `sequence` ( `seq_name` varchar(200) NOT NULL,技术计方 `current_value` bigint(20) NOT NULL, `_increment` int(4) NOT NULL, PRIMARY KEY (`seq_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8通过 SELECT LAST_INSERT_ID() 方法,更新 sequence 表,干货进行 ID 递增,分享并同时获取上次更新的浅谈值。这里注意,订单 current_value = LAST_INSERT_ID(current_value + _increment) 将更新的号生 ID 赋值给了 LAST_INSERT_ID ,否则返回的成设将是行 id。
UPDATE sequence SET current_value = LAST_INSERT_ID(current_value + _increment) WHERE seq_name = #{seqName}最后 Dao 提供服务,技术计方需要提醒的是注意数据库的事务隔离级别,如果将 getSeq() 方法放到 Service 中有事务的方法里,将出现问题,因为数据库事务开启会创建一张视图,在事务没有提交之前,更新的 ID 还没有被提交到数据库中,这在多线程并发操作的站群服务器情况下,如果事务里的其他方法导致性能慢了,可能出现两个请求获取到相同的 ID,所以解决方法一是不要将 getSeq() 方法放到有事务的方法里,另一种就是将 getSeq() 方法的隔离界别为 PROPAGATION_REQUIRES_NEW ,实现开启新事务,外层事务不会影响内部事务的提交。
@Autowired private SeqDao seqDao; @Autowired private PlatformTransactionManager transactionManager; @Override public long getSeq(final String seqName) throws Exception { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); // 事务行为,独立于外部事物独立运行 transactionTemplate .setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); return (Long) transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { try { Seq seq = new Seq(); seq.setSeqName(seqName); if (seqDao.update(seq) == 0) { throw new RuntimeException("seq update failure."); } return seq.getId(); } catch (Exception e) { throw new RuntimeException("seq update error."); } } }); }稍复杂一点的方法
上述的方法的问题,想必大家都知道,就是每次获取 ID 都要调用数据库,在高并发的情况下会对数据库产生极大的压力,我们的改进方法也很简单,就是一次申请一个段的 ID,然后发到内存里,每次获取 ID 先从内存里取,当内存中的b2b信息网 ID 段全部被获取完毕,则再一次调用数据库重新申请一个新的 ID 段。
同样有数据库表的设计,通过 Name 区分业务,用 ID 标明已经申请到的最大值。当然如果是分布式架构,也可以通过增加步长属性来实现。
CREATE TABLE `sequence_value` ( `Name` varbinary(50) DEFAULT NULL, `ID` int(11) DEFAULT NULL ) ENGINE = InnoDB DEFAULT CHARSET = utf8Step 是 ID 段的内存对象,有两个属性,其中 currentValue 当前的使用到的值,endValue 是内存申请的最大值。
class Step { private long currentValue; private long endValue; Step(long currentValue, long endValue) { this.currentValue = currentValue; this.endValue = endValue; } public void setCurrentValue(long currentValue) { this.currentValue = currentValue; } public void setEndValue(long endValue) { this.endValue = endValue; } public long incrementAndGet() { return ++currentValue; } }代码的实现稍微复杂一点,获取 ID 会根据业务标识 sequencename,先从内存获取 Step 的 ID 段,如果为 null,则从数据库中读取当前最新的值,并根据步长计算 Step,然后返回请求 ID。如果从内存中直接获取到 Step,则直接取 ID,服务器托管并对 currentValue 进行加一。当 currentValue 的值超过 endValue 时,则更新数据库的 ID,重新计算 Step。
private Map<String,Step> stepMap = new HashMap<String, Step>(); public synchronized long get(String sequenceName) { Step step = stepMap.get(sequenceName); if(step ==null) { step = new Step(startValue,startValue+blockSize); stepMap.put(sequenceName, step); } else { if (step.currentValue < step.endValue) { return step.incrementAndGet(); } } if (getNextBlock(sequenceName,step)) { return step.incrementAndGet(); } throw new RuntimeException("No more value."); } private boolean getNextBlock(String sequenceName, Step step) { // "select id from sequence_value where name = ?"; Long value = getPersistenceValue(sequenceName); if (value == null) { try { // insert into sequence_value (id,name) values (?,?) value = newPersistenceValue(sequenceName); } catch (Exception e) { value = getPersistenceValue(sequenceName); } } // update sequence_value set id = ? where name = ? and id = ? boolean b = saveValue(value,sequenceName) == 1; if (b) { step.setCurrentValue(value); step.setEndValue(value+blockSize); } return b; }使用该方法获取 ID 可以减少对数据库的访问量,以降低数据库的压力,但是同样需要注意,获取 ID 同样关注数据库事务问题,因为当系统重启的时候,stepMap 为 null,所以会取数据库查询当前 ID,更计算更新 Step,然后更新数据库的 ID。如果该方法被放到数据库事务里,由于其他方法性能慢了,导致查询之后没有及时更新,并发情况下另一个线程查询的时候,可能会获取到该线程未提交的 ID,因而出现两个线程获取到相同的 ID 问题。
本文小结
订单号生成是一个非常简单的功能,但是在高并发的场景下,高性能和高可用就成为了需要关注的要点。所以,实际工作中的每一个小细节都值得我们去深思。
相关文章
大家好,我是明哥。今天给大家分享一下自己整理的一篇 Python 参数的内容,内容非常的干,全文通过案例的形式来理解知识点,自认为比网上 80% 的文章讲的都要明白,如果你是入门不久的 python2025-11-05
1. 寻找第K大题意有一个无序整数数组,请你根据排序思路,找出数组中第K大的数。给定一个整数数组a, 请返回第K (1<=K<=n) 大的数(包括重复的元素,不用去重),保证答案存在。示例2025-11-05
有道无术,术尚可求也!有术无道,止于术!我们上一章分析了Netty中NioServerSocketChaennl的创建于初始化,本章节将继续分析NioServerSocketChannel的分析,Ni2025-11-05- 解决方法如下打开终端,输入以下命令:cd /etc/fonts/conf.d/sudo cp 49-sansserif.conf 49-sansserif.conf_backupsudo rm 49-2025-11-05
我在 Shopee 维护一个 Service Mesh 系统,大部分的 RPC 调用要经过这个系统,这个系统每分钟要处理上千万的请求。我们在本文中就把它叫做 Oitsi 系统吧,方便描述一些。干的事情2025-11-05


最新评论