项目为单机项目 接口中是有重复验证的
- 当前功能的重复验证是在功能开头进行的
- 验证重复后,做了一大堆的其他工作在进行保存的
问题叙述
甲方在视图展示页面发现了不该有的重复。 我方检查接口和测试功能,并未复现问题。
问题产生原因
测试发现无问题之后,删除重复数据,不久后甲方有反应相同的问题。 这次阴差阳错的由于远程处理卡顿问题,连续点击测试N次功能点,错误数据由此产生
问题排除
已经确认为并发问题,但是具体造成原因还不明朗 最后通过反复测试,最终发现两个可疑点
- 功能中验证重复是在一开始进行的,途中做了很多很多无关的工作后,才进行了最终的save,可能期间过程有些延迟导致的
- 其中使用到了图片上传,但是不是前端调用的文件服务而是选择让后端处理
- 其中使用到了查询第三方远程库,且数据量优点大,同时
sql
已经无法在进行优化了
- 由于各种原因,对各系统的数据库为同一个且为单机数据库,可能存储的延迟造成的。
最后通过模拟线上数据库的环境进行测试,发现疑点二可以消除。
疑点一的环境比较复杂只能通过 睡眠等待 来模拟环境
问题确认跟处理办法
- 通过 睡眠等待 来模拟
数据重复验证
的系类操作,发现就是这里出的问题 - 最终处理
- 用最终的存储数据库直接兜底推荐,在不可重复字段上加入了唯一索引
- 其他处理方式参考
- 对于一些数据的强验证,一定注意数据录入和数据验证之间的延迟。
- 对唯一行的数据一定要进行数据库兜底
- 对增改的操作,中需要大量验证的时候,考虑下锁
synchronized
- 分布式下问题参考( 极客时间~每日一课
@RestController
@RequestMapping(“/front”)
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping(value=”/formBuySubmit”,method=RequestMethod.POST)
public ResultUtil
public interface BookService {
ResultUtil
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao;
/**
* 初步怀疑是判断重复之后接下来的其他操作导致了延迟的增加
* @param bean
* @return
*/
@SuppressWarnings("unlikely-arg-type")
@Override
public synchronized ResultUtil<BookBean> formBuy(BookBean bean) {
ResultUtil<BookBean> res = new ResultUtil<BookBean>();
// BookDao.sleep(1000); if(BookDao.verify(bean.getBookName())){ res.setResult(false); res.setResultInfo(“重复了”); // throw new RuntimeException(“存在重复”); }else { // 进行延时 - 模拟期间的 上传文件,查询第三方库等操作 BookDao.sleep(5000); // BookDao.sleep(1000); BookBean book = bookDao.save(bean); res.setResult(true); res.setResultInfo(“荐购成功,请等待管理员审核”); res.setResponseData(book); } return res; } }
@Component
@Slf4j
public class BookDao {
static List
public static Boolean verify(String name){
if(names.contains(name)){
log.info("name:"+name);
log.info("nameList"+ StringUtils.join(names.toArray(),","));
return true;
}
return false;
}
public static void sleep(long millis ){
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ResultUtil
private boolean result; //正确与否
private String resultInfo;//回调信息
private T responseData; //回调数据
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public T getResponseData() {
return responseData;
}
public void setResponseData(T responseData) {
this.responseData = responseData;
}
public String getResultInfo() {
return resultInfo;
}
public void setResultInfo(String resultInfo) {
this.resultInfo = resultInfo;
}
}
public class TimeFormat { /**
* yyyy-MM-dd HH:mm:ss
*/
public static final SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String currentTime(){
String str="";
Date date = new Date();
try {
str = format1.format(date);
} catch (Exception e) {
System.out.println("转换时间格式异常(yyyy-MM-dd):"+str);
str="";
e.printStackTrace();
}
return str;
}
}
@Data public class BookBean implements Serializable{ private static final long serialVersionUID = 1L; private String bookName; //名称 private String createTime; //创建时间
}
- 模拟测试并发
```http
###
POST http://localhost:8080/front/formBuySubmit
Content-Type: application/x-www-form-urlencoded
bookName=1231
###
POST http://localhost:8080/front/formBuySubmit
Content-Type: application/x-www-form-urlencoded
bookName=1231
小知识
- 测试并发的过程中发现
postMan
的压测不是并发执行,他是串行的气死个人