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

天机学堂 第四天 高并发优化总结

前端每隔15秒就发起一次请求,将播放记录写入数据库。

但问题是,提交播放记录的业务太复杂了,其中涉及到大量的数据库操作:

如何进行优化

单机并发能力 变同步为异步 合并写请求

提高单机并发:优化SQL,尽量走索引,避免双重for循环,添加缓存

 提高单机并发能力

变同步为异步

合并写请求

合并写请求方案其实是参考高并发读的优化思路:当读数据库并发较高时,我们可以把数据缓存到Redis,这样就无需访问数据库,大大减少数据库压力,减少响应时间。

既然读数据可以建立缓存,那么写数据可以不可以也缓存到Redis呢?

答案是肯定的,合并写请求就是指当写数据库并发较高时,不再直接写到数据库。而是先将数据缓存到Redis,然后定期将缓存中的数据批量写入数据库。

提交学习记录业务优化

记录是否已经存在放入缓存中(因为每次提交都会去数据库查询),更新学习记录时间放入缓存中

对提交学习记录进行改造,每隔15秒进行一次提交对数据库压力太大,考虑到只有最后一次提交才有效,所以我们对存在数据库操作的地方进行优化

而播放进度信息,不管更新多少次,下一次续播肯定是从最后的一次播放进度开始续播。也就是说我们只需要记住最后一次即可。因此可以采用合并写方案来降低数据库写的次数和频率,而异步写做不到。

 如何设计缓存字段?

用户学习视频的过程中,可能会在多个视频之间来回跳转,这就会导致频繁的创建缓存、缓存过期,影响到最终的业务性能。该如何解决呢?

使用hash key解决

实际操作中可以直接把实体类转化为JSON 当做value存入

 但是存在一定问题

但问题来了,我们怎么知道哪一次提交是最后一次提交呢?

只要用户一直在提交记录,Redis中的播放进度就会一直变化。如果Redis中的播放进度不变,肯定是停止了播放,是最后一次提交。

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

核心思想

每当前端提交播放记录时,我们可以设置一个延迟任务并保存这次提交的进度。等待20秒后(因为前端每15秒提交一次,20秒就是等待下一次提交),检查Redis中的缓存的进度与任务中的进度是否一致。(把数据缓存到redis中,同时设置一个20秒的延迟任务,20秒后执行这个任务,执行这个任务的时候再一次跟redis中的时间比对,如果一样则更新数据库,否则跳过

  • 不一致:说明持续在提交,无需处理

  • 一致:说明是最后一次提交(暂停了或者离开播放了),(提交延迟任务)更新学习记录、更新课表最近学习小节和时间到数据库中

 延迟任务方案

DelayQueue的用法 

package com.tianji.learning.utils;import lombok.Data;import java.time.Duration;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;@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;}}
}

接下来就可以创建延迟任务,交给延迟队列保存:

package com.tianji.learning.utils;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;import java.time.Duration;
import java.util.concurrent.DelayQueue;@Slf4j
class DelayTaskTest {@Testvoid testDelayQueue() throws InterruptedException {// 1.初始化延迟队列DelayQueue<DelayTask<String>> queue = new DelayQueue<>();// 2.向队列中添加延迟执行的任务log.info("开始初始化延迟任务。。。。");queue.add(new DelayTask<>("延迟任务3", Duration.ofSeconds(3)));queue.add(new DelayTask<>("延迟任务1", Duration.ofSeconds(1)));queue.add(new DelayTask<>("延迟任务2", Duration.ofSeconds(2)));// TODO 3.尝试执行任务}
}

执行任务

package com.tianji.learning.utils;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;import java.time.Duration;
import java.util.concurrent.DelayQueue;@Slf4j
class DelayTaskTest {@Testvoid testDelayQueue() throws InterruptedException {// 1.初始化延迟队列DelayQueue<DelayTask<String>> queue = new DelayQueue<>();// 2.向队列中添加延迟执行的任务log.info("开始初始化延迟任务。。。。");queue.add(new DelayTask<>("延迟任务3", Duration.ofSeconds(3)));queue.add(new DelayTask<>("延迟任务1", Duration.ofSeconds(1)));queue.add(new DelayTask<>("延迟任务2", Duration.ofSeconds(2)));// 3.尝试执行任务while (true) {DelayTask<String> task = queue.take();log.info("开始执行延迟任务:{}", task.getData());}}
}

 

 开始改造

封装的工具类

延迟队列里面放的就是这一个个DelayTask<T>

@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;}}
}

 需要开启另一个线程来执行任务

@Slf4j
@RequiredArgsConstructor
@Component
public class LearningRecordDelayTaskHandler {private final StringRedisTemplate redisTemplate;private final DelayQueue<DelayTask<RecordTaskData>> queue = new DelayQueue<>();private final static String RECORD_KEY_TEMPLATE = "learning:record:{}";private final LearningRecordMapper recordMapper;private final ILearningLessonService lessonService;private static volatile boolean begin = true;// 项目启动后 当前类实例化 属性输入之后 方法就会运行 一般用来做初始化工作@PostConstructpublic void init(){CompletableFuture.runAsync(this::handleDelayTask);log.debug("开启 新线程执行handleDelayTask方法");// 开启 新线程执行handleDelayTask方法}@PreDestroy  // 当前类是实例 销毁之前该方法执行public void destroy(){log.debug("关闭学习记录处理的延迟任务");begin = false;}private void handleDelayTask(){while (begin){try {// 1.尝试获取任务   take是阻塞方法DelayTask<RecordTaskData> task = queue.take();log.debug("获取到要处理的播放记录任务");RecordTaskData data = task.getData();// 2.读取Redis缓存LearningRecord record = readRecordCache(data.getLessonId(), data.getSectionId());log.debug("获取到要处理的播放记录任务,任务数据{}  缓存中的数据{}",data,record);if (record == null) {continue;}// 3.比较数据if(!Objects.equals(data.getMoment(), record.getMoment())){// 4.如果不一致,播放进度在变化,无需持久化continue;}// 5.如果一致,证明用户离开了视频,需要持久化// 5.1.更新学习记录record.setFinished(null);recordMapper.updateById(record);// 5.2.更新课表LearningLesson lesson = new LearningLesson();lesson.setId(data.getLessonId());lesson.setLatestSectionId(data.getSectionId());lesson.setLatestLearnTime(LocalDateTime.now());lessonService.updateById(lesson);log.debug("准备持久化学习记录信息");} 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();}}
}

使用线程池来处理任务

目前我们的延迟任务执行还是单线程模式,大家将其改造为线程池模式,

 拉取方法 还是使用哪个开辟的线程去拉取,但是拉取完之后的执行让线程池里面的来执行

 面试题

相关文章:

天机学堂 第四天 高并发优化总结

前端每隔15秒就发起一次请求&#xff0c;将播放记录写入数据库。 但问题是&#xff0c;提交播放记录的业务太复杂了&#xff0c;其中涉及到大量的数据库操作&#xff1a; 如何进行优化 单机并发能力 变同步为异步 合并写请求 提高单机并发&#xff1a;优化SQL&#xff0c;尽…...

Canva收购Leonardo.ai,增强生成式AI技术能力

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

前端练习<HtmlCSS>——照片墙(附完整代码及实现效果)

这个小练习也来源于b站up小K师兄&#xff0c;大家可以通过下面的链接学习哦~up讲的非常详细。 纯CSS写一个简单酷炫的照片墙效果&#xff5e; 先看一下这个照片墙的效果&#xff1a; 1.鼠标没有放到图片上时&#xff0c;照片同比例&#xff0c;每张照片都有倒影的效果。 2.然…...

PHP基于微信小程序的打车平台-计算机毕业设计源码78689

摘 要 本文介绍的是基于PHP开发的打车平台小程序。该系统旨在为用户提供一个便捷、高效的平台&#xff0c;以实现网约车的打车功能。随着社交媒体和互联网的普及&#xff0c;网约车已成为日常交通中常见的形式。然而&#xff0c;传统的打车方式存在不方便、不及时等问题。 微信…...

Vue element ui分页组件示例

https://andi.cn/page/621615.html...

redis存储结构

一、整体结构图 二、redisDb结构体 Redis是一个高性能的键值存储系统&#xff0c;它支持多种类型的数据结构&#xff0c;如字符串、列表、集合、散列等。Redis数据库由多个数据库组成&#xff0c;每个数据库用一个redisDb结构体来表示。 dict *dict; dict指向一个字典结构的指…...

SQL Server 数据误删的恢复

在日常的数据库管理中&#xff0c;数据的误删操作是难以避免的。为了确保数据的安全性和完整性&#xff0c;我们必须采取一些措施来进行数据的备份和恢复。本文将详细介绍如何在 SQL Server 中进行数据的备份和恢复操作&#xff0c;特别是在发生数据误删的情况下。假设我们已经…...

墨烯的C语言技术栈-C语言基础-018

char c; //1byte字节 8bit比特位 int main() { int a 10; //向内存申请四个字节,存储10 &a; //取地址操作符 return 0; } 每个字节都有地址 而a的地址就是它第一个字节的地址 要先开始调试才可以查看监控和查看内存 左边是地址 中间是内存中的数据 最后面的是…...

C端与B端 - 第一弹 - 理解和区分C端与B端软件开发

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有疑问和建议&#xff0c;请私信或评论留言&#xff01; 前言 在软件开发领域…...

穿越多元宇宙的.NET:一场跨平台的星际旅行

概述 在软件开发的浩瀚宇宙中&#xff0c;.NET无疑是一颗耀眼的恒星&#xff0c;散发着多平台开发的光芒。从单一的.NET Framework出发&#xff0c;我们如今已拥有一个多元化的.NET宇宙&#xff0c;每个变体都是一个独特的星球&#xff0c;拥有自己的生态系统和生存法则。本文将…...

Python自学第五天

# 嵌套 字典嵌套字典 # 字典列表 now {pet:cat,color:black} now1 {pet:cat,color:pipe} wq [now,now1] # 这里是中括号 不是花括号 花括号打印不出来 for ff in wq:print(ff) # 创建20个外星人 打印前三个 并且显示一共创建了多少个外星人 now [] for wq in range(20):# 这…...

Cookie-Monster:一款针对Web浏览器的安全分析与数据提取工具

关于Cookie-Monster Cookie-Monster是一款针对常见Web浏览器的安全分析与数据提取工具&#xff0c;该工具可以帮助广大研究人员提取并分析Edge、Chrome和Firefox浏览器中的Cookie数据。 Cookie-Monster适用于红队和蓝队成员&#xff0c;能够提取WebKit主密钥&#xff0c;找到具…...

C语言的结构体

结构体定义 结构体指针...

C语言 写一个函数days,实现某日在本年中是第几天计算。

写一个函数days, 【定义一个结构体变量(包括年、月、日)。计算该日在本年中是第几天,注意闰年问题&#xff08;即将闰年情况包含在内&#xff09;】 由主函数将年、月、日传递给days函数,计算后将日子数传回主函数输出。 ​#include <stdio.h>typedef struct {int yea…...

2-50 基于matlab的遗传模拟退火算法的聚类算法

基于matlab的遗传模拟退火算法的聚类算法&#xff0c;以模糊K-均值聚类算法为基础&#xff0c;对各样本的聚类中心进行优化&#xff0c;输出聚类可视化结果。聚类类别数可自由输入。程序已调通&#xff0c;可直接运行。 2-50 遗传模拟退火算法的聚类算法 - 小红书 (xiaohongshu…...

电脑屏幕录制软件,分享4款(2024最新)

在今天&#xff0c;我们的电脑屏幕成为了一个多彩多姿的窗口。通过它我们可以浏览网页、观看视频、处理文档、进行游戏……有时&#xff0c;我们想要记录下这些精彩瞬间&#xff0c;与朋友分享&#xff0c;或者作为教程留存&#xff0c;这时&#xff0c;电脑屏幕录制就显得尤为…...

机械学习—零基础学习日志(高数16——函数极限性质)

零基础为了学人工智能&#xff0c;真的开始复习高数 这里我们继续学习函数极限的性质。 局部有界性 充分条件与必要条件 极限存在是函数局部有界的充分条件。什么是充分条件&#xff0c;什么是必要条件呢&#xff1f;我这里做了一点小思考&#xff0c;和大家分享&#xff0c…...

初识c++——list

一、list 1、list结构 c中list为双向带头循环列表&#xff1a; 二、list接口 1、构造 using namespace std; #include<iostream> #include<list> #include<vector> int main() {list<int> lt; //构造空的listlist<int> lt1(10, 1); //构造的l…...

angular入门基础教程(八)表单之双向绑定

绑定表单数据 为了让表单使用 Angular 的特性实现数据绑定&#xff0c;需要导入 FormsModule。 这个比 vue 要繁琐点&#xff0c;不复杂&#xff0c;但是比 react 的自己手动实现要方便&#xff0c;ng 帮我们实现了双向绑定 import { Component } from "angular/core&qu…...

【C++】C++中的find方法介绍

目录 一.find方法基本用法 1.查找字符 2.查找子字符串 3.查找子字符串&#xff08;从指定位置开始&#xff09; 4.查找字符范围 5.查找不包含特定字符的范围 二.使用string::npos返回无效位置 三.总结 在C中&#xff0c; std::string 类的 find 成员函数用于查找子字…...

Windows平台PDF处理终极指南:Poppler for Windows让你告别复杂编译

Windows平台PDF处理终极指南&#xff1a;Poppler for Windows让你告别复杂编译 【免费下载链接】poppler-windows Download Poppler binaries packaged for Windows with dependencies 项目地址: https://gitcode.com/gh_mirrors/po/poppler-windows 还在为Windows系统上…...

中关村、首体院、京奥电竞三方签约,共探AI+电竞产学研一体化突破

AI电竞&#xff1a;三方签约开启产学研新篇在今日的大会上&#xff0c;中关村人工智能研究院、首都体育学院、京奥电竞&#xff08;北京&#xff09;科技有限公司举行了一场重量级的三方签约。中关村人工智能研究院专注于具有产业价值和颠覆意义的人工智能与交叉学科领域探索&a…...

SolidWorks 2024新手避坑指南:从草图到三维实体,这5个特征操作最容易出错

SolidWorks 2024新手避坑指南&#xff1a;从草图到三维实体的5个关键特征操作 刚接触SolidWorks的新手工程师常常会在从二维草图转向三维实体建模的过程中踩到各种"坑"。这些错误不仅浪费时间&#xff0c;还可能让人对这款强大的三维设计软件产生挫败感。本文将聚焦五…...

GoQt实战教程:构建你的第一个跨平台桌面应用

GoQt实战教程&#xff1a;构建你的第一个跨平台桌面应用 【免费下载链接】goqt Golang bindings to the Qt cross-platform application framework. 项目地址: https://gitcode.com/gh_mirrors/go/goqt 想要用Golang开发跨平台桌面应用吗&#xff1f;GoQt是你的终极解决…...

AI学习 Newsletter 的手工感设计:从断点驱动到可追溯实践

1. 项目概述&#xff1a;这不是一份 newsletter&#xff0c;而是一份 AI 社区共建的实践手记 “Learn AI Together — Towards AI Community Newsletter #14”——看到这个标题&#xff0c;你第一反应可能是&#xff1a;又一份 AI 领域的资讯汇总&#xff1f;点开看看最新论文…...

CH340串口调试进阶:手把手教你搭建RS422转TTL双机通信测试环境

CH340串口调试进阶&#xff1a;手把手教你搭建RS422转TTL双机通信测试环境 在工业自动化、物联网设备开发中&#xff0c;稳定可靠的串行通信是设备间数据交互的基石。当传输距离超过几米&#xff0c;或环境存在电磁干扰时&#xff0c;传统的TTL电平通信就会暴露出抗干扰能力弱、…...

Windows热键冲突终极指南:如何用Hotkey Detective一键精准定位占用程序

Windows热键冲突终极指南&#xff1a;如何用Hotkey Detective一键精准定位占用程序 【免费下载链接】hotkey-detective A small program for investigating stolen key combinations under Windows 7 and later. 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-detecti…...

高通量细胞因子/生物因子检测技术介绍

高通量细胞因子/生物因子检测技术介绍—多维免疫分析技术&#xff0c;赋能精准医学与转化研究 导语 伴随精准医学领域持续深耕与转化医学研究的高速推进&#xff0c;细胞因子、趋化因子、生长因子等各类可溶性生物标志物的动态表达变化&#xff0c;成为解析疾病发病机制、研判…...

NotebookLM移动端体验全拆解(iOS/Android双端对比报告·仅限内测用户知晓的性能阈值)

更多请点击&#xff1a; https://kaifayun.com 第一章&#xff1a;NotebookLM移动端体验全景概览 NotebookLM 作为 Google 推出的基于用户自有文档构建的 AI 助手&#xff0c;其移动端&#xff08;iOS/Android&#xff09;已正式开放下载。该应用并非简单将网页版界面缩放适配…...

3个步骤掌握Betaflight飞控固件:从零开始打造专业级无人机飞行体验

3个步骤掌握Betaflight飞控固件&#xff1a;从零开始打造专业级无人机飞行体验 【免费下载链接】betaflight Open Source Flight Controller Firmware 项目地址: https://gitcode.com/gh_mirrors/be/betaflight Betaflight作为全球最受欢迎的开源飞控固件&#xff0c;为…...