java面试项目三:在线教育

1、项目简介

项目规模:15人

项目周期:2021.5-至今

开发环境:Intellij IDEA、Maven、JDK1.8、GIT

所用框架:Spring、SpringBoot、Mybatis-Plus、SpringCloud、Shiro、XXL-Job、

其他技术:RocketMQ、Redis、Mysql、MongoDB、ElasticSearch、JWT、Nginx、Nacos、Gateway、Nginx、EasyExcel、Sentinel

项目描述:XX在线教育是垂直的小学、初中、高中知识学习网站,课程紧跟前沿理念,帮助学生在当今课程体系体量压力较大的环境下,保证学生学习成绩提高,课程设置科学,满足不同基础用户的学习需求,快速匹配学习路线,使教育资源共享化,降低了学习门槛,课程主要分为免费课,VIP课,付费课三种,平台主要通过课程抽成,VIP会费及广告收费收取利益,平台主要分为网站前台,运营商后台,讲师后台等三方面组成

技术描述:

                  1、SpringCloud来建立各微服务之间的实时/异步的业务调用

                  2、通过Redis来完成首页轮播图缓存展示,分布式幂等校验,以及分布式锁的应用,课程排行等

                  3、RocketMQ来解耦服务间信息的同步,分布式事务,确保最终一致性,并对晚上起到削峰的作用

                  4、GateWay用来统一路由、对各业务线请求进行鉴权、限流处理提高系统健壮性

                  5、借助MongoDB完成评论,反馈留言等非核心业务数据的存储,使用EasyExcel导入导出

                  6、XXL-Job以及JavaMail实现定时发送邮件给老师统计本月课程售卖情况以及购买人群相关信息

                  7、通过阿里云第三方技术实现OSS图片上传,Alidayu短信发送,视频点播以及AliPay支付功能

                  8、借助ElasticSearch完成全文检索以及根据用户喜好定向推送课程(Redis)等

                  9、Nginx进行请求分发,负载均衡,Sentienl对高压接口进行限流和降级

项目职责:

            1、配合产品讨论业务方案,并持续跟进,参与项目的需求探讨

            2、负责后台广告模块、智能推荐的日常开发

            3、对核心业务的攻关,并提供相关解决方案,对其他需求开发的补位

            4、修改测试人员提出的BUG,编写相关接口文档,配合前端人员完成接口联调

            5、完成上级交代的其他任务,根据需求文档完成所分模块进行日常开发工作

二、项目介绍 

我最近参与的这个项目是服务于在职提升人员为主的在线教育平台,通过和.

个人老师入驻形式,向学习者提供在线和点播的网络授课资源。

对于上架的课程我们分成了免费与付费两种课程,所以平台是通过抽取佣金的方式来进行盈利的。包括像首页我们也是有广告投放功能,这也是平台盈利的方式之一。

整个平台主要分为网站前台、运营商后台、讲师管理后台三个子系统,我就从整体项目的架构方面来给您介绍吧。

三、项目开发流程 

在我们项目立项以后,项目经理、研发部和产品部一起针对项目进行了需求分析会议。会议结束以后,产品部门会给我们研发部提供需求文档,然后我们研发部再进行开会分析这些需求,根据需求进行分组后,分组后再进行讨论、创建表、写排期等,开发文档我们使用是这个swagger自动生成API文档。然后就是和我们项目老大一块去搭建框架,然后提交到远程仓库master上,由组员进行拉取分支,接下来就是根据需求进行开发。

四、项目架构图 

五、架构介绍 

1、微服务架构

在设计的时候考虑到项目是2C的(面向学员个人的),随着项目的运营,访问量也会不断增加的,所以我们这个项目选用的是SpringCloud+Nacos的微服务架构模式,其实当时也有考虑过dubbo+zookeeper;使用Dubbo+zooKeeper的话服务之间的依赖性太强,综合考虑还是选用了SpringCloud+Nacos的微服务架构,您也知道这个SpringCloud提供了很多成熟的服务组件,能为微服务架构提供一站式的解决方案, 而且调用方式使用的是更加轻量的RestFul API,服务与服务之间不存在耦合和依赖,只需要遵守规范和约定就能互相调用。并且每个微服务是使用Spring Boot来开发的,开发速度也很快,而且呢也很容易和其他技术进行整合。

2、入口层

首先是入口层,我们在这里用Nginx做了负载均衡和反向代理,对用户请求进行了转发。它的响应速度、抗并发、限流这些功能性能上都比较高,所以咱们业内用的也比较多。为了防止单点故障,我还给Nginx做了主备。像静态资源、详情页面也都是用它来做处理的,实现了资源的动静分离

3、网关层

接下来是网关层,为了保证服务的安全性,用zuul网关设置了微服务的统一请求入口,帮我们完成请求路由、校验过滤这些功能,对zuul网关也配置了集群,保证网关服务的一个高可用嘛。

4、服务层

再往下是服务层,我们是将服务统一注册到Nacos上面的, Nacos作为整个微服务的注册中心,同时也是配置中心,所有的配置都能传到Nacos上(Nacos的概念),加上个@RefreshScope注解就可以实现配置的实时刷新功能,服务之间通过Feign接口调用。因为Feign集成了Ribbon和熔断器(Hystrix),所以请求过来之后会通过Ribbon的负载均衡分配到一台机器并进行调用。也有效防止了服务扇出调用导致的雪崩效应,让系统更加稳定。由于咱们这个是分布式系统,许多依赖难免会调用失败,比如出现超时、异常这些,那熔断器就能帮我们避免因为依赖调用问题,而导致的系统级联故障,从而提高系统弹性。链路调用出现问题的话,定位起来也方便,用zipkin就行。

5、数据层

再往下就到了数据层,使用的是MySQL数据库,数据库的重要性就不用多说了,“非必要请求,肯定是不让它不走数据库的”。像一些“热数据”呢,是交给Redis处理的;对于项目中用户评论这样的数据,数据量特别大,访问也很频繁,但是结构比较松散的数据,是用MongoDB数据库存储的;搜索这方面使用的是ElasticSearch搜索引擎,提高搜索响应速度,因为ES相对于其他搜索引擎,它还有一个文件系统缓存(ES的近实时 或 文件系统缓存的作用),所以实时性比较高,ES还支持分布式,加入节点自动均衡等特点。当然,对他们也都做了集群配置,保证高可用嘛。除了借助中间件的方式,我们也对MySQL也做了主从同步,提高数据的安全性。另外,对于一些大表,比如订单表,就用mycat做了拆分,减轻了单表的压力。

6、消息队列

除此之外呢还使用了一些其他的中间件作为架构的补充,比如一些需要异步处理的场景,我是借助了RocketMQ的消息队列来处理的,对于项目中订单支付成功增加积分的业务,为了保证原子性,使用的RocketMQ的分布式事务消息来解决的。当然,对它也做了集群配置,保证它的一个高可用。

 六、项目模块-课程搜索

1、当学员点击分类查询或者是在搜索框中搜索进行展示的时候,需要到数据库进行联查多张表比如:(课程标签表...),然后再到前台进行展示;但是在数据库中进行联查多张表的话我考虑这样几个问题:

    1. 会增加数据库连接数,给数据库造成压力;
    2. 联查数据量大的话查询效率会很低;
    3. Mysql做like这样的模糊查询,效率和结果都不尽人意;

所以我就考虑使用索引库去解决这个问题

原因就在于mysql采用的是正排索引,所谓的正排索引就是我们得从头到尾把数据查询一遍,查看哪些数据里面包含我们要查询的关键字,效率非常低,其次就是mysql也没有分词,而且mysql索引存储的是字段的内容(如果字段内容过长,就只存前几位的内容为索引)

-----------------------------

2、但是倒排索引正好相反,他维护了一个字典表,key就是我们要搜索的关键词,value是包含这些关键词的数据的id,我们只要对比字典表就能知道哪几条数据有我们要查询的关键字,然后拿到这些数据的id,马上就能找到我们要找的数据了。

---------------------------------

3、当前主流的索引库有solr和elasticSearch,考虑到solr虽然搜索方面相对强大一些但是他的实时性并不高,问题就在于solr在建立索引时,搜索效率会下降,相比起来es的实时搜索效率会更高一些。毕竟es的搜索被称为近实时,而且天然支持分布式也是我们当时选择的一个原因。

----------------------------------------

4、

这里说一下ES的近实时:

Es设计的理念就是分布式搜索引擎,底层其实还是基于lucene的。核心思想就是在多台机器上启动多个es进程实例,组成了一个es集群。es中存储数据的基本单位是索引,一个索引差不多就是相当于是mysql里的一个库。index -> type -> mapping -> document -> field。

简单的说一下就是一个index包含多个type,type由多个mapping组成,type中有多条document,每条document有多个field。

接下来咱们再说一下而是其他比较好的地方:使用ES可以去实现模糊搜索、分词查询、高亮显示、聚合。

IK分词器:ES默认是自带分词的。但是ES是国外的产品,他们只对英文分词效果好,而对我们汉字它会将每一个汉字分成一个词语,很显然这是不行的。因此我们当时选择了国内比较好用的分词器IK分词器。(如果问到其他的分词器可以说:庖丁分词器,ICU 分词器。thulac分词,(清华大学分词器)

查询高亮:ES在查询的时候我们可以设置查询关键字的高亮显示,指定高亮的样式,其实也就是拼接上html的样式标签。

索引库的更新:索引库的更新我们使用的是logstash进行数据库和ES的数据同步,他的工作原理很简单,就是定时执行我们配置文件中所定义的sql语句,他需要两个插件 一个是读取msql数据的插件,一个是同步ES的插件。

七、项目模块-广告模块 

1、广告页

由于广告页访问量比较大,而且更新较少,所以我们用了redis缓存,redis 对于1000万条以内的数据性能是比较高的,并且支持持久化和冷热交互。另外它还支持集群模式、哨兵监控和主从复制,我们当时使用了集群模式。redis和spring整合后有一个模板工具叫redistemplet  通过redistemplete可以存储多种数据类型。

2、广告投放

广告的收费设计有很多种,比如说CPC(点击次数)、CPA(转化效果)、CPM(千次曝光)等收费方式,我们采用的是CPM的计费方式。

首先,我们用Quartz定时器做了一个定时任务,就是定时获取广告缓存与数据库进行同步,然后对缓存中所有广告的曝光量进行判断,如果曝光量为0则无需任何操作,如果曝光量不为0,则对该用户的账户余额进行相应扣费(10元/1000次),扣费完成后将曝光量重置为0。然后判断账户余额是否小于100,如果小于100,就将该用户正在投放的所有广告进行数据同步,然后删除广告缓存,并将广告状态修改为待投放,同时调用短信接口发送短信提醒用户余额不足。

由于首页投放的广告会出现多个用户同时点击而被扣费的情况,为了避免数据不一致,我们使用了分布式锁,目前实现分布式锁的技术有很多, 我们在项目中是使用Redis来解决的。(redis分布式锁)

3、限时秒杀

我们每天会在定时发送一定数量的优惠券用于给用户抢券,为了严格控制优惠券的发放数量, 我是借助redis的decr来做的, 首先他们是可以保证原子性的, 我们提前将优惠券数量放到redis中, 当用户点击抢券的时候发送请求到服务端, 首先进行抢券开始时间的查询,如果当前时间小于抢券开始时间,直接返回错误,防止用户拿到接口直接调用的问题,然后判断优惠券的数量,如过数量小于等于0直接返回失败,这样的话后面的大量请求无需给系统带来压力。

如果当前数量还有的话,那么会根据当前的用户id +“counts”进行redis查询,我们是在抢购成功时通过redis给当前用户做了一个标记,目的是避免一个账户重复抢券的情况,然后如果数量充足,且无重复秒杀情况,执行decr操作,decr操作的话会执行减一并且返回当前的值,然后再次进行判断当前值是否小于0,如果是则返回失败。否则调用redis通过用户id +“counts”做key,值是默认给的一个success代表抢券成功(就是刚刚所说的重复抢券问题),然后mq异步的方式给用户-优惠券表中增加数据。

4、购物车

在详情页点击“加入购物车”或者“查看购物车”的时候我们做了一个判断,假如学生没有登录,我们会将学生加入到购物车中的课程信息存入到cookie中,然后在学生点击“去结算”时提醒学生进行登陆,登录成功后将购物车中的信息和学生对应的购物车中的信息关联起来存入到redis中,如果学生已经登录,那么学生将课程加入购物车时直接将课程信息存入redis中,之所以将信息放redis中的目的就是为了防止学生频繁操作购物车,增加系统压力,而redis可以看做是一个持久化的缓存。之后学生填写完收获地址信息以及选择支付方式,点击“提交订单”时会将redis中的信息取出来插入到数据库的订单表中,并清除redis中该学生购物车的信息,同时将订单表中该条记录的状态设置为1。 接下来学生可以选择进行支付,支付成功后会将订单的状态改为2,并将支付宝返回的交易信息存入数据库的交易记录表中,同时调用阿里接口发送短信通知学生支付成功,此时的订单已进入后台去处理,前台再查看订单时,我们会根据状态显示不同的信息,比如:状态1是未支付订单,状态为2的是支付成功等待审核状态。

八、项目模块-热门推荐

1、我们首页有一个课程推荐栏,当学员第一次访问时直接推荐当前最热门,评分最高的课程,之后随着学员浏览的课程我们会直接在课程推荐中推荐学员最近经常访问的课程进行推荐.

2、具体的实现是我们定义的标签,在讲师上传课程的时候需要指定添加的标签,我们并没有直接通过课程的分类来进行推荐,因为课程分类太广泛,局限性很大,而我们使用标签可以很好的扩展课程的维度。

3、而且考虑到首页访问量巨大,课程数据很多的情况,所以我当时采用的是redis缓存技术+ES搜索引擎实现的.来提高抗并发的能力以及记录课程标签的检索速度.

4、该模块分为两种情况,第一种就是用户不登录的情况,如果用户不登录的话我们就展示本站所有课程访问量前十的课程进行展示。

4.1.首先每当用户点击课程的时候我们都会将该课程的id作为key然后判断redis中是否存在该key,如果不存在,那就score为1并存入redis的zset数据结构中。如果存在就进行incr操作。

4.2.然后我们会从redis中取出排名前十的课程ID在ES索引库中使用terms查询课程数据,并返回页面进行展示。

这里我们只是将课程ID存入Redis,并没有将整个课程数据存入,因为内存非常的珍贵,我们不可能将那么多数据存入缓存,而且拿到课程ID之后是走的ES,而没有去给数据库增加压力.

5、第二种在学员登录的情况下,当学员每点击一次课程,会先判断redis中是否存在该课程标签.如果没有就直接将学员id加浏览量标识(”-num”)作为key,课程标签作为value存入redis缓存中,并将zset的分数设为1.如果redis中已存在该课程标签,就使用increment方法将分数+1.并且同时会将学员id加时间标识(“-time”)作为key,课程标签作为value,当前时间的时间戳作为分数存入缓存.目的是为了记录各个标签的访问时间.

6、此外我们使用quartz定时器每隔一段时间就查询这两个zset.将访问时间最近的三个标签删掉,目的是排除学员误触的情况.然后将访问量前三的课程标签以zset类型存入定向推荐缓存中.分数分别设为3,2,1.然后再获取学员最近访问的三个标签,也放入该缓存中,分数统一设为3,如果标签已存在直接使用incr+3.(这个缓存用来做定向推荐的,每一小时更新一次).

7、当学员再次访问首页时获取用来定向推荐的缓存中的标签,然后进行多字段查询elasticsearch拿到课程然后在课程推荐中展示。

猜你喜欢

转载自blog.csdn.net/weixin_45934981/article/details/130847565