当前位置: 首页 > news >正文

后端两个接口需分开写,前端需不串行并同时刷新调用但数据不同步NOTE

前言:

首先简述一下业务与问题

getUserTotalScoreList 接口是获取当前考试考生的总得分值列表信息(读数据)

getItemTotalScore 接口是自动计算当前考试考生的对应类型题目的总分值(写数据)

目前前端是需要同时刷新页面并同时调用以上两个接口,由于第一个接口是读数据,第二个接口是写数据,导致同步刷新获取的时候,数据不一致,getItemTotalScore 计算完成后进行返回考生 score 18,之后从 getUserTotalScoreList  进行获取时还是老数据 score 15,需要再次调用这个接口才能获取最新的 score 18

由上可知遇到了典型的
数据不一致问题,前提前端那边不进行串行调用接口的情况下

以下我是使用的
分布式锁 + Redis状态标记 进行处理的

这里是 Redisson 分布式锁工具类 以及 定义了一个 Redis 常量接口

/**** 分布式锁工具类 **/
public class RedissonUtil {// 计算得分方法正在进行public static final String RUNNING = "0";// 计算得分方法已经完成public static final String FINISHED = "1";/*** 获取锁*/static public RLock getLock(String key, RedissonClient redissonClient) {return redissonClient.getLock("exam:score:rwlock:" + key);}
}/**** Redis 常量接口 **/
public interface RedisConstants {/*** 判断计算得分是否已经完成*/String SCORE_TASK_FINISHED_KEY = "score_task_finished:";
}

这里是 getItemTotalScore 方法(写操作)

首先是使用 Redisson 中的分布式锁方法进行加锁,若加锁失败,意味着当前 (examId,uerId) 正在被其他线程处理或处于竞争状态,这时进行相关处理

若加锁成功,首先先将将当前考试考生之前已经完成计算的旧数据状态给 delete 删除(注意,状态删除操作不要写在读方法中),然后基于 examId + uerIdkey 来进行 redis 的状态标记处理(注意这里的 key 必须保证唯一,避免全局大锁),向其他人告知这个考生的成绩正在计算中(Running)

在计算考生成绩结束后,再修改当前 key 的状态为结束,告知其他人“我”已经完成这位考生的分数计算(Finished)

最后释放锁

@Resource
private RedissonClient redissonClient;@Resource
private RedisTemplate<String, Object> redisTemplate;public UserAnswerChooseAndEssayDTO getItemTotalScore(Integer examId, Integer userId) {
// 【解决前端同时刷新调用接口的并发问题】
// 1.获取 redisson 锁
RLock lock = RedissonUtil.getLock(examId + ":" + userId, redissonClient);
try {// 1.1 尝试加锁,等待最多15秒,持有锁最多30秒boolean isLocked = lock.tryLock(15, 30, TimeUnit.SECONDS);if (!isLocked) {String status = (String) redisTemplate.opsForValue().get(RedisConstants.SCORE_TASK_FINISHED_KEY + examId + ":" + userId);if (RUNNING.equals(status)) {throw new MyException(HttpStatus.SERVICE_UNAVAILABLE.value(), "成绩正在计算中,请稍后重试!");} else {throw new MyException(HttpStatus.REQUEST_TIMEOUT.value(), "成绩计算被阻塞,请重试!");}}// 1.2 先将当前考试考生之前已经完成计算的旧数据状态给删除redisTemplate.delete(RedisConstants.SCORE_TASK_FINISHED_KEY + examId  + ":" + userId);// 1.3 进行标记,表示写操作正在运行中redisTemplate.opsForValue().set(RedisConstants.SCORE_TASK_FINISHED_KEY + examId + ":" + userId, RUNNING);///////////// 这是计算过程代码.......// 10.1 进行标记,表示写操作已经运行完成redisTemplate.opsForValue().set(RedisConstants.SCORE_TASK_FINISHED_KEY + examId + ":" + userId, FINISHED);return resultDTO;
} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "计算过程被中断!");
} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}
}}

这里是 getUserTotalScoreList 方法(读操作)

由于需要避免数据不同步问题,读操作肯定需要比写操作要“慢”,才能保证数据的实时/最终一致性

首先,我这里是设置最大循环重试三次(根据自己项目的响应情况来)

如果从 Redis 状态标记中获取当前 examId + userId key 状态为 Running 的话,则表示还在计算中,这时需要进行等待(我这里是设置等待时间为 700 毫秒)

若在循环重试的过程中,发现其状态为 Finished 了,则表示当前考试考生的得分计算完成了,跳出

接着,再次进行获取 Redis 状态标记,查看是否还在计算中,进行相关处理

最后,这里的话我是直接进行返回查询了,如果有其他的情况或更严谨的流程,可以在相应的模块进行加固代码逻辑

@Resource
private RedisTemplate<String, Object> redisTemplate;// 重试的次数
public static final int MAX_RETRY_TIMES = 3;
// 每次等待的时间(毫秒)
public static final long WAIT_MILLIS = 700;public List<UserExamCustom> getUserTotalScoreList(Integer examId, Integer userId) {
// 【解决前端同时刷新调用接口的并发问题】
// 1.首先进行循环检查,若得分计算已完成则直接返回
for (int i = 0; i < MAX_RETRY_TIMES; i++) {String status = redisTemplate.opsForValue().get(RedisConstants.SCORE_TASK_FINISHED_KEY + examId + ":" + userId);if (status == null || Objects.equals(status, RUNNING)) {try {Thread.sleep(WAIT_MILLIS);} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new MyException(HttpStatus.SERVICE_UNAVAILABLE.value(), "成绩查询被中断,请稍后重试!");}} else if (Objects.equals(status, FINISHED)) {break;}
}// 2.循环完毕,若还未完成,则进行提示,其他情况直接返回
if (Objects.equals(redisTemplate.opsForValue().get(RedisConstants.SCORE_TASK_FINISHED_KEY + examId + ":" + userId), RUNNING)) {throw new MyException(HttpStatus.BAD_REQUEST.value(), "成绩正在计算中,请稍后刷新页面!");
} else {return userExamMapper.selectByExamId(examId);
}}

http://www.dtcms.com/a/414675.html

相关文章:

  • 华北建设集团有限公司oa网站上海旅游网站建设情况
  • Appinventor笔记4-数字块与文本块
  • 龙溪营销型网站制作上海官网
  • 支付方式大升级!AI代理将进入购物环节
  • 前端实验(序)——前端开发基础
  • reset saved-configuration 概念及题目
  • 口腔病变识别分割数据集labelme格式1317张1类别
  • 做网站的qq兼职网易企业邮箱登录登录入口
  • 【开题答辩全过程】以 springboot毕业设计管理系统为例,包含答辩的问题和答案
  • 越南网站建设青岛做网络直播的网站
  • 【愚公系列】《人工智能70年》045-生成式Al的辉煌与难题(ChatGPT一鸣惊人)
  • 学习嵌入式的第四十二天——ARM——UART
  • MCP协议深度解析(理论篇):AI工具生态的统一语言
  • 沧州网站建设哪家专业微娱网络小程序代理
  • git-filter-repo - 强大的Git历史重写工具
  • 阿里云wordpress在哪里设置密码网站建设相关优化
  • 常州专业网站建设公司咨询做家具有那个网站好
  • Vim复制粘贴剪切命令详解
  • STM32H743-ARM例程8-EXTI外部中断
  • ARM(IMX6ULL)——通信(UART)
  • 网站 开发逻辑开发app开发公司
  • Kong Gateway 实操实例:代理上游服务并配置限流插件
  • 陕西西安网站设计公司重庆住房建设工程信息网官网
  • 【鸿蒙心迹】 我和新人的鸿蒙应用上架之路
  • 鸿蒙NEXT开发浅进阶到精通14:鸿蒙开发项目中遇到的需求问题及解决笔记05
  • 做网站申请多少类商标天津优化代理
  • 学前端视频课程笔记
  • 有关网站开发的创意工厂外包小件加工
  • Metal - 8.深入剖析纹理贴图
  • 品牌网站建设 十蝌蚪小提交图片的网站要怎么做