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

网站流程表哪个网站可以做片头

网站流程表,哪个网站可以做片头,什么网站免费购物商城,小网站推广学习记录-使用Redis合并写请求来优化性能 1.业务背景 学习进度的统计功能:为了更精确的记录用户上一次播放的进度,采用的方案是:前端每隔15秒就发起一次请求,将播放记录写入数据库。但问题是,提交播放记录的业务太复杂了&#x…

学习记录-使用Redis合并写请求来优化性能

1.业务背景

学习进度的统计功能:为了更精确的记录用户上一次播放的进度,采用的方案是:前端每隔15秒就发起一次请求,将播放记录写入数据库。但问题是,提交播放记录的业务太复杂了,其中涉及到大量的数据库操作

在这里插入图片描述

2.解决方案思路

如图:
在这里插入图片描述

由于数据都缓存到Redis了,积累一些数据后再批量写入数据库,这样数据库的写频率、写次数都大大减少,对数据库压力小了非常多!

优点:

  • 写缓存速度快,响应时间大大减少
  • 降低数据库的写频率和写次数,大大减轻数据库压力

缺点:

  • 实现相对复杂
  • 依赖Redis可靠性
  • 不支持事务和复杂业务

场景:

  • 写频率较高、写业务相对简单的场景

3.持久化思路

对于合并写请求方案,一定有一个步骤就是持久化缓存数据到数据库。一般采用的是定时任务持久化:

但是定时任务的持久化方式在播放进度记录业务中存在一些问题,主要就是时效性问题。

  • 假如定时任务间隔较短,例如20秒一次,对数据库的更新频率太高,压力太大
  • 假如定时任务间隔较长,例如2分钟一次,更新频率较低,时效性可能超过2分钟,不满足需求

在学习记录统计场景下有什么办法能够在不增加数据库压力的情况下,保证时间误差较低吗?

假如一个视频时长为20分钟,我们从头播放至15分钟关闭,每隔15秒提交一次播放进度,大概需要提交60次请求。

但是下一次我们再次打开该视频续播的时候,肯定是从最后一次提交的播放进度来续播。也就是说续播进度之前的N次播放进度都是没有意义的,都会被覆盖。既然如此,完全没有必要定期把这些播放进度写到数据库,只需要将用户最后一次提交的播放进度写入数据库即可。

只要能判断Redis中的播放进度是否变化即可。怎么判断呢?

每当前端提交播放记录时,我们可以设置一个延迟任务并保存这次提交的进度。等待20秒后(因为前端每15秒提交一次,20秒就是等待下一次提交),检查Redis中的缓存的进度与任务中的进度是否一致。

  • 不一致:说明持续在提交,无需处理
  • 一致:说明是最后一次提交,更新学习记录、更新课表最近学习小节和时间到数据库中

4.延迟任务方案对比

DelayQueueRedissonMQ时间轮
原理JDK自带延迟队列,基于阻塞队列实现。基于Redis数据结构模拟JDK的DelayQueue实现利用MQ的特性。例如RabbitMQ的死信队列时间轮算法
优点不依赖第三方服务分布式系统下可用不占用JVM内存分布式系统下可以不占用JVM内存不依赖第三方服务性能优异
缺点占用JVM内存只能单机使用依赖第三方服务依赖第三方服务只能单机使用

以上四种方案都可以解决问题,不过本例中我们会使用DelayQueue方案。因为这种方案使用成本最低,而且不依赖任何第三方服务,减少了网络交互。

但缺点也很明显,就是需要占用JVM内存,在数据量非常大的情况下可能会有问题。但考虑到任务存储时间比较短(只有20秒),因此也可以接收。

如果你们的数据量非常大,DelayQueue不能满足业务需求,大家也可以替换为其它延迟队列方式,例如Redisson、MQ等

5.Redis数据结构设计

一方面我们要缓存写数据,减少写数据库频率;另一方面我们要缓存播放记录,减少查询数据库。因此,缓存中至少要包含3个字段:

  • 记录id:id,用于根据id更新数据库
  • 播放进度:moment,用于缓存播放进度
  • 播放状态(是否学完):finished,用于判断是否是第一次学完

课程有很多,每个课程的小节也非常多。每个小节都是一个独立的KEY,需要创建的KEY也会非常多,浪费大量内存。可以把一个课程的多个小节作为一个KEY来缓存

在这里插入图片描述

6.代码实现

6.1定义延迟任务类

@Data
public class DelayTask<D> implements Delayed {private D data;private long deadlineNanos;public DelayTask(D data, Duration delayTime) {this.data = data;this.deadlineNanos = System.nanoTime() + delayTime.toNanos();}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(Math.max(0, deadlineNanos - System.nanoTime()), TimeUnit.NANOSECONDS);}@Overridepublic int compareTo(Delayed o) {long l = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);if(l > 0){return 1;}else if(l < 0){return -1;}else {return 0;}}
}

6.2定义延迟任务处理类

package com.tianji.learning.utils;import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.DelayQueue;@Slf4j
@Component
@RequiredArgsConstructor
public class LearningRecordDelayTaskHandler {private final StringRedisTemplate redisTemplate;private final LearningRecordMapper recordMapper;private final ILearningLessonService lessonService;private final DelayQueue<DelayTask<RecordTaskData>> queue = new DelayQueue<>();private final static String RECORD_KEY_TEMPLATE = "learning:record:{}";private static volatile boolean begin = true;@PostConstructpublic void init(){CompletableFuture.runAsync(this::handleDelayTask);}@PreDestroypublic void destroy(){begin = false;log.debug("延迟任务停止执行!");}public void handleDelayTask(){while (begin) {try {// 1.获取到期的延迟任务DelayTask<RecordTaskData> task = queue.take();RecordTaskData data = task.getData();// 2.查询Redis缓存LearningRecord record = readRecordCache(data.getLessonId(), data.getSectionId());if (record == null) {continue;}// 3.比较数据,moment值if(!Objects.equals(data.getMoment(), record.getMoment())) {// 不一致,说明用户还在持续提交播放进度,放弃旧数据continue;}// 4.一致,持久化播放进度数据到数据库// 4.1.更新学习记录的momentrecord.setFinished(null);recordMapper.updateById(record);// 4.2.更新课表最近学习信息LearningLesson lesson = new LearningLesson();lesson.setId(data.getLessonId());lesson.setLatestSectionId(data.getSectionId());lesson.setLatestLearnTime(LocalDateTime.now());lessonService.updateById(lesson);} catch (Exception e) {log.error("处理延迟任务发生异常", e);}}}public void addLearningRecordTask(LearningRecord record){// 1.添加数据到Redis缓存writeRecordCache(record);// 2.提交延迟任务到延迟队列 DelayQueuequeue.add(new DelayTask<>(new RecordTaskData(record), Duration.ofSeconds(20)));}public void writeRecordCache(LearningRecord record) {log.debug("更新学习记录的缓存数据");try {// 1.数据转换String json = JsonUtils.toJsonStr(new RecordCacheData(record));// 2.写入RedisString key = StringUtils.format(RECORD_KEY_TEMPLATE, record.getLessonId());redisTemplate.opsForHash().put(key, record.getSectionId().toString(), json);// 3.添加缓存过期时间redisTemplate.expire(key, Duration.ofMinutes(1));} catch (Exception e) {log.error("更新学习记录缓存异常", e);}}public LearningRecord readRecordCache(Long lessonId, Long sectionId){try {// 1.读取Redis数据String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);Object cacheData = redisTemplate.opsForHash().get(key, sectionId.toString());if (cacheData == null) {return null;}// 2.数据检查和转换return JsonUtils.toBean(cacheData.toString(), LearningRecord.class);} catch (Exception e) {log.error("缓存读取异常", e);return null;}}public void cleanRecordCache(Long lessonId, Long sectionId){// 删除数据String key = StringUtils.format(RECORD_KEY_TEMPLATE, lessonId);redisTemplate.opsForHash().delete(key, sectionId.toString());}@Data@NoArgsConstructorprivate static class RecordCacheData{private Long id;private Integer moment;private Boolean finished;public RecordCacheData(LearningRecord record) {this.id = record.getId();this.moment = record.getMoment();this.finished = record.getFinished();}}@Data@NoArgsConstructorprivate static class RecordTaskData{private Long lessonId;private Long sectionId;private Integer moment;public RecordTaskData(LearningRecord record) {this.lessonId = record.getLessonId();this.sectionId = record.getSectionId();this.moment = record.getMoment();}}
}
  • ① 添加播放记录到Redis,并添加一个延迟检测任务到DelayQueue
  • ② 查询Redis缓存中的指定小节的播放记录
  • ③ 删除Redis缓存中的指定小节的播放记录
  • ④ 异步执行DelayQueue中的延迟检测任务,检测播放进度是否变化,如果无变化则写入数据库
http://www.yayakq.cn/news/144264/

相关文章:

  • 网站搭建徐州百都网络搭建山东济南网网站建设
  • 网站开发服务税率是多少建设合同网上备案上哪个网站
  • 门户网站建设报告vs215开发python网站开发
  • 网站没有经过我司审核通过白名单学网站开发可以创业吗
  • 专业做网站官网制作网页计算器
  • 做网站需要缴什么费用网站建设背景分析
  • 中英文双语企业网站网站经营与建设
  • 主机做网站做ppt模板网站
  • 柳州建设局网站怎样设计网站版面
  • 西双版纳 网站建设网页制作工具按其制作方式分为
  • 基层建设被哪些网站全文收录河南网站制作
  • 网站建设个人先进材料自己写的网站怎么发布
  • 先做产品网站还是app禄丰县住房和城乡建设局网站
  • 301的网站用什么来做动漫制作必须会画画吗
  • 网站文字很少怎么做优化设计上海展会2022
  • 网站方案策划书wordpress页面添加描述
  • 如何建设网站 知乎wordpress 后台500
  • 手机网站建设品牌好晋州网站建设哪家好
  • hltm 做网站教程突泉建设局三务公开网站
  • 广州注册公司在哪个网站手机网站的引导页
  • 淡蓝色网站网络工程师可能自学吗
  • 企业网站展示广元网站建设价格
  • 网站开发流行工具企业网站适合响应式嘛
  • asp.net 知名网站wordpress主题网址导航葬爱
  • 拿了网赌代理后怎样做自己的网站网站怎么做弹出表单
  • 网站建设网络网件路由器登录密码
  • 网站开发实战第二章wordpress当前页询价
  • 抖音代运营ppt网站建设优化服务信息
  • 备案网站资料上传教程济南富新网站建设
  • php 除了做网站青岛专业网站建设价格