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。

<insert timeout="30" id="update" parameterType="Seq">

UPDATE sequence

SET

current_value = LAST_INSERT_ID(current_value _increment)

WHERE

seq_name = #{seqName}

<selectKey resultType="long" keyProperty="id" order="AFTER">

<![CDATA[SELECT LAST_INSERT_ID() ]]>

</selectKey>

同样有数据库表的设计,通过 Name 区分业务,用 ID 标明已经申请到的最大值。当然如果是分布式架构,也可以通过增加步长属性来实现。

CREATE TABLE `sequence_value` (

`Name` varbinary(50) DEFAULT NULL,

`ID` int(11) DEFAULT NULL

) ENGINE = InnoDB DEFAULT CHARSET = utf8

Step 是 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。

java生成订单号主流方法(浅谈订单号生成设计方案)(1)

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 问题。

java生成订单号主流方法(浅谈订单号生成设计方案)(2)

本文小结

订单号生成是一个非常简单的功能,但是在高并发的场景下,高性能和高可用就成为了需要关注的要点。所以,实际工作中的每一个小细节都值得我们去深思。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页