在最近的性能测试中,某一个查询接口指标不通过,开发做了N次优化,最终的优化方案如下:异步查询然后转同步,再加上redis缓存。此为背景。

在测试过程中发现一个BUG:同样的请求在第一次查询结果是OK的,但是第二次查询(理论上讲得到的缓存数据)缺失了某些字段。

后端服务的测试代码如下,代码内容作了简化,留下了关键的部分,doSomething(dataMap);为简化方法,其中teacherPadAsyncService.doExcuteLikeSateAsync()teacherPadAsyncService.doExcuteAccuracyAsync()teacherPadAsyncService.doExcuteTeacherTagAsync这三个是异步方法:

 @Override
public void doExecute(Map<String, Object> dataMap) {
String cache = defaultRedisUtil.getString(RedisKeyConfig.COURSE_PKG_DETAIL_KEY + id);
if (StringUtils.isNotBlank(cache)) {
dataMap = JSON.parseObject(cache, Map.class);
return;
}
doSomething(dataMap);
CountDownLatch countDownLatch = new CountDownLatch(3);
String traceKey = TraceKeyHolder.getTraceKey();
teacherPadAsyncService.doExcuteLikeSateAsync(dataMap, coursePackage.getId(),ResourceTypeEnum.COURSE_PACKAGE.value, currentUser.getSystemId(), countDownLatch, traceKey);
teacherPadAsyncService.doExcuteAccuracyAsync(dataMap, coursePackage.getId(), countDownLatch, traceKey);
teacherPadAsyncService.doExcuteTeacherTagAsync(dataMap, coursePackage, countDownLatch, traceKey);
doSomething(dataMap);
defaultRedisUtil.setString(RedisKeyConfig.COURSE_PKG_DETAIL_KEY + id, JSON.toJSONString(dataMap), RedisKeyConfig.COURSE_PKG_DETAIL_EXPIRE_TIME);
try {
countDownLatch.await();
} catch (InterruptedException e) {
logger.error("异步处理线程异常", e);
}
}

teacherPadAsyncService.doExcuteLikeSateAsync()这个方法是异步查询点赞状态,会在dataMap里面添加一个字段state,但是在第二次请求的时候有可能发现这个字段缺失,这只是其中一个BUG。原因在于往redis里面放置信息的时机不对,大概是由于写代码太着急,正确的做法应该是在异步转同步以后再去操作redis。下面是改之后的代码:

 @Override
public void doExecute(Map<String, Object> dataMap) {
String cache = defaultRedisUtil.getString(RedisKeyConfig.COURSE_PKG_DETAIL_KEY + id);
if (StringUtils.isNotBlank(cache)) {
dataMap = JSON.parseObject(cache, Map.class);
return;
}
doSomething(dataMap);
CountDownLatch countDownLatch = new CountDownLatch(3);
String traceKey = TraceKeyHolder.getTraceKey();
teacherPadAsyncService.doExcuteLikeSateAsync(dataMap, coursePackage.getId(),ResourceTypeEnum.COURSE_PACKAGE.value, currentUser.getSystemId(), countDownLatch, traceKey);
teacherPadAsyncService.doExcuteAccuracyAsync(dataMap, coursePackage.getId(), countDownLatch, traceKey);
teacherPadAsyncService.doExcuteTeacherTagAsync(dataMap, coursePackage, countDownLatch, traceKey);
doSomething(dataMap);
try {
countDownLatch.await();
defaultRedisUtil.setString(RedisKeyConfig.COURSE_PKG_DETAIL_KEY + id, JSON.toJSONString(dataMap), RedisKeyConfig.COURSE_PKG_DETAIL_EXPIRE_TIME);
} catch (InterruptedException e) {
logger.error("异步处理线程异常", e);
}
}

BUG的原因也比较简单,由于第一次查询的时候redis里面内容时空的,所以走了数据库查询,查询到结果后,放到redis里面,但是在存redis时候,异步的查询任务并没有完成,导致第一次请求得多的响应是对的,但是redis里面存放的却是错误的。在缓存有效期内,查询的结果都将是错误的。

当然这个实现方法的BUG不止这一个,这里不列举了,有机会再分享。


  • 郑重声明:文章首发于公众号“FunTester”,禁止第三方(腾讯云除外)转载、发表。

技术类文章精选

非技术文章精选

异步查询转同步加redis业务实现的BUG分享的更多相关文章

  1. java 异步查询转同步多种实现方式:循环等待,CountDownLatch,Spring EventListener,超时处理和空循环性能优化

    异步转同步 业务需求 有些接口查询反馈结果是异步返回的,无法立刻获取查询结果. 正常处理逻辑 触发异步操作,然后传递一个唯一标识. 等到异步结果返回,根据传入的唯一标识,匹配此次结果. 如何转换为同步 ...

  2. javascript 同步加载与异步加载

    HTML 4.01 的script属性 charset: 可选.指定src引入代码的字符集,大多数浏览器忽略该值. defer: boolean, 可选.延迟脚本执行,相当于将script标签放入页面 ...

  3. Javascript 文件的同步加载与异步加载

    HTML 4.01 的script属性 charset: 可选.指定src引入代码的字符集,大多数浏览器忽略该值.defer: boolean, 可选.延迟脚本执行,相当于将script标签放入页面b ...

  4. js 异步加载和同步加载

    异步加载 异步加载也叫非阻塞模式加载,浏览器在下载js的同时,同时还会执行后续的页面处理.在script标签内,用js创建一个script元素并插入到document中,这种就是异步加载js文件了: ...

  5. 关于requireJS的同步加载和异步加载

    这篇随笔主要记录require('name')和require(['name1','name2'])在同步和异步加载使用的区别 1.require('name')同步加载模块的形式 define(fu ...

  6. AJAX中的同步加载与异步加载

    AJAX是四个单词的简写,其中Asynchronous即异步的意思,异步的链接可以同时发起多个,并且不会阻止JS代码执行.与之对应的概念是同步,同步的链接在同一时刻只会有一个,并且会阻止后续JS代码的 ...

  7. 【UE4 C++ 基础知识】<11>资源的同步加载与异步加载

    同步加载 同步加载会造成进程阻塞. FObjectFinder / FClassFinder 在构造函数加载 ConstructorHelpers::FObjectFinder Constructor ...

  8. Redis和MySQL数据同步及Redis使用场景

    1.同步MySQL数据到Redis (1) 在redis数据库设置缓存时间,当该条数据缓存时间过期之后自动释放,去数据库进行重新查询,但这样的话,我们放在缓存中的数据对数据的一致性要求不是很高才能放入 ...

  9. Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历

    Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历 https://www.cnblogs.com/shuxiaolong/p/DotNet_Task_BUG.html 异步Task简单 ...

随机推荐

  1. Python的优缺点、以及解释器种类

    优点 Python起始定位“优雅”.“明确”.“简洁”,工具型语言,上手快,实用性强. 开发效率高,支持库强大,很多功能都有与之对应的最优模块支持. 高级语言,编程时无需考虑内存等底层具体实现. 可移 ...

  2. 2019浙江ACM省赛——部分题目

    有一些题目过了我还没有重新写,先放一些我重新写好了的吧 签到题拿到了信心吧,9分钟写完两题,我们贼开心,我大哥说签到题有什么好开心的,如果不是我有一些地方卡了下,可能还是更快吧,还有就是测试案例多试了 ...

  3. dotnet 使用 MessagePack 序列化对象

    和很多序列化库一样,可以通过 MessagePack 序列化和反序列化,和 json 相比这个库提供了二进制的序列化,序列化之后的内容长度比 json 小很多 这个库能序列的内容不多,大多数时候建议使 ...

  4. maxmind geoip2使用笔记

    客户需求如下,nginx的访问日志中ip,匹配出对应的国家,省份和城市,然后给我了一个maxmind的连接参考. 查找资料,有做成hive udf的使用方式, 我们项目中一直使用 waterdrop ...

  5. linux包之dracut

    这是一个工具类,不是一个后台服务类 centos7.2-minimal就下面三个包 [root@1st-kvm ~]# rpm -qa|grep dracutdracut-config-rescue- ...

  6. docker 命令汇总1

    命令汇总 docker history fa5fa5为镜像id或者镜像名 docker export 30b >h.tar30b为容器id或者容器名# docker export angry_b ...

  7. JNDI数据源的使用

    有时候我们数据库的连接会使用jndi的方式 try { InitialContext ic = new InitialContext(); dataSource = (DataSource) ic.l ...

  8. java中使用javaMail工具类发送邮件

    1.引入依赖 <!--javaMail--> <dependency> <groupId>javax.mail</groupId> <artifa ...

  9. 动态规划之用最少的字符操作将字符串A转换为字符串B

    1.试用动态规划算法实现下列问题:设A和B是两个字符串.我们要用最少的字符操作,将字符串A转换为字符串B,这里所说的字符操作包括: (1)删除一个字符. (2)插入一个字符. (3)将一个字符改为另一 ...

  10. 1030 完美数列 (25 分)C、Java、python

    题目描述 给定一个正整数数列,和正整数p,设这个数列中的最大值是M,最小值是m,如果M <= m * p,则称这个数列是完美数列. 现在给定参数p和一些正整数,请你从中选择尽可能多的数构成一个完 ...