智学轩在线教育平台
项目地址
https://github.com/PlanBBBBB/zhixuexuan
项目经历
2023.09 - 2023.11
智学轩在线教学平台
主要成员
项目描述: 本项目是一个面向大众用户职业技能学习的在线教育平台,提供了职业技能培训相关课程。项目基于B2B2C的业务模式,包括门户、个人学习中心、教学机构管理平台、运营平台、社交系统和系统管理6大模块。
技术概述: 本项目采用SpringCloud微服务架构,使用Nacos实现服务的注册与发现,统一配置文件管理,Gateway网关实现动态路由以及负载均衡,使用Redis提高数据读取和检索效率,使用MinIO进行分布式文件存储,同时使用XXL-Job完成分布式任务调度。
技术栈: SpringCloud + SpringSecurity + Nacos + Redis + MySQL + Mybatis + RabbitMQ + Elasticsearch + MinlO + XXL-Job+ Nginx+Vue
项目职责:
- 负责课程内容管理模块的设计与开发,使Redis缓存课程信息减轻数据库压力,同时解决缓存穿透,缓存击穿、数据一致性等问题。
- 使用Elasticsearch为课程建立索引,提高课程检索效率,基于本地消息表加XXL-Job任务调度实现分布式事务控制。
- 使用线程池实现任务的并行处理,提高执行效率,同时基于消息表的状态字段来避免任务重复执行,保证任务的幂等性。
- 负责媒体资源管理模块的开发,将视频,图片,文档等媒体资源上传到MinIO分布式文件系统,实现大文件断点续传。
- 参与订单管理模块的开发,实现课程购买,代金券抢购等业务功能,使用RabbitMQ进行结果通知,基于延迟队列处理未支付订单。
- 使用RabbitMQ进行流量削峰,优化代金券抢购,同时解决了代金券超卖和重复下单问题,基于Redis+Lua脚本实现。
- 对各模块进行统一异常处理,自定义项目异常,对不同异常编写不同的处理逻辑,基于JSR303框架实现请求参数的合法性校验。
- 负责搭建Redis主从集群,加入Sentinel机制,实观读写分离,提高系统的高可用、高并发能力。
面试题
使用设计模式优化代码:
登录、支付:(工厂+策略)
提供了很多种策略,都让spring容器管理
提供一个工厂:准备策略对象,根据参数提供对象
下订单:(责任链)
参数校验 -> 补充订单信息 -> 计算相关信息 -> 订单入库
第7点
系统如何处理异常?
- 我们自定义一个统一的异常处理器去捕获并处理异常
- 使用控制器增强注解**@ControllerAdvice(也可以用@RestControllerAdvice)和异常处理注解@ExceptionHandler**来实现
如何处理自定义异常?
- 程序在编写代码时,根据校验结果主动抛出自定义异常类对象,抛出异常时指定详细的异常信息,异常处理器捕获异常信息记录、异常日志,并响应给用户
如何处理未知异常?
- 接口执行过程中的一些运行时异常也会被异常处理器统一捕获,记录异常日志,统一响应给用户500错误
- 在异常处理其中还可以对某个异常类型进行单独处理(使用@ExceptionHandler来声明要捕获的异常类型)
请求参数的合法性如何校验?
- 使用基于
JSR-303
的校验框架实现,SpringBoot
提供了JSR-303
的支持,它就是spring-boot-starter-validation
,它包括了很多校验规则,只需要在模型类中通过注解指定校验规则,在Controller
方法上开启校验。
- 使用基于
第4点
断点续传是怎么做的?
我们是基于分块上传的模式实现断点续传的需求,当文件上传一部分断网后前边已经上传过的不再上传。
- 前端对文件分块
- 前端使用多线程一块一块上传,上传前给服务端发一个消息校验该分块是否上传,如果已上传则不再上传。
- 等所有分块上传完毕,服务端合并所有分块,校验文件的完整性(用md5值去校验)。因为分块全部上传到了服务器,服务器将所有分块按顺序进行合并,就是写每个分块文件内容按顺序依次写入一个文件中。使用字节流去读写文件。
- 前端给服务传了一个md5值,服务端合并文件后计算合并后文件的md5是否和前端传的一样,如果一样则说文件完整,如果不一样说明可能由于网络丢包导致文件不完整,这时上传失败需要重新上传
分块文件清理问题?
上传一个文件进行分块上传,上传一半不传了,之前上传到minio的分块文件要清理吗? 怎么做的?
在数据库中有一张文件表记录minio中存储的文件信息。
文件开始上传时会写入文件表,状态为上传中,上传完成会更新状态为上传完成
当一个文件传了一半不再上传了说明该文件没有上传完成,会有定时任务去查询文件表中的记录,如果文件未上传完成则删除minio中没有上传成功的文件目录。
第2点和第3点
xxl-job的工作原理是什么?xxl-job是什么?
xxl-job分布式任务调度服务由调度中心和执行器组成,调度中心负责按任务调度策略向执行器下发任务,执行器负责接收任务,执行任务
首先部署并启动xxl-job调度中心(一个java工程,打成jar包可以放到虚拟机上运行)
1
nohup java -jar xxl-job-admin... &
在微服务中添加xxl-job依赖,在微服务中配置执行器
- 依赖
- yml配置
- 配置类
1
2
3
4<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>启动微服务,执行器向调度中心上报自己
在微服务中写一个任务方法,并用xxl-job的注解去标记执行任务的方法名称
1
2
3
4
public void testJob() {
log.debug("开始执行.......");
}在调度中心配置任务调度策略,调度策略就是每个多长时间执行,又或者是每天/每月的固定时间去执行等
在调度中心启动任务
调度中心根据任务调度策略,到达时间就开始下发任务给执行器
执行器收到任务就开始执行任务
如何保证任务不重复执行?
- 调度中心按分片广播的方式去下发任务
- 执行器收到作业分片广播的参数:分片总数(shardTotal)和分片序号(shardIndex),计算
任务id % 分片总数
(taskId % shardTotal),如果结果等于分片序号,就去执行这个任务(taskId % shardTotal = shardIndex)。这样就可以保证不同的执行器执行不同的任务 - 配置调度过期策略为忽略,避免同一个执行器多次重复执行同一个任务
- 配置任务阻塞处理策略为丢弃后续调度,注意:丢弃也没事,下一次调度还可以执行
- 另外还要保证任务处理的幂等性,执行过的任务可以打一个状态标记已完成(上面的代码设置status=2即为完成),下次再次调度该任务时,判断该任务已完成,就不再执行
任务幂等性如何保证?
幂等性描述的是一次和多次请求某一个资源,对于资源本身,应该返回同样的结果
幂等性是为了解决重复提交问题,例如:恶意刷单,重复支付等
解决幂等性的常用方案
- 数据库约束,例如:唯一索引、主键
- 乐观锁:常用于数据库,更新数据时,根据乐观锁的状态去更新
- 唯一序列号,请求前生成的唯一序列号,携带序列号去请求,执行时在redis记录该序列号,用于表示该序列号请求已经执行过了,如果相同的序列号再次来执行,则说明是重复执行。
本项目的解决方式是在数据库中添加状态处理字段,视频处理完成,则更新该字段为已完成,执行视频处理之前判断状态是否为已完成,若已完成则不处理
什么是分布式事务?
- 由多个服务通过网络完成一个事务叫分布式事务
- 例如:课程发布操作不仅要在本地数据库插入课程信息,还要请求索引服务将课程信息添加到索引库,这里就存在分布式事务
分布式事务控制的方案有哪些?
- 首先根据CAP原理决定我们的需求,是要实现CP还是AP
- 实现CP就是要实现强一致性,可以使用Seata框架基于AT、TCC模式去实现
- 我们项目中大部分实现的是AP,使用本地消息表加任务调度,保证分布式事务最终数据一致性
⭐⭐⭐如何使用本地消息表加任务调度完成分布式事务控制?
学习文章:https://juejin.cn/post/6844904041659498509
- 以发布课程为例进行说明,发布课程需要在内容管理数据库中写课程发布表记录,同时将课程信息同步到Redis、ElasticSearch、MinIO,这里存在分布式事务
- 点击发布课程,使用本地事务向发布表写一个课程信息,同时向消息表写一个消息记录(标记发布的是哪门课程)
- xxl-job的调度中心使用分片广播模式向执行器下发任务,开始扫描消息表,查询到了待处理消息
- 根据消息的内容将课程信息同步到Redis、ElasticSearch、MinIO
- 任务完成,删除消息表记录。整个分布式事务完成,最终保证了一致性
关于线程池的问题
在使用线程池实现任务的并行处理、并结合消息表状态字段确保任务的幂等性,可能涉及以下一些任务:
- 课程内容更新:
- 当有新的课程内容需要更新时,可以通过异步任务进行并行处理,使用线程池来提高处理效率。
- 通过消息表记录课程更新任务的状态,避免重复执行,确保任务的幂等性。这可以包括课程信息的修改、添加新的教材等。
- 媒体资源上传与处理:(本项目)
- 媒体资源包括视频、图片、文档等,上传和处理这些资源可能是一个较为耗时的任务。
- 使用线程池实现并行处理上传任务(数据汇总),同时通过消息表记录上传任务的状态,确保幂等性。这可以包括将媒体资源上传到MinIO分布式文件系统,并处理大文件的断点续传。
- 数据汇总:如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能
- 订单处理与通知:
- 订单管理模块可能需要处理用户购买课程的订单,进行库存扣减、支付状态更新等操作。
- 使用线程池进行订单处理任务的并行处理,通过消息表记录任务状态,确保订单处理的幂等性。同时,使用RabbitMQ进行结果通知,基于延迟队列处理未支付订单,以提高系统的稳定性。
总体而言,这些任务的异步处理和并行执行,通过线程池的机制可以有效提高系统的响应速度和处理能力。消息表的引入则有助于确保任务的幂等性,避免由于重复执行任务而引发的问题。