1、  我们需要接收一个外部的订单,而这个订单号是不允许重复的

2、  数据库对外部订单号没有做唯一性约束

3、  外部经常插入相同的订单,对于已经存在的订单则拒绝处理

对于这个需求,很简单我们会用下面的代码进行处理(思路:先查找数据库,如果数据库存在则直接退出,否则插入)

package com.yhj.test;

import com.yhj.dao.OrderDao;

import com.yhj.pojo.Order;

/**

@Description:并发测试用例

@Author YHJ  create at 2011-7-7 上午08:41:44

@FileName com.yhj.test.TestCase.java

*/

public class TestCase {

/**

* data access object class for deal order

*/

private OrderDao orderDao;

/**

@Description:插入测试

@param object 要插入的object实例

@author YHJ create at 2011-7-7 上午08:43:15

@throws Exception

*/

public void doTestForInsert(Order order) throws Exception {

Order orderInDB = orderDao.findByName(order.getOrderNo());

if(null != orderInDB)

throw new Exception("the order has been exist!");

orderDao.save(order);

}

}

对于这种情况,好像如果不用数据库做唯一性约束又不借助外部其他的一些工具,是没有办法实现的。那怎么做呢?

引入缓存,我们看下面的代码

package com.yhj.test;

import com.yhj.dao.OrderDao;

import com.yhj.pojo.Order;

import com.yhj.util.MemcacheUtil;

import com.yhj.util.MemcacheUtil.UNIT;

/**

@Description:并发测试用例

@Author YHJ  create at 2011-7-7 上午08:41:44

@FileName com.yhj.test.TestCase.java

*/

public class TestCase {

/**

* data access object class for deal order

*/

private OrderDao orderDao;

/**

@Description:插入测试

@param object 要插入的object实例

@author YHJ create at 2011-7-7 上午08:43:15

@throws Exception

*/

public void doTestForInsert(Order order){

String key=null;

try{

Order orderInDB = orderDao.findByName(order.getOrderNo());

//查DB,如果数据库已经有则抛出异常

if(null != orderInDB)

throw new Exception("the order has been exist!");

key=order.getOrderNo();

//插缓存,原子性操作,插入失败 表明已经存在

if(!MemcacheUtil.add(key, order, MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))

throw new Exception("the order has been exist!");

//插DB

orderDao.save(order);

}catch (Exception e) {

e.printStackTrace();

}finally{

MemcacheUtil.del(key);

}

}

}

1、  查找数据库,如果数据库已经存在则抛出异常

2、  插入缓存,如果插入失败则表明缓存中已经存在,抛出异常

3、  如果上述2步都没有抛出异常,则执行插入数据库的操作

4、  删除缓存

机器异常情况下,不能执行finally语句,但是放在memcache中的数据会在1分钟后超时。

貌似没有问题。使用LodeRunner测试100个并发的操作,发现仍然有重复的订单插入,这个是为什么呢?我们再来看这段代码!

public void doTestForInsert(Order order){

String key=null;

try{

Order orderInDB = orderDao.findByName(order.getOrderNo());

//查DB,如果数据库已经有则抛出异常

if(null != orderInDB)

throw new Exception("the order has been exist!");

key=order.getOrderNo();

//插缓存,原子性操作,插入失败 表明已经存在

if(!MemcacheUtil.add(key, order, MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))

throw new Exception("the order has been exist!");

//插DB

orderDao.save(order);

}catch (Exception e) {

e.printStackTrace();

}finally{

MemcacheUtil.del(key);

}

}

时刻1:

时刻

线程2到达,查数据库发现没有

时刻

线程2开始写缓存

时刻

线程2写缓存失败,抛出异常,执行finally

时刻

线程2执行finally,删除缓存,开始构建返回结果

时刻

线程2成功返回

时刻

因此上述代码仍然有插入多条重复记录的可能,我们在并发20的测试中发现成功插入了5笔订单,其中4笔是不应该插入的!

那我们应该怎么解决呢?其实只要解决一个问题,只有插入DB时候的异常是可以删除的,其他地方不应该删除,那能不能将代码改成下面的呢?

public void doTestForInsert(Order order){

String key=null;

try{

Order orderInDB = orderDao.findByName(order.getOrderNo());

//查DB,如果数据库已经有则抛出异常

if(null != orderInDB)

throw new Exception("the order has been exist!");

key=order.getOrderNo();

//插缓存,原子性操作,插入失败 表明已经存在

if(!MemcacheUtil.add(key, order, MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))

throw new Exception("the order has been exist!");

//插DB

orderDao.save(order);

MemcacheUtil.del(key);

}catch (Exception e) {

e.printStackTrace();

}//finally{

//         MemcacheUtil.del(key);

//     }

}

这样显然不行,为什么呢?

这样是保证了只有插入DB成功了才会删除缓存,但是当插入DB的时候发生了一个异常,删除缓存就不会再执行,虽然我们有一分钟超时,但意味着我们一分钟内该笔订单是不能再被处理的,而实际上这边订单并没有处理成功,所以这样是不满足需求的!

继续改进

代码如下:加一个标志位

public void doTestForInsert(Order order){

String key=null;

boolean needDel=false;

try{

Order orderInDB = orderDao.findByName(order.getOrderNo());

//查DB,如果数据库已经有则抛出异常

if(null != orderInDB)

throw new Exception("the order has been exist!");

key=order.getOrderNo();

//插缓存,原子性操作,插入失败 表明已经存在

if(!MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))

throw new Exception("the order has been exist!");

needDel=true;

//插DB

orderDao.save(order);

}catch (Exception e) {

e.printStackTrace();

}finally{

if(needDel)

}

}

这样是不是完美解决了呢?

在其他异常执行的时候是不会删除缓存的,我们套在之前的代码上,线程2判断缓存中存在抛出异常执行finally的时候是不会删除缓存的,因此线程3没有机会执行写缓存的操作,从而保证了线程1是唯一能够插入DB的。

还有没有其他漏洞呢?期待大家发现……

如何处理高并发情况下的DB插入的更多相关文章

  1. 关于WCF服务在高并发情况下报目标积极拒绝的异常处理

    最近弄了个wcf的监控服务,偶尔监控到目标服务会报一个目标积极拒绝的错误.一开始以为服务停止了,上服务器检查目标服务好好的活着.于是开始查原因. 一般来说目标积极拒绝(TCP 10061)的异常主要是 ...

  2. WCF服务在高并发情况下报目标积极拒绝的异常处理 z

    http://www.cnblogs.com/kklldog/p/5037006.html wcf的监控服务,偶尔监控到目标服务会报一个目标积极拒绝的错误.一开始以为服务停止了,上服务器检查目标服务好 ...

  3. Jackson高并发情况下,产生阻塞

    情况:在高并发情况下,查看线程栈信息,有大量的线程BLOCKED. 从线程栈得知,线程栈中出现了阻塞,锁在了com.fasterxml.jackson.databind.ser.SerializerC ...

  4. Linux的虚拟内存管理-如何分配和释放内存,以提高服务器在高并发情况下的性能,从而降低了系统的负载

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  5. 高并发情况下分布式全局ID

    1.高并发情况下,生成分布式全局id策略2.利用全球唯一UUID生成订单号优缺点3.基于数据库自增或者序列生成订单号4.数据库集群如何考虑数据库自增唯一性5.基于Redis生成生成全局id策略6.Tw ...

  6. c# redis 利用锁(StackExchange.Redis LockTake)来保证数据在高并发情况下的正确性

    之前有写过一篇介绍c#操作redis的文章 http://www.cnblogs.com/axel10/p/8459434.html ,这篇文章中的案例使用了StringIncrement来实现了高并 ...

  7. 小D课堂 - 新版本微服务springcloud+Docker教程_6-05 高级篇幅之高并发情况下

    笔记 5.高级篇幅之高并发情况下接口限流特技         简介:谷歌guava框架介绍,网关限流使用 1.nginx层限流 2.网关层限流 开始 mysql最大的连接数就是3千多.如果想把应用搞好 ...

  8. Java高并发情况下的锁机制优化

    本文主要讲并行优化的几种方式, 其结构如下: 锁优化 减少锁的持有时间 例如避免给整个方法加锁 1 public synchronized void syncMethod(){ 2 othercode ...

  9. Mysql高并发情况下的解决方案(转)

    查询了下Mysql 关于高并发的处理的资料,在这记录一下. 高并发大多的瓶颈在后台数据逻辑处理,在存储,mysql的正常的优化方案如下: 1.代码中sql语句优化 2.数据库字段优化,索引优化 3.加 ...

随机推荐

  1. mariadb(mysql)从库relaylog损坏无法同步的处理方法

    故障说明 晚上备用服务器自动重启,收到报警,备用服务器上的mariadb从库无法去同步主库.启动mariadb后,报如下错误(重点看红色字体)  mariadb_1 | -- :: [Note] Pl ...

  2. win7 iis7 asp.net 编译器错误消息: CS0016:

    编译器错误消息: CS0016: 未能写入输出文件“c:/Windows/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files/root ...

  3. [转]linux文件批量转码

    linux系统里提供的文件转化编码的命令iconv,例如: iconv -t utf- -f gb2312 -c test.xml > text_UTF8.xml -f  源编码-t  目标编码 ...

  4. HDU 2824 The Euler function --------欧拉模板

    The Euler function Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Other ...

  5. properties配置文件编码问题

    properties配置文件编码问题 person.last-name=哈哈 person.age=18 person.bitrh=2019/01/12 person.boss=false perso ...

  6. Java数据结构基础知识你知道多少?

    http://www.cnblogs.com/qlky/p/7359462.html http://www.importnew.com/28263.html HashMap( java 7) 构造函数 ...

  7. Android开发之旅2:HelloWorld项目的目录结构

    引言 前面Android开发之旅:环境搭建及HelloWorld,我们介绍了如何搭建Android开发环境及简单地建立一个HelloWorld项目,本篇将通过HelloWorld项目来介绍Androi ...

  8. .NET Core 微服务架构-Docker部署

    本文主要介绍通过Docker来部署通过.NET Core开发的微服务架构,部署的微服务主要包括统一网关(使用Ocelot开发).统一认证(IdentityServer4).应用服务(ASP.NET C ...

  9. 区别String、StringBuilder、Stringbuffer的总结

    1.三者在执行速度上: StringBuilder > StringBuffer > String 2. String:不可变长字符串 StringBuilder : 为可变长字符串 St ...

  10. mac上显示.开头的文件

    第一种方法是在finder中按下command+shift+.键. 第二种方法是在命令行输入如下命令 defaults write com.apple.Finder AppleShowAllFiles ...