以生成订单号为例:多个用户下单时,如果我们只部署了一个服务,那么在订单生成的方法上使用 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. CS294-112 深度强化学习 秋季学期(伯克利)NO.3 Reinforcement learning introduction
  2. Linux内核中的GPIO系统之(3):pin controller driver代码分析
  3. Android中应用百度地图API开发地图APP实例-显示百度地图
  4. 生产计划到底该怎样改进?这6点帮你节省至少60%的计划时间
  5. NOIP模拟测试18「引子·可爱宝贝精灵·相互再归的鹅妈妈」
  6. 使用消息中间件时,如何保证消息不丢失且仅仅被消费一次
  7. Vivado定制DDR3 IP核注意事项
  8. Redis的三个框架:Jedis,Redisson,Lettuce
  9. 【引用】使用CommonDialog的ShowSave后如何判断是保存还是取消?
  10. 关于非堆内存申请的性能测试
  11. 用python写的翻译器
  12. jsp中java代码的输出,Java控制台输入,输出!-JSP教程,Java技巧及代码
  13. 音乐制作宿主软件-Cubase Elements 11.0.20 MacOS
  14. java还原三阶魔方_魔方小站四阶魔方教程2 一看就懂的魔方教程(魔方玩法视频教程+还原公式一步一步图解+3D动画)...
  15. Linux文件内容查阅 - cat, tac, nl, more, less, head, tail, od
  16. 微信支付分700分,有什么好处?
  17. SQLAlchemy中的Django风格的数据库路由器
  18. 你不得不会的EXCEL选择性粘贴
  19. 补充函数编程,程序功能要求:有一个已知数组d,程序调用函数out输出数组 d的所有元素;调用函数plus对数组d中所有大于0的元素分别加上30;调用函数 cpy将d数组所有元素
  20. Android-适配各国语言、屏幕尺寸、系统版本及常见适配方法总结

热门文章

  1. 只让输入数字的输入框
  2. Shiro 与 Spring框架整合基本配制说明
  3. C++常见内存错误及解决方案
  4. Auto.js学习笔记4—按键模拟
  5. (一) 模式识别入门
  6. ChatGPT:智能问答,文案撰写神器
  7. 小程序抓包反编译测试从0到1
  8. arguments的解释
  9. Uva 10107 - What is the Median?
  10. 威纶通触摸屏验证标签不存在于设备问题