100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > java单号生成器_订单号生成工具类 - Yaphis的个人页面 - OSCHINA - 中文开源技术交流社区...

java单号生成器_订单号生成工具类 - Yaphis的个人页面 - OSCHINA - 中文开源技术交流社区...

时间:2018-07-18 01:40:46

相关推荐

java单号生成器_订单号生成工具类 - Yaphis的个人页面 - OSCHINA - 中文开源技术交流社区...

引言

许多企业系统都涉及到了订单号的生成。订单号可以帮我们我们标识用户的一次行为。因此它必须是全局唯一的。我们当然可以采用类似UUID这种全时空唯一的字符串来标识一个订单,但是UUID对于用户和我们自己来说都过于复杂了,用户无法记忆甚至无法用它来要求客服查询。一个好的订单号在保证简单、唯一性的情况下,应该具有自解性。根据这个订单号我们可以解读出用户购买的业务、购买的时间等信息。下面介绍几种订单号的生成方案。

解决方案

加锁和时间戳

如果是在小型单台服务的系统上,订单号的生成可以采用简单的时间戳(精确到毫秒)配合JVM级别的锁来实现。但是更常见的情况是,我们出于系统高可用、负载均衡等方面的考虑,部署的架构往往是多服务器的。这种情况下,就必须重新考虑订单号的生成策略了。

数据库自增ID

利用数据库的自增ID,结合当前时间戳或者业务编号,是一种非常常见的订单号生成方式。有两种实现方式:

新建一张表定义自增列,在应用层获取该列的自增值

新建一张序列表,保存自增序列的当前值

这两种实现方式各有优劣:

方式1必须定义该列的字段足够大,否则可能达到最大值后会报错。

方式2序列的增长在应用层,必须保证事物。

它们共同的问题是:

在生成订单号时,我们都需要进行一次数据库操作,在高并发高访问的情况下,容易造成数据库压力过大,形成性能瓶颈。

系统足够庞大,进行分库分表之后。又会带来保证序列唯一性的额外问题。

它们很容易泄漏商品的销量和操作次数这些敏感信息

集中式ID管理

常见的方式有:

将自增序列保存到集中式or分布式缓存中,由于集群服务是共享分布式缓存的,因此很好地解决了订单号的唯一性问题,同时缓存具有足够的伸缩性,也可以做高可用、持久化。

订单号服务,独立的订单号生成管理服务,提供给各业务线进行调用。

集中式ID管理的优势很明显,无论是性能扩展还是高可用方面都有非常好的解决方案。劣势就是系统较为庞大,需要搭建专门的缓存服务(如redis、tair、memcached等),订单号服务为了避免单点故障还需要做冗余。

系统标识的订单号生成方案

我们可以根据系统的一些唯一属性来生成唯一的订单号,这个属性可以是服务器的IP、机器码、MAC地址等,结合JVM锁,既避免了数据库自增ID的性能瓶颈问题,又无需集中式ID管理的大材小用。下面是我的一个简单实现方案。

是通过时间戳+IP后两位+自增序列+随机数来生成的订单号,使用可重入锁代替内置锁(synchronized)以解决在高并发请求下的细微性能问题。

mons.util.order;

.InetAddress;

.UnknownHostException;

importjava.text.SimpleDateFormat;

importjava.util.Date;

importjava.util.Random;

importjava.util.concurrent.TimeUnit;

importjava.util.concurrent.locks.Lock;

importjava.util.concurrent.locks.ReentrantLock;

mons.lang3.StringUtils;

importorg.slf4j.Logger;

importorg.slf4j.LoggerFactory;

/**

*订单号生成器

*

*@authorYaphis4月29日下午7:12:44

*/

publicclassOrderGenerater{

privatestaticfinalLoggerLOG=LoggerFactory.getLogger(OrderGenerater.class);

privatevolatilestaticintserialNo=0;

privatestaticfinalStringFORMATSTRING="yyMMddHHmmssSSS";

/**

*使用公平锁防止饥饿

*/

privatestaticfinalLocklock=newReentrantLock(true);

privatestaticfinalintTIMEOUTSECODES=3;

/**

*生成订单号,生成规则时间戳+机器IP最后两位+2位随机数+两位自增序列

*采用可重入锁减小锁持有的粒度,提高系统在高并发情况下的性能

*

*@parambuinessId

*@return

*/

publicstaticStringgenerateOrder(){

StringBuilderbuilder=newStringBuilder();

builder.append(getDateTime(FORMATSTRING)).append(getLastNumOfIP());

builder.append(getRandomNum()).append(getIncrement());

returnbuilder.toString();

}

/**

*获取系统当前时间

*

*@paramformatStr

*@return

*/

privatestaticStringgetDateTime(StringformatStr){

SimpleDateFormatformat=newSimpleDateFormat(formatStr);

returnformat.format(newDate());

}

/**

*获取自增序列

*

*@return

*/

privatestaticStringgetIncrement(){

inttempSerialNo=0;

try{

if(lock.tryLock(TIMEOUTSECODES,TimeUnit.SECONDS)){

if(serialNo>=99){

serialNo=0;

}else{

serialNo=serialNo+1;

}

tempSerialNo=serialNo;

}else{

//指定时间内没有获取到锁,存在激烈的锁竞争或者性能问题,直接报错

LOG.error("cannotgetlockin:{}seconds!",TIMEOUTSECODES);

thrownewRuntimeException("generateOrdercannotgetlock!");

}

}catch(Exceptione){

LOG.error("tryLockthrowsException:",e);

thrownewRuntimeException("tryLockthrowsException!");

}finally{

lock.unlock();

}

if(tempSerialNo

return"0"+tempSerialNo;

}else{

return""+tempSerialNo;

}

}

/**

*返回两位随机整数

*

*@return

*/

privatestaticStringgetRandomNum(){

intnum=newRandom(System.nanoTime()).nextInt(100);

if(num

return"0"+num;

}else{

returnnum+"";

}

}

/**

*获取IP的最后两位数字

*

*@return

*/

privatestaticStringgetLastNumOfIP(){

Stringip=getCurrentIP();

returnip.substring(ip.length()-2);

}

/**

*获取本机IP

*

*@return

*/

privatestaticStringgetCurrentIP(){

Stringip="";

try{

ip=InetAddress.getLocalHost().getHostAddress();

}catch(UnknownHostExceptione){

LOG.error("getLocalHostthrowsUnknownHostException:",e);

thrownewRuntimeException("cannotgetip!");

}

if(StringUtils.isBlank(ip)){

LOG.error("ipisblank!");

thrownewRuntimeException("ipisblank!");

}

returnip;

}

}

附一段测试代码

mons.util.order;

importjava.util.ArrayList;

importjava.util.List;

importorg.junit.Assert;

importorg.junit.Test;

importorg.slf4j.Logger;

importorg.slf4j.LoggerFactory;

/**

*订单号生成器单元测试

*

*@authorYaphis5月1日下午3:17:51

*/

publicclassOrderGeneraterTest{

privatestaticfinalLoggerLOG=LoggerFactory.getLogger(OrderGeneraterTest.class);

/**

*验证订单号生成是否会重复和耗时(单线程)

*/

@Test

publicvoidtestGenerateOrder(){

ListorderList=newArrayList();

longstartTime=System.currentTimeMillis();

for(inti=0;i

StringorderId=OrderGenerater.generateOrder();

if(orderList.contains(orderId)){

LOG.info("orderId:{}",orderId);

LOG.info("orderList:{},list:{}",newObject[]{orderList.size(),orderList});

Assert.fail("订单号重复!");

}

orderList.add(orderId);

}

LOG.info("generateOrdercost:{}",System.currentTimeMillis()-startTime);

}

/**

*验证订单号生成是否会重复和耗时(多线程)

*/

publicstaticvoidmain(String[]args){

Listlist=newArrayList();

longstartTime=System.currentTimeMillis();

//模拟1000个并发请求

for(intj=0;j

newThread(newThreadTest(list)).start();

}

LOG.info("testGenerateOrderMultithreadcost:{}",System.currentTimeMillis()-startTime);

}

/**

*测试线程

*

*@authorYaphis5月1日下午3:59:19

*/

publicstaticclassThreadTestimplementsRunnable{

privateListlist;

publicThreadTest(Listlist){

this.list=list;

}

publicvoidrun(){

for(inti=0;i

StringorderId=OrderGenerater.generateOrder();

if(list.contains(orderId)){

LOG.error("订单号重复!");

break;

}

list.add(orderId);

}

}

}

}

其他:

以上实现其实还比较简陋,许多情况没有考虑。例如多网卡情况下的多IP问题等、欢迎大家交流指正!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。