以生成订单号为例:多个用户下单时,如果我们只部署了一个服务,那么在订单生成的方法上使用 synchronized 可以保证订单号唯一,但是应用部署在多个服务器上时,用户访问不同服务器上的服务时,synchronized 就不能同步了。换句话说,synchronized 只能保证一个应用中的同步。
多服务下保证订单号唯一
环境: Spring 环境,Junit 4 测试,Spring JdbcTemplate
原理:利用单号生成器表,记录当前的订单号,通过事务操作单号生成器表,先 update 订单号 + 1,再获取这个号码,拼接成订单号。
表结构:
# 订单表
CREATE TABLE `tb_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_number` varchar(255) NOT NULL COMMENT '订单号',
`creator` varchar(255) DEFAULT NULL COMMENT '服务名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
# 单号生成器
CREATE TABLE `tb_no_generator` (
`id` varchar(255) NOT NULL COMMENT '类型',
`next_num` int(11) NOT NULL COMMENT '计数器',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 计数器初始化
INSERT INTO `tb_no_generator` VALUES ('order', '0');
测试代码:
- 线程一
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
public class ByGenerator {
// 暂停 500 个线程
private CountDownLatch begin = new CountDownLatch(1);
// 启动 500 个线程
private CountDownLatch end = new CountDownLatch(500);
private ExecutorService exec = (500);
private ApplicationContext ctx;
private JdbcTemplate jt;
private PlatformTransactionManager transactionManager;
{
ctx = new ClassPathXmlApplicationContext("classpath:config/spring/**");
jt = (JdbcTemplate) ("jdbcTemplate");
transactionManager = (PlatformTransactionManager) ("transactionManager");
}
@Test
public void test() throws InterruptedException {
// 睡眠 2s,因为要启动两个单元测试,即每个单元测试都加载一次配置文件,相当于两个服务
// 为了保证启动第二个单元测试时,减小手动启动的时间误差,因此睡眠 2s
(2000L);
for (int i = 0; i < 500; i++) {
Runnable run = new Runnable() {
public void run() {
try {
// 使创建好的线程全部等待
();
// 开启事务,如果没有开启事务,那么订单号会重复。在实际开发中,在 service 层加上 @Transactional 注解即可,这里因为是单元测试,因此我使用了编程式事务
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = (def);
("insert into tb_order (order_number,creator) values ( " + generate()
+ ", 'ByGenerator')");
// 提交事务
(status);
} catch (InterruptedException e) {
();
} finally {
// 每创建一个线程,计数器 -1
();
}
}
};
(run);
}
// 当执行完 for 循环时,释放所有线程
();
();
();
}
private String generate() {
// 获取今天的日期
String date = (new Date(), "yyyyMMdd");
// 首先将单号生成器表 tb_no_generator 的计数器 +1
("update tb_no_generator set next_num= next_num + 1 where id = 'order' ");
// 查询单号
String next_num = ("select next_num from tb_no_generator where id='order' ", );
// 返回数据类似于 20180403001、20180403100
return date
+ (next_num.length() == 3 ? next_num : (next_num.length() == 2 ? ("0" + next_num) : ("00" + next_num)));
}
}
class DateUtils {
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();
private static final Object lockObj = new Object();
private static SimpleDateFormat getSdf(final String pattern) {
ThreadLocal<SimpleDateFormat> simpleDateFormat = (pattern);
// 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
if (simpleDateFormat == null) {
synchronized (lockObj) {
simpleDateFormat = (pattern);
if (simpleDateFormat == null) {
simpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
};
(pattern, simpleDateFormat);
}
}
}
return ();
}
public static String format(Date date, String pattern) {
return getSdf(pattern).format(date);
}
}
- 线程二
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class ByGenerator2 {
private CountDownLatch begin = new CountDownLatch(1);
private CountDownLatch end = new CountDownLatch(500);
private ExecutorService exec = Executors.newFixedThreadPool(500);
private ApplicationContext ctx;
private JdbcTemplate jt;
private PlatformTransactionManager transactionManager;
{
ctx = new ClassPathXmlApplicationContext("classpath:config/spring/**");
jt = (JdbcTemplate) ctx.getBean("jdbcTemplate");
transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager");
}
@Test
public void test() throws InterruptedException {
for (int i = 0; i < 500; i++) {
Runnable run = new Runnable() {
public void run() {
try {
begin.await();
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
jt.update("insert into tb_order (order_number,creator) values ( " + generate()
+ ", 'ByGenerator2' )");
transactionManager.commit(status);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
end.countDown();
}
}
};
exec.execute(run);
}
begin.countDown();
end.await();
exec.shutdown();
}
private String generate() {
String date = DateUtils.format(new Date(), "yyyyMMdd");
jt.execute("update tb_no_generator set next_num= next_num + 1 where id = 'order' ");
String next_num = jt.queryForObject("select next_num from tb_no_generator where id='order' ", String.class);
return date
+ (next_num.length() == 3 ? next_num : (next_num.length() == 2 ? ("0" + next_num) : ("00" + next_num)));
}
}
验证生成数据是否有重复:
SELECT * FROM tb_order a JOIN tb_order b ON a.order_number = b.order_number WHERE = 'ByGenerator' AND = 'ByGenerator' and !=
SELECT * FROM tb_order a JOIN tb_order b ON a.order_number = b.order_number WHERE = 'ByGenerator2' AND = 'ByGenerator2' and !=
SELECT * FROM tb_order a JOIN tb_order b ON a.order_number = b.order_number WHERE = 'ByGenerator' AND = 'ByGenerator2' and !=
SELECT * FROM tb_order a JOIN tb_order b ON a.order_number = b.order_number WHERE = 'ByGenerator2' AND = 'ByGenerator' and !=
注:本人对并发与分布式事务认知尚浅,本文如有问题,望您指教。