当前位置: 首页 > 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.khdw.cn/news/1005.html

相关文章:

  • 网站开发西安seo优化专家
  • 乌镇网站开发文档怎样优化网站排名靠前
  • 怀化网站建设google chrome谷歌浏览器
  • 设计开发建设网站关键词密度
  • 文章网站后台管理系统培训班报名
  • 定制网站开发报价app下载注册量推广平台
  • asp.net网站建设ppt百度关键词规划师入口
  • 网站建设贝尔利网络优化seo是什么工作
  • 霞浦县网站seo优化排名东莞seo报价
  • 多个域名 一个网站宁波seo网站
  • 有没有做英语题的网站网络营销推广方案ppt
  • arprice插件wordpress优化关键词的公司
  • 变性 wordpress关键词排名优化易下拉软件
  • 什么网站可以做性格测试seo优化范畴
  • 跨境独立站建站平台有哪些定制网站多少钱
  • 商城网站的psd模板免费下载百度主页面
  • 网站顶部下拉广告风云榜百度
  • 聊城网站建设有限公司新媒体营销案例分析
  • 商务网站策划 网站目标与经营模式定位东莞网络推广营销
  • 做毕业设计网站需要的工具站长之家音效素材
  • wordpress设置网址导航关键词优化排名查询
  • 网站空间 流量西安seo排名扣费
  • 嘉兴快速建站合作班级优化大师怎么下载
  • 南部县网站建设推广网络推广
  • 怎么编辑网站培训学校招生营销方案
  • 女生做网站主题有哪些网络推广电话销售技巧和话术
  • 网页制作专业必备seo在哪学
  • 外贸网站建设软件有哪些seo搜索优化网站推广排名
  • 商城类网站方案网络做推广公司
  • 桂林生活网官方网站天津seo顾问