以生成订单号为例:多个用户下单时,如果我们只部署了一个服务,那么在订单生成的方法上使用 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

注:本人对并发与分布式事务认知尚浅,本文如有问题,望您指教。

多服务保证订单号唯一相关推荐

  1. PHP生成随机数;订单号唯一

    //8-12位随机数 function makeRand($num=8){$strand = (double)microtime() * 1000000;if(strlen($strand)<$ ...

  2. 订单号唯一ID顺序生成(一个轻量的实现)

    一个唯一的ID可以使用UUID,但不是顺序的. 一个自增的ID可以使用数据库序列.自增主键.雪花算法等等. 本文分享一个简单实用的一个ID生成代码,支持生成顺序自增且唯一的ID,一个工具类可以直接拷贝 ...

  3. Java怎么避免重复订单_javaEE高并发之如何产生唯一不重复订单号

    javaEE高并发之如何产生唯一不重复订单号 1.方案一:使用进程ID,线程ID,IP,MAC地址和时间戳进行拼接产生订单号 (1)如果没有并发,订单号只在一个线程内产生,那么由于程序是顺序执行的,不 ...

  4. java唯一订单号_java web在高并发和分布式下实现订单号生成唯一的解决方案

    方案一: 如果没有并发,订单号只在一个线程内产生,那么由于程序是顺序执行的,不同订单的生成时间戳正常不同,因此用时间戳+随机数(或自增数)就可以区分各个订单.如果存在并发,且订单号是由一个进程中的多个 ...

  5. 支付不重复订单号生成

    最近的项目中支付的订单号是使用附加项目中的工具做的,是一种带数据库自增形式的订单号,开始测试的时候没有任何问题,但是再高并发的实际应用中还是有不小的问题,只怪自己考虑太少,必须在高并发模式下保证订单号 ...

  6. 线上订单号重复了?一招搞定它!

    欢迎关注方志朋的博客,回复"666"获面试宝典 问题的背景 公司老的系统原先采用的时间戳生成订单号,导致了如下情形 打断一下:大家知道怎么查系统某项重复的数据吧 SELECT * ...

  7. 订单系统设计 —— 订单号设计

    文章目录 一.订单号特性 二.业界方案 方案1:数据库自增ID(不推荐) 方案2:UUID(不推荐) 方案3:分布式唯一ID 三.因子分表法 3.1 方案设计 3.2 因子分表法 VS 分布式唯一ID ...

  8. 解析各大电子商务网站订单号的生成方式

    http://blog.csdn.net/yahuvi/article/details/50818789 摘要:订单是整个电子商务的核心.整个电子商务的流程也是围绕订单的状态执行的.这篇博客主要向大家 ...

  9. 雪花算法(snowflake) :分布式环境,生成全局唯一的订单号 | CSDN 博文精选

    戳蓝字"CSDN云计算"关注我们哦! 作者 |  琦彦  责编 | 阿秃 转自 | CSDN 博客 snowflake方案 snowflake是Twitter开源的分布式ID生成算 ...

最新文章

  1. 与大疆并称双雄、估值近百亿的极飞科技,现在是一家什么公司?
  2. Java——重载和重写
  3. 关于现代房地产很形象的一个比喻
  4. 计算机网络第七版 第四章课后答案
  5. live2d模型导入unity报错 live2dsdk与Cubism下载 live2dSDKforUnity使用手册
  6. 自动化 c语言学不会,为什么那么多人学不会PLC自动化编程?
  7. docker启动镜像命令
  8. 【开发日常】什么是标准?什么是协议?标准和协议之间是什么关系?
  9. PMOS类型 LDO基本原理
  10. 本世纪最好的NSA!
  11. 使用python进行基音周期的计算
  12. npm下载swiper包报错
  13. RobotFramework学习笔记二:遇到Frame框架
  14. TensorFlow实时任意风格迁移,送女朋友的创意礼物有了
  15. 综合隧道广播通信与调度系统应用于南昌市青山湖、滕王阁、洛阳路隧道
  16. ssl证书到期时间查询方法
  17. 线性表的顺序表示与实现2
  18. 假如互联网公司做铁道部12306订票网站
  19. 论文阅读:Interactive Multiobjective Optimisation: Preference Changes and Algorithm Responsiveness
  20. Linux 创建桌面图标

热门文章

  1. c语言中如何防止数组下标越界
  2. 真彩色转256色算法
  3. 自然数e的存在性证明.
  4. 深度学习:AlexNet实现服装分类(Pytorch)
  5. Yii2常用数据库操作整理
  6. 信息化加速发展背景下全球化的新特征
  7. android 桌面悬浮窗 录屏时间控制显示效果
  8. 通过nmap的nse脚本(dns-brute.nse)扫描和收集子域名
  9. 你不知道的 facebook 盈利模式
  10. Mac 模拟登陆12306