多服务保证订单号唯一
以生成订单号为例:多个用户下单时,如果我们只部署了一个服务,那么在订单生成的方法上使用 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 java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
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 ByGenerator {// 暂停 500 个线程private CountDownLatch begin = new CountDownLatch(1);// 启动 500 个线程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");}@Testpublic void test() throws InterruptedException {// 睡眠 2s,因为要启动两个单元测试,即每个单元测试都加载一次配置文件,相当于两个服务// 为了保证启动第二个单元测试时,减小手动启动的时间误差,因此睡眠 2sThread.sleep(2000L);for (int i = 0; i < 500; i++) {Runnable run = new Runnable() {public void run() {try {// 使创建好的线程全部等待begin.await();// 开启事务,如果没有开启事务,那么订单号会重复。在实际开发中,在 service 层加上 @Transactional 注解即可,这里因为是单元测试,因此我使用了编程式事务TransactionDefinition def = new DefaultTransactionDefinition();TransactionStatus status = transactionManager.getTransaction(def);jt.update("insert into tb_order (order_number,creator) values ( " + generate()+ ", 'ByGenerator')");// 提交事务transactionManager.commit(status);} catch (InterruptedException e) {e.printStackTrace();} finally {// 每创建一个线程,计数器 -1end.countDown();}}};exec.execute(run);}// 当执行完 for 循环时,释放所有线程begin.countDown();end.await();exec.shutdown();}private String generate() {// 获取今天的日期String date = DateUtils.format(new Date(), "yyyyMMdd");// 首先将单号生成器表 tb_no_generator 的计数器 +1jt.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);// 返回数据类似于 20180403001、20180403100return 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 = sdfMap.get(pattern);// 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdfif (simpleDateFormat == null) {synchronized (lockObj) {simpleDateFormat = sdfMap.get(pattern);if (simpleDateFormat == null) {simpleDateFormat = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat(pattern);}};sdfMap.put(pattern, simpleDateFormat);}}}return simpleDateFormat.get();}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");}@Testpublic 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 a.creator = 'ByGenerator' AND b.creator = 'ByGenerator' and a.id!=b.idSELECT * FROM tb_order a JOIN tb_order b ON a.order_number = b.order_number WHERE a.creator = 'ByGenerator2' AND b.creator = 'ByGenerator2' and a.id!=b.idSELECT * FROM tb_order a JOIN tb_order b ON a.order_number = b.order_number WHERE a.creator = 'ByGenerator' AND b.creator = 'ByGenerator2' and a.id!=b.idSELECT * FROM tb_order a JOIN tb_order b ON a.order_number = b.order_number WHERE a.creator = 'ByGenerator2' AND b.creator = 'ByGenerator' and a.id!=b.id
注:本人对并发与分布式事务认知尚浅,本文如有问题,望您指教。
多服务保证订单号唯一相关推荐
- PHP生成随机数;订单号唯一
//8-12位随机数 function makeRand($num=8){$strand = (double)microtime() * 1000000;if(strlen($strand)<$ ...
- 订单号唯一ID顺序生成(一个轻量的实现)
一个唯一的ID可以使用UUID,但不是顺序的. 一个自增的ID可以使用数据库序列.自增主键.雪花算法等等. 本文分享一个简单实用的一个ID生成代码,支持生成顺序自增且唯一的ID,一个工具类可以直接拷贝 ...
- Java怎么避免重复订单_javaEE高并发之如何产生唯一不重复订单号
javaEE高并发之如何产生唯一不重复订单号 1.方案一:使用进程ID,线程ID,IP,MAC地址和时间戳进行拼接产生订单号 (1)如果没有并发,订单号只在一个线程内产生,那么由于程序是顺序执行的,不 ...
- java唯一订单号_java web在高并发和分布式下实现订单号生成唯一的解决方案
方案一: 如果没有并发,订单号只在一个线程内产生,那么由于程序是顺序执行的,不同订单的生成时间戳正常不同,因此用时间戳+随机数(或自增数)就可以区分各个订单.如果存在并发,且订单号是由一个进程中的多个 ...
- 支付不重复订单号生成
最近的项目中支付的订单号是使用附加项目中的工具做的,是一种带数据库自增形式的订单号,开始测试的时候没有任何问题,但是再高并发的实际应用中还是有不小的问题,只怪自己考虑太少,必须在高并发模式下保证订单号 ...
- 线上订单号重复了?一招搞定它!
欢迎关注方志朋的博客,回复"666"获面试宝典 问题的背景 公司老的系统原先采用的时间戳生成订单号,导致了如下情形 打断一下:大家知道怎么查系统某项重复的数据吧 SELECT * ...
- 订单系统设计 —— 订单号设计
文章目录 一.订单号特性 二.业界方案 方案1:数据库自增ID(不推荐) 方案2:UUID(不推荐) 方案3:分布式唯一ID 三.因子分表法 3.1 方案设计 3.2 因子分表法 VS 分布式唯一ID ...
- 解析各大电子商务网站订单号的生成方式
http://blog.csdn.net/yahuvi/article/details/50818789 摘要:订单是整个电子商务的核心.整个电子商务的流程也是围绕订单的状态执行的.这篇博客主要向大家 ...
- 雪花算法(snowflake) :分布式环境,生成全局唯一的订单号 | CSDN 博文精选
戳蓝字"CSDN云计算"关注我们哦! 作者 | 琦彦 责编 | 阿秃 转自 | CSDN 博客 snowflake方案 snowflake是Twitter开源的分布式ID生成算 ...
最新文章
- 与大疆并称双雄、估值近百亿的极飞科技,现在是一家什么公司?
- Java——重载和重写
- 关于现代房地产很形象的一个比喻
- 计算机网络第七版 第四章课后答案
- live2d模型导入unity报错 live2dsdk与Cubism下载 live2dSDKforUnity使用手册
- 自动化 c语言学不会,为什么那么多人学不会PLC自动化编程?
- docker启动镜像命令
- 【开发日常】什么是标准?什么是协议?标准和协议之间是什么关系?
- PMOS类型 LDO基本原理
- 本世纪最好的NSA!
- 使用python进行基音周期的计算
- npm下载swiper包报错
- RobotFramework学习笔记二:遇到Frame框架
- TensorFlow实时任意风格迁移,送女朋友的创意礼物有了
- 综合隧道广播通信与调度系统应用于南昌市青山湖、滕王阁、洛阳路隧道
- ssl证书到期时间查询方法
- 线性表的顺序表示与实现2
- 假如互联网公司做铁道部12306订票网站
- 论文阅读:Interactive Multiobjective Optimisation: Preference Changes and Algorithm Responsiveness
- Linux 创建桌面图标