当前位置: 首页 > 科技观察

浅谈订单号生成的设计方案

时间:2023-03-19 01:47:03 科技观察

今天我们讨论分享订单号生成的简单实现方案,针对实际场景中需要使用订单号生成服务的需求提供解决方案。最简单的方法是根据数据库auto_increment_increment获取ID。首先在数据库中创建一个sequence表,其中seq_name用于区分不同的业务标识,以支持各种业务场景下的自增ID,current_value为当前值,_increment为步长,可以支持对的哈希分布式数据库策略。CREATETABLE`sequence`(`seq_name`varchar(200)NOTNULL,`current_value`bigint(20)NOTNULL,`_increment`int(4)NOTNULL,PRIMARYKEY(`seq_name`))ENGINE=InnoDBDEFAULTCHARSET=utf8通过SELECTLAST_INSERT_ID()方法,更新序列表,增加ID,同时获取最后更新的值。这里注意current_value=LAST_INSERT_ID(current_value+_increment)将更新后的ID赋值给LAST_INSERT_ID,否则返回rowid。UPDATEsequenceSETcurrent_value=LAST_INSERT_ID(current_value+_increment)WHEREseq_name=#{seqName}最后,Dao提供服务。需要提醒的是数据库的事务隔离级别。如果getSeq()方法放在Service中有事务的方法中,就会有问题,因为数据库事务会创建视图。在事务提交之前,更新后的ID还没有提交到数据库。在多线程并发操作的情况下,如果事务中有其他方法导致性能变慢,可能会出现两次请求获取相同ID的情况,所以第一种解决方法是不要将getSeq()方法放在有事务的方法中,另一种是将getSeq()方法的隔离域设置为PROPAGATION_REQUIRES_NEW,实现开启一个新的Transaction,外层事务不会影响内层事务的提交。@AutowiredprivateSeqDaoseqDao;@AutowiredprivatePlatformTransactionManagertransactionManager;@OverridepubliclonggetSeq(finalStringseqName)throwsException{TransactionTemplatetransactionTemplate=newTransactionTemplate(transactionManager);//事务行为,独立于外部事物独立运行transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);return(Long)transactionTemplate.execute(newTransactionCallback(){publicObjectdoInTransaction(TransactionStatusstatus){try{Seqseq=newSeq();seq.setSeqName(seqName);if(seqDao.update(seq)==0){thrownewRuntimeException("sequpdatefailure.");}returnseq.getId();}catch(Exceptione){thrownewRuntimeException("sequpdateerror.");}}});}稍微复杂一点的方法上面这个方法的问题,我想大家都知道,就是每次获取ID,都要调用数据库.这样的话,会对数据库造成很大的压力。我们的改进方法也很简单,就是每次申请一个segmentID,然后送入内存。每次获取ID,都是先从内存中取出。获取到后,再次调用数据库,重新申请新的ID段。还有数据库表的设计。业务以Name区分,已申请的最大值以ID标示。当然,如果是分布式架构,也可以通过增加stepsize属性来实现。CREATETABLE`sequence_value`(`Name`varbinary(50)DEFAULTNULL,`ID`int(11)DEFAULTNULL)ENGINE=InnoDBDEFAULTCHARSET=utf8Step是ID段的内存对象,有两个属性,其中currentValue是当前使用的value和endValue是内存申请的最大值。classStep{privatelongcurrentValue;privatelongendValue;Step(longcurrentValue,longendValue){this.currentValue=currentValue;this.endValue=endValue;}publicvoidsetCurrentValue(longcurrentValue){this.currentValue=currentValue;}publicvoidsetEndValue(longendValue){this.end}Value=publiclongincrementAndGet(){return++currentValue;}}代码的实现稍微复杂一些。获取ID首先会根据业务标识sequencename从内存中获取该Step的ID段。如果为空,则从数据库中读取当前最新值。并根据步长计算Step,然后返回requestID。如果直接从内存中获取Step,则直接取ID,currentValue加1。当currentValue的值超过endValue时,更新数据库的ID,重新计算Step。privateMapstepMap=newHashMap();publicsynchronizedlongget(StringsequenceName){Stepstep=stepMap.get(sequenceName);if(step==null){step=newStep(startValue,startValue+blockSize);stepMap.put(sequenceName,step);}else{if(step.currentValue