项目地址

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

项目职责:

  1. 负责课程内容管理模块的设计与开发,使Redis缓存课程信息减轻数据库压力,同时解决缓存穿透,缓存击穿、数据一致性等问题。
  2. 使用Elasticsearch为课程建立索引,提高课程检索效率,基于本地消息表加XXL-Job任务调度实现分布式事务控制
  3. 使用线程池实现任务的并行处理,提高执行效率,同时基于消息表的状态字段来避免任务重复执行,保证任务的幂等性。
  4. 负责媒体资源管理模块的开发,将视频,图片,文档等媒体资源上传到MinIO分布式文件系统,实现大文件断点续传
  5. 参与订单管理模块的开发,实现课程购买,代金券抢购等业务功能,使用RabbitMQ进行结果通知,基于延迟队列处理未支付订单。
  6. 使用RabbitMQ进行流量削峰,优化代金券抢购,同时解决了代金券超卖和重复下单问题,基于Redis+Lua脚本实现。
  7. 对各模块进行统一异常处理,自定义项目异常,对不同异常编写不同的处理逻辑,基于JSR303框架实现请求参数的合法性校验。
  8. 负责搭建Redis主从集群,加入Sentinel机制,实观读写分离,提高系统的高可用、高并发能力。

面试题

使用设计模式优化代码:

登录、支付:(工厂+策略)
提供了很多种策略,都让spring容器管理
提供一个工厂:准备策略对象,根据参数提供对象


下订单:(责任链)

参数校验 -> 补充订单信息 -> 计算相关信息 -> 订单入库

第7点

  1. 系统如何处理异常?

    • 我们自定义一个统一的异常处理器去捕获并处理异常
    • 使用控制器增强注解**@ControllerAdvice(也可以用@RestControllerAdvice)和异常处理注解@ExceptionHandler**来实现
  2. 如何处理自定义异常?

    • 程序在编写代码时,根据校验结果主动抛出自定义异常类对象,抛出异常时指定详细的异常信息,异常处理器捕获异常信息记录、异常日志,并响应给用户
  3. 如何处理未知异常?

    • 接口执行过程中的一些运行时异常也会被异常处理器统一捕获,记录异常日志,统一响应给用户500错误
    • 在异常处理其中还可以对某个异常类型进行单独处理(使用@ExceptionHandler来声明要捕获的异常类型)
  4. 请求参数的合法性如何校验?

    • 使用基于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分布式任务调度服务由调度中心和执行器组成,调度中心负责按任务调度策略向执行器下发任务,执行器负责接收任务,执行任务

    1. 首先部署并启动xxl-job调度中心(一个java工程,打成jar包可以放到虚拟机上运行)

      1
      nohup java -jar xxl-job-admin...  & 
    2. 在微服务中添加xxl-job依赖,在微服务中配置执行器

      • 依赖
      • yml配置
      • 配置类
      1
      2
      3
      4
      <dependency>
      <groupId>com.xuxueli</groupId>
      <artifactId>xxl-job-core</artifactId>
      </dependency>
    3. 启动微服务,执行器向调度中心上报自己

    4. 在微服务中写一个任务方法,并用xxl-job的注解去标记执行任务的方法名称

      1
      2
      3
      4
      @XxlJob("testJob")
      public void testJob() {
      log.debug("开始执行.......");
      }
    5. 在调度中心配置任务调度策略,调度策略就是每个多长时间执行,又或者是每天/每月的固定时间去执行等

    6. 在调度中心启动任务

    7. 调度中心根据任务调度策略,到达时间就开始下发任务给执行器

    8. 执行器收到任务就开始执行任务

如何保证任务不重复执行?

  1. 调度中心按分片广播的方式去下发任务
  2. 执行器收到作业分片广播的参数:分片总数(shardTotal)和分片序号(shardIndex),计算任务id % 分片总数(taskId % shardTotal),如果结果等于分片序号,就去执行这个任务(taskId % shardTotal = shardIndex)。这样就可以保证不同的执行器执行不同的任务
  3. 配置调度过期策略为忽略,避免同一个执行器多次重复执行同一个任务
  4. 配置任务阻塞处理策略为丢弃后续调度,注意:丢弃也没事,下一次调度还可以执行
  5. 另外还要保证任务处理的幂等性,执行过的任务可以打一个状态标记已完成(上面的代码设置status=2即为完成),下次再次调度该任务时,判断该任务已完成,就不再执行

任务幂等性如何保证?

  • 幂等性描述的是一次和多次请求某一个资源,对于资源本身,应该返回同样的结果

  • 幂等性是为了解决重复提交问题,例如:恶意刷单,重复支付等

  • 解决幂等性的常用方案

    1. 数据库约束,例如:唯一索引、主键
    2. 乐观锁:常用于数据库,更新数据时,根据乐观锁的状态去更新
    3. 唯一序列号,请求前生成的唯一序列号,携带序列号去请求,执行时在redis记录该序列号,用于表示该序列号请求已经执行过了,如果相同的序列号再次来执行,则说明是重复执行。

    本项目的解决方式是在数据库中添加状态处理字段,视频处理完成,则更新该字段为已完成,执行视频处理之前判断状态是否为已完成,若已完成则不处理

什么是分布式事务?

  • 由多个服务通过网络完成一个事务叫分布式事务
  • 例如:课程发布操作不仅要在本地数据库插入课程信息,还要请求索引服务将课程信息添加到索引库,这里就存在分布式事务

分布式事务控制的方案有哪些?

  • 首先根据CAP原理决定我们的需求,是要实现CP还是AP
  • 实现CP就是要实现强一致性,可以使用Seata框架基于AT、TCC模式去实现
  • 我们项目中大部分实现的是AP,使用本地消息表加任务调度,保证分布式事务最终数据一致性

⭐⭐⭐如何使用本地消息表加任务调度完成分布式事务控制?

学习文章:https://juejin.cn/post/6844904041659498509

  • 发布课程为例进行说明,发布课程需要在内容管理数据库中写课程发布表记录,同时将课程信息同步到Redis、ElasticSearch、MinIO,这里存在分布式事务
    1. 点击发布课程,使用本地事务向发布表写一个课程信息,同时向消息表写一个消息记录(标记发布的是哪门课程)
    2. xxl-job的调度中心使用分片广播模式向执行器下发任务,开始扫描消息表,查询到了待处理消息
    3. 根据消息的内容将课程信息同步到Redis、ElasticSearch、MinIO
    4. 任务完成,删除消息表记录。整个分布式事务完成,最终保证了一致性

image-20231229222033846


image-20231229222007464

关于线程池的问题

在使用线程池实现任务的并行处理、并结合消息表状态字段确保任务的幂等性,可能涉及以下一些任务

  1. 课程内容更新:
    • 当有新的课程内容需要更新时,可以通过异步任务进行并行处理,使用线程池来提高处理效率。
    • 通过消息表记录课程更新任务的状态,避免重复执行,确保任务的幂等性。这可以包括课程信息的修改、添加新的教材等。
  2. 媒体资源上传与处理(本项目)
    • 媒体资源包括视频、图片、文档等,上传和处理这些资源可能是一个较为耗时的任务。
    • 使用线程池实现并行处理上传任务(数据汇总),同时通过消息表记录上传任务的状态,确保幂等性。这可以包括将媒体资源上传到MinIO分布式文件系统,并处理大文件的断点续传。
    • 数据汇总:如果所有接口(或部分接口)的没有依赖关系,就可以使用线程池+future来提升性能
  3. 订单处理与通知:
    • 订单管理模块可能需要处理用户购买课程的订单,进行库存扣减、支付状态更新等操作。
    • 使用线程池进行订单处理任务的并行处理,通过消息表记录任务状态,确保订单处理的幂等性。同时,使用RabbitMQ进行结果通知,基于延迟队列处理未支付订单,以提高系统的稳定性。

总体而言,这些任务的异步处理和并行执行,通过线程池的机制可以有效提高系统的响应速度和处理能力。消息表的引入则有助于确保任务的幂等性,避免由于重复执行任务而引发的问题。