高并发下生成自定义规则的订单号

目录

背景

规则

问题

分析

思路

数据库

线程锁

方案

讨论



  • 背景

半年以前做的一个流程相关的项目,近期在做性能测试;之前的功能测试已经做完了,都没有什么问题。   

项目采用的springmvc框架,生成订单号以及存储订单号都是在activiti的监听service中进行的。项目业务数据库和activiti数

据库是分离的。代码流程为  业务service-->监听service-->业务service。

  • 规则

这里重点说明一下我们系统的订单号生成规则:项目名-YYYYMMDD-(从当年1月1日计目前的订单数量+1)COUNT;即

订单号的生成由3部分组成(项目名、日期、COUNT),其中项目名固定,日期由系统生成,最后一个COUNT是需要通过读取

数据库来进行计算的。

  • 问题

在性能测试时发现在多线程情况下生成的订单号是重复的,导致了后台报错,只一条创建成功,其他创建都以失败告终。

  • 分析

之前的代码如下,没有考虑到多线程高并发情况下的线程案例问题。

	private String getNum(){
		AtomicInteger count = new AtomicInteger(数据库获取大于今年的个数);
		return String.format("%04d", count.incrementAndGet());
	}
  • 思路

面对多线程的情况,之前想到了2个思路:数据库和线程锁

数据库

从数据库层面解决,直接在数据库创建一个function用来生成订单号,在插入数据的时候就调用这个function以此来解决高并发的情

况,这种方式如果能实现效果是最好的。

create or replace function fillWords() returns varchar as $fillWords$
declare 
	temp_word varchar;
	temp_count int4;
begin
	SELECT
				(COUNT (T .node_id) + 1) into temp_count 
			FROM
				t_base_order T
			WHERE
				T .start_time >= date_trunc('year', now()); 
	temp_word := temp_count || '';
	if(length(temp_word) < 4) then 
		temp_word := lpad(temp_word, 4, '0');
	end if;
	return 'RMS-' || to_char(now(), 'yyyyMMdd') || temp_word;
end;
$fillWords$ language plpgsql;

使用这种方式试了一下,还是会生成重复的原因。后来一分析发发现,单条插入数据时的事务并没有commit,因此在高并发情况下

查询的COUNT还是一样的(数据库默认的隔离级别为Read committed);一想那我就修改隔离等级吧,一查,我勒个去,postgresql直接

不支持Read uncommitted,那个伤心......

那就来做提交事务吧,项目采用的是声明式事务,如果要单个提交事务就需要编程式事务,修改了一下午,是各种报错,也可能是

我自己没有修改对吧。总之这种手动事务的方式最终是没有成功。

线程锁

听性能测试的同事说,线程锁这种形式非常损耗性能,但没办法了,数据库的方案形不通,那就只能这种方式了呗。

1、最开始的想法是使用LOCK直接把生成订单号和插入数据的代码块直接锁起来,但不知道为什么还是有重复的出现;后来一想,

也是事务没有提交导致的。

2、听另外一个同事说,加到代码块上不行,应该把锁加到方法上,于是一试,还是重复,崩溃呀。

想了2天也不知道使用什么方法,网上百度的都是使用随机数来进行处理,但与我们项目的规则不符合呀。

最终突然想到一个办法,就是利用JAVA的AtomicInteger来实现,因此这个类是线程安全的。思路如下:

第一次的时候,直接把查询的个数加载到static变量中,然后利用这个变量的自增来给COUNT设值,感觉这个方案不错,一试还真

行,效率也不差。具体实现,请看最后一节。

  • 方案

直接贴代码:

private static AtomicInteger count = new AtomicInteger(0);

private String getNum(){
	if(count.get() == 0){
		LOG.error("+++++++++"+count.get());
		synchronized(count){
			LOG.error("----------"+count.get());
			//下面这个再次判断很重要,防止了多次设值
			if(count.get() == 0){
				count.set(数据库获取大于今年的个数);
				LOG.error("**********"+count.get());
			}
		}
	}
	return String.format("%04d", count.incrementAndGet());
}

这种实现方案,只是第一次并发的情况下加锁损耗了性能,第二次并发的情况下就不会有锁的影响了;并且由于锁之中的再次判

断,既防止了多次设值,也提交了效率,这个设计不错(自我感觉哈^_^)。

  • 讨论

最后的这个方案其实也有问题,就是如果项目要做负载均衡的情况下,也会出现重复单号的情况,因此如果项目要做分布式部署的

情况下,可以将这段代码发布成一个公用服务,所有的分布式部署都调用这个公用服务,这样就不会有问题了。但我们项目不会做分布式

部署,因此就不考虑公用服务这种方案了。

各位有什么好的想法,也希望大家一起交流哈。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值