1.核心功能:- 发帖、评论、私信、转发;- 点赞、关注、通知、搜索;- 权限、统计、调度、监控;
2.核心技术:
- Spring Boot、SSM
- Redis、Kafka、ElasticSearch
- Spring Security、Quatz、Caffeine
3.项目亮点:
- 项目构建在Spring Boot+SSM框架之上,并统一的进行了状态管理、事务管理、异常处理;
- 利用Redis实现了点赞和关注功能,单机可达5000TPS;
- 利用Kafka实现了异步的站内通知,单机可达7000TPS;
- 利用ElasticSearch实现了全文搜索功能,可准确匹配搜索结果,并高亮显示关键词;
- 利用Caffeine+Redis实现了两级缓存,并优化了热门帖子的访问,单机可达8000QPS。
- 利用Spring Security实现了权限控制,实现了多重角色、URL级别的权限管理;
- 利用HyperLogLog、Bitmap分别实现了UV(访问次数)、DAU(日活跃用户数量)的统计功能,100万用户数据只需43.5M内存空间;
- 利用Quartz实现了任务调度功能,并实现了定时计算帖子分数、定时清理垃圾文件等功能;
- 利用Actuator对应用的Bean、缓存、日志、路径等多个维度进行了监控,并通过自定义的端点对数据库连接进行了监控。
image.png
1、登录用微信或者QQ登录的方式,说一下有几次交互过程?
2、怎么同时多篇文章的提交,多个评论的产生,如何解决高并发问题。
3、项目中的xx技术栈的作用是什么?当时为何没有考虑其他技术栈呢?
4、对于帖子中的敏感词、评论区的敏感词是如何处理的?
5、关注、点赞和收藏是否会提醒?如何做到的呢?用了什么技术栈?
6、ES的功能是什么?如何解决ES和数据库的同步功能?
7、帖子是否有置顶、加精和删除的功能?置顶是如何实现的?
8、是否有热榜排序功能?使用的是Redis那个数据结构?
9、是否做过测试,同时支持多少人发帖?
10、对于同名的文章怎么处理?会检测恶意刷帖吗?

三层架构:

  • 表示层:主要对用户的请求接受,以及数据的返回,为客户端提供应用程序的访问。
  • 业务逻辑层:主要负责对数据层的操作。也就是说把一些数据层的操作进行组合。
  • 数据访问层:主要看数据层里面有没有包含逻辑处理,实际上它的各个函数主要完成各个对数据文件的操作。而不必管其他操作。

    项目主体

    请简要介绍一下你的项目?

    这个项目的整体结构来源于牛客网,主要使用了Springboot、Mybatis、MySQL、Redis、Kafka、ES等工具。主要实现了用户的注册、登录、发帖、点赞、系统通知、按热度排序、搜索等功能。另外引入了redis数据库来提升网站的整体性能,实现了用户凭证的存取、点赞关注的功能。基于 Kafka 实现了系统通知:当用户获得点赞、评论后得到通知。利用定时任务定期计算帖子的分数,并在页面上展现热帖排行榜。

    项目中注册是怎么实现的?

    用户注册,会输入用户名、密码和邮箱,密码需要加盐加密再存入数据库:
    项目 - 图2
    另外,默认未激活的用户状态 status=0 也会存入数据库,当然,我们会为该注册用户随机生成一个唯一的激活码一并存入数据库:
    项目 - 图3
    我们使用 Spring Mail 给这个用户的邮箱发送激活邮件,这个激活邮件中就包含该用户的激活链接:
    项目 - 图4
    另外,用户注册的时候会为该用户生成一个随机头像,这个头像的地址会被存入 user 表。这个随机头像的实现其实很简单,用的是牛客的一个头像库,包含了 1000 张头像图片
    项目 - 图5

    项目中登陆和授权的功能是怎么做的?

    使用Kaptcha包,可随机生成字符和图片。
    登陆的流程:

  • 用户登录 —> 生成登录凭证LoginTicket存入 Redis,Cookie 中存一份 key

  • 每次执行请求都会通过 Cookie 去 Redis 中查询该用户的登陆凭证是否过期和是否有效。点击记住我可以延长登录凭证的过期时间,用户退出则其登录凭证变为无效状态
  • 根据这个登录凭证对应的用户 id,去数据库中查询这个用户信息
  • 使用 ThreadLocal 在本次请求中一直持有这个用户信息
  • 优化点:每次请求前都需要去数据库查询这个用户信息,访问频率比较高,所以我们考虑把登录成功的用户信息在 Redis 中保存一会,拦截器每次查询前先去 Redis 中查询,然后缓存和数据库的一致性问题的话,使用的是旁路缓存模式,也就是先更新数据库,然后直接删除缓存中的数据。

    项目中账号设置功能是怎么做的?头像上传呢?

    上传文件:
    -请求:必须是POST请求
    -表单:enctype=”multipart/form-data”
    -Spring MVC:通过MultipartFile处理上传文件
    通过定义MultipartFile类headerImage,调用transferTo()方法存到指定路径
    image.png
    注意,浏览器在响应图片的时候,用到了流传输。
    image.png

    项目中如何实现登陆状态检查?

    image.png
    使用拦截器来实现登陆状态检查的功能。
    本项目中,每次请求都会检查request中的login_ticket,判断其是否有效以及是否过期,然后把找到的user信息存放在ThreadLocal中,并在完成处理后,自动释放。(方便的进行用户信息取用)

    项目中如何保持用户的登陆状态?

    采用ThreadLocal持有用户信息。
    由于 HTTP 协议是无状态的,完成操作关闭浏览器后,客户端和服务端的连接就断开了,所以我们必须要有一种机制来保证客户端和服务端之间会话的连续性。
    采用Cookie + Session:
    按照 Cookie + Seesion 的机制,服务端在接到客户端请求的时候,只要去 Cookie 中获取到 sessionID 就能据此拿到 Session 了。Session 存活期间,我们认为客户端一直处于活跃状态(用户处于登录态),一旦 Session 超期过时,那么就可以认为客户端已经停止和服务器进行交互了(用户退出登录)。
    如果遇到禁用 Cookie 的情况,一般的做法就是把这个 sessionID 放到 URL 参数中。这也是经常在面试中会被问到的问题。
    采用ThreadLocal:
    使用 Session 机制,会使得服务器集群很难扩展,无法保证每台服务器session数据的共享。
    简单来说,我们把用户数据存入 ThreadLocal 里,这样,只要本次请求未处理完,这个线程就一直还在,当前用户数据就一直被持有,当服务器对本次请求做出响应后,这个线程就会被销毁。
    ThreadLocal底层是通过每个线程中的ThreadLocalMap来维护的。
    那同一个用户发出的两次请求可能被不同的两个线程进行处理,如何使得这个两个线程的 ThreadLocal 持有相同的用户信息呢?
    过滤器。
    具体来说,我们定义一个过滤器,在每次请求前都对用户进行判断(为了避免每次请求都经过过滤器,可以将登录成功的用户信息暂时存储到 Redis 中),然后将已经登录成功的用户信息存到 ThreadLocal 里,从而使得该线程在本次请求中持有该用户信息。

    项目中的发布帖子/发送私信是怎么做的?

    采用Ajax,浏览器提供的 XMLHttpRequest 对象。这个对象为向服务器发送请求和解析服务器返回的响应提供了流畅的接口,使得浏览器可以发出 HTTP 请求与接收 HTTP 响应,实现在页面不刷新(局部刷新)的情况下和服务端进行数据交互

    项目中发布评论是怎么做的?

    项目中,发布评论用到了事务。发布评论需要增加评论数据和修改帖子的评论数量,则需要修改comment表和discuss_post表相应的字段。需要执行两次DML语句,所以要保证原子性。发布评论这块要引入事务管理。
    spring支持两种方式管理事务:
  1. 基于 TransactionTemplate 的编程式事务,实际中很少使用
  2. 基于 xml 配置或者注解 @Transactional 的声明式事务。

声明式事务管理实际是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
另外,我们还可以通过 @Transactional 注解的 isolation 参数配置隔离级别、以及通过 propagation 参数配置传播行为

  1. @Transactional(isolation = Isolation.READ_COMMITTED,
  2. propagation = Propagation.REQUIRED)

项目中过滤敏感词是怎么实现的?

敏感词过滤是采用了一种前缀树的数据结构。
Trie数的原理就是通过公共前缀来提高字符串的匹配效率。

Redis

项目中哪里用到了Redis

  1. 使用Redis存储验证码

image.png
因为验证码需要频繁的进行访问与刷新,因此对性能的要求较高;
验证码不需要永久保存,通常在很短的时间后就会失效;
分布式部署的时候,存在session共享的问题。
采用字符串key-Value结构。
2. 使用redis存储实体或用户获得的赞
注意:存储的赞有实体和用户两种。image.png
image.png
点赞使用set类型存储,key为点赞对象,set中保存点赞人的ID
3. 使用Redis存储关注和被关注目标
image.png
image.png
关注使用zSet类型存储,key为被关注者,set保存关注者以及关注时间为score
4. 使用redis存储登录凭证:
因为后台在每次处理请求的时候都要查询用户的登录凭证,访问的频率非常高,因此需要使用redis存储。
image.png
采用Set数据结构。
5. 使用redis缓存用户信息:
因为后台在每次处理请求的时候都要根据用户的凭证用户信息,访问的频率非常高。
image.png
缓存用户数据使用Value类型,key为用userID得到的key,value为user对象(设置过期时间,且数据修改时需要清除缓存)

  1. 统计网站的UV和DAU

image.png
image.png
HyperLogLog:超级日志,统计独立整数个数。统计UV(独立访问)时,以日期为 rediskey ,将客户端IP addHyperLogLog中(redisTemplate.opsForHyperLogLog().add(redisKey, i);
统计指定日期的UV,则需要调用union()方法,将所有日期keyList加入到redisKey中,统计新redisKey的size。
image.png
Bitmap:位图,比如365天的签到,只需要365/8个字节的大小。统计DAU(日活跃用户)时,以日期为 rediskey ,以用户ID作为位(在数据中的位置),用 or 操作,可以方便的统计一段时间内的注册用户访问人数。
7. Redis可以使用zset对需要排序的数据进行自定义的排序。

Redis存取的登陆凭证有效时间是多少?

image.png

怎么往Spring框架中配置Redis,介绍常见的Redis操作?

如何配置:
1,导入jar包
2,配置端口,以及配置类redisTemplate(注入连接工厂/设置序列化方式(json))
常见操作
Value类型:redisTemplate.opsForValue().set(redisKey, 1),redisTemplate.opsForValue().get(redisKey), redisTemplate.opsForValue().increment(redisKey),
Hash类型:redisTemplate.opsForHash().put(redisKey, “id”, 1), 还有get等操作
List类型:redisTemplate.opsForList().leftPush(redisKey, 101), 还有size, index, range, leftPop等操作
Set类型:add, size, pop, members等操作
Zset类型:redisTemplate.opsForZSet().add(redisKey, “Linda”, 92), 有socre,rank,reverseRank, range等操作
操作key:可以delete,以及设置过期时间
同时支持绑定操作,支持事务(编程式事务,在事务中一般不包含查询)
为什么不包含查询:redis事务就是一系列命令的批量操作,批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

Kafka

什么是消息队列

消息队列是一个存放消息的容器,生产者把消息放在队列中,消费者从消息队列中取出数据。消息队列的主要功能(优点)在于:
解耦:生产者只负责把消息放在队列中,而不用关心谁去使用它。
异步:生产者把消息放在队列中后即可返回,而不用一个个的通知消费者去执行,消费者是异步的获取消息的。
限流:生产者一次性产生大量的数据时,不会给消费者造成压力,消费者可以根据自身的能力,去消息队列中取数据。

消息队列作为信息传递的中间件,需要注意哪些问题?

1、高可用:因为消息队列如果宕机,会导致整个系统不可用。(分布式/集群的现成支持)
2、数据持久化:防止数据丢失
3、如何取数据:消息队列主动通知或者消费者轮询。

Java中的blockingqueue,可以提供线程间的消息队列

BQ也是生产者与消费者模式,属于点对点式消息队列?(一个消息只会被消费一次)Blocking Queue构建了一个桥梁,能够解决生产速度/消费速度不匹配问题。阻塞的时候只是在那里等着,但是不会占用CPU资源,对性能不会有影响。

什么是kafka?

Kafka为分布式流处理平台。流处理是指对不断产生的动态数据流实时处理,基于分布式内存,具有数据处理快速,高效,低延迟的特性。
Kafka简介:Kafka是一种消息队列,主要用来处理大量数据状态下的消息队列,一般用来做日志的处理,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景
特点:

  • 高吞吐量,低延迟:Kafka每秒可以处理几十万条消息,他的延迟最低只有几毫秒,每个topic可以分为多个partition,consumer group对partition进行consume操作。
  • 可扩展性:kafka集群支持热扩展
  • 持久性、可靠性: 消息被持久化到本地硬盘,并且支持数据备份防止数据丢失
  • 容错性: 允许集群中节点失败(若副本数量为n,则允许n - 1 个节点失败)
  • 高并发: 支持数千个客户端同时读写、

Kafka主要提供的功能包括:消息系统,日志收集,用户行为跟踪,流式数据处理。

Producer:消息生产者,向Kafka中发布消息的角色。
Consumer:消息消费者,即从Kafka中拉取消息消费的客户端。
Consumer Group:消费者组,消费者组则是一组中存在多个消费者,消费者消费Broker中当前Topic的不同分区中的消息,消费者组之间互不影响,所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。某一个分区中的消息只能够一个消费者组中的一个消费者所消费
Broker:经纪人,一台Kafka服务器就是一个Broker,一个集群由多个Broker组成,一个Broker可以容纳多个Topic。
Topic:主题,可以理解为一个队列,生产者和消费者都是面向一个Topic
Partition:分区,为了实现扩展性,一个非常大的Topic可以分布到多个Broker上,一个Topic可以分为多个Partition,每个Partition是一个有序的队列(分区有序,不能保证全局有序)
Replica:副本Replication,为保证集群中某个节点发生故障,节点上的Partition数据不丢失,Kafka可以正常的工作,Kafka提供了副本机制,一个Topic的每个分区有若干个副本,一个Leader和多个Follower
Leader:每个分区多个副本的主角色,生产者发送数据的对象,以及消费者消费数据的对象都是Leader。
Follower:每个分区多个副本的从角色,实时的从Leader中同步数据,保持和Leader数据的同步,Leader发生故障的时候,某个Follower会成为新的Leader。

在项目哪里用到了Kafka?

当有点赞,评论,关注请求时,会发送系统通知点赞,评论,关注的对象。在处理系统信息时,使用到了Kafka,具体来说,先定义了生产者类和消费者类,其中生产者被点赞/评论/关注功能对应的Controller使用,产生消息。而消费者负责消息(message)到来时,把消息存到数据库内。

Kafka的消息模型,以及常见术语

消息模型:发布-订阅模型,消费者订阅了某一主题(topic)后,生产者采用类似广播的方式,将消息通过主题传递给所有的订阅者。
Topic:主题,类似于文件夹,用来存放不同的数据。
Partition:主题分区,同一主题的不同分区可以存放在不同的Broker上面,保证并发能力和负载均衡。
Offset:消息在Partition中的存放位置。
Broker:可以理解为kafka集群里面的一台或多台服务器,它本身是没有复制的,上面可能运行着topic1的leader, topic2的follower等等。

ES

端口是多少?

9300 TCP协议
9200 HTTP协议
image.png

什么是ElasticSearch,存储原理,功能,特点

概念:
-一个分布式的、Restful风格的搜索引擎
-支持对各种类型的数据检索
-搜索速度快,可以提供实时的搜索服务
-便于水平扩展,每秒可以处理PB级海量数据。
术语
-索引、类型、文档、字段

上述概念与MySQL中的同步 索引:相当于database 类型:相当于table 文档:相当于一行数据,格式是JSON 字段:相当于一列,JSON中的属性 **注意,6.0以后类型逐渐废弃

-集群、节点、分片、副本

分片:将索引分成不同的片,提高并发度 副本:对分片的备份,提高可用性

项目中哪里使用到了ES,如何使用

  1. ES在使用之前需要安装一个中文分词插件ik。
  2. 引入依赖

-spring-boot-starter-data-elasticsearch
配置Elasticsearch
-cluster-name、cluster-node
Spring Data Elasticsearch
-ElasticsearchTemplate
-ElasticsearchRepository
在进行帖子搜索时,使用到了ES。可用RepositoryTemplate两种方式,由于Repository搜索到的结果(直接返回的post类,方便)没有高亮标签(why),所以使用了template方式重写了mapResults函数,获得了带有高亮标签的post。
使用消息队列(kafka)的方式,实现发帖/删帖后ES数据库的自动更新。
搜索:定义SearchQuery,确定搜素内容,排序方式,高亮等。接着使用elasticTemplate.queryForPage方法,需要重写mapResults函数,得到高亮数据。 高亮数据就是在检测出的词条前后加一个<em>标签。
image.png
image.png

拦截器

拦截器的作用

目的:让未登录用户不能访问某些页面
原理:在方法前标注自定义注解,拦截所有的请求,只处理带有该注解的方法。

什么是Interceptor,在项目的哪里使用到了Interceptor?

Interceptor是SpringMVC的处理器(handler)拦截器,用于对处理器进行预处理和后处理。本项目中,每次请求都会检查request中的login_ticket,把找到的user信息存放在协程中,并在完成处理后,自动释放。(方便的进行用户信息取用)

日志和异常

是怎样实现统一捕获异常的?

在SpringBoot的项目某一路径下,加上对应的错误页面,发生错误时自动会跳转。服务器的三层结构中,错误会层层向上传递,所以只需要在表现层(controller)统一处理错误即可。
方法:在controller中加上advice包,并通过注解@ControllerAdvice和@ExceptionHandler,统一捕获异常。

是怎样实现统一记录日志的?

使用了AOP技术(面向切面编程),这里使用到的是SpringAOP。 AOP技术能够将哪些与业务,但是为业务模块共同调用的逻辑或责任(比如事务处理,日志记录,权限控制等),封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的扩展性和维护性。 SpringAOP本质上基于动态代理,当要代理的对象实现了某接口,会使用JDK动态代理,在运行时通过创建接口的代理实例,织入代码。当要代理的对象没有实现接口,则使用Cglib技术(编译时增强),通过子类代理织入代码。

Spring

什么是Spring框架?

有很多模块组成,利用这些模块可以方便开发工作。这些模块是:核心容器(spring core)/数据访问和集成(Spring JDBC)/Web(Spring Web/MVC)/AOP(Spring Aop)/消息模块/测试模块(Spring Test)等。

对Spring IoC的理解

IoC的意思是控制反转,是一种设计思想,把需要在程序中手动创建对象的控制权交给了Spring框架。IoC的载体是IoC容器,本质是一个工厂,数据结构上来看是一个Map,用来存放着各种对象。当我们创建一个对象时,只需要配置好配置文件/注解,而不用担心对象是怎么被创建出来的。

什么是DAO?

data access object,存放数据库访问对象。

Spring MVC是什么,是怎样的工作流程?

服务器分为表现层/业务层/数据层,其中Spring MVC是工作在表现层,作用是接收/解析用户发送的请求,调用对应的业务类,根据业务类返回的结果(ModelAndView),调用view进行视图渲染,并将渲染后的View返回给请求者。具体分为以下8步。
1.客户端(浏览器)发送请求给前端处理器(DispatcherServlet)(发送请求,响应结果);
2.DispatcherServlet根据请求信息调用HandlerMapping,查找到对应的Handler;
3.查找到对应的Handler(也就是Controller)后,由HandlerAdapter适配器处理;
4.HandlerAdapter根据Handler来调用真正的Controller;
5.Controller进行业务处理,返回ModelAndView对象,Model是数据对象,View是逻辑上的View;
6.ViewResolver根据逻辑view找到实际view;
7.DispatcherServlet把Model传给view进行视图渲染,然后返回给请求者。
C - Controller:控制器。接受用户请求,调用 Model 处理,然后选择合适的View给客户。
M - Model:模型。业务处理模型,接受Controller的调遣,处理业务,处理数据。
V - View:视图。返回给客户看的结果。

DispatcherServlet处理流程?

在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作。DispatcherServlet 是 SpringMVC统一的入口,所有的请求都通过它。DispatcherServlet 是前端控制器,配置在web.xml文件中,Servlet依自已定义的具体规则拦截匹配的请求,分发到目标Controller来处理。 初始化 DispatcherServlet时,该框架在web应用程序WEB-INF目录中寻找一个名为[servlet-名称]-servlet.xml的文件,并在那里定义相关的Beans,重写在全局中定义的任何Beans。在看DispatcherServlet 类之前,我们先来看一下请求处理的大致流程:
-1Tomcat 启动,对 DispatcherServlet 进行实例化,然后调用它的 init() 方法进行初始化,在这个初始化过程中完成了:对 web.xml 中初始化参数的加载;建立 WebApplicationContext(SpringMVC的IOC容器);进行组件的初始化;
-2客户端发出请求,由 Tomcat 接收到这个请求,如果匹配 DispatcherServlet 在 web.xml中配置的映射路径,Tomcat 就将请求转交给 DispatcherServlet 处理;
-3DispatcherServlet 从容器中取出所有 HandlerMapping 实例(每个实例对应一个 HandlerMapping接口的实现类)并遍历,每个 HandlerMapping 会根据请求信息,通过自己实现类中的方式去找到处理该请求的 Handler(执行程序,如Controller中的方法),并且将这个 Handler 与一堆 HandlerInterceptor (拦截器)封装成一个 HandlerExecutionChain 对象,一旦有一个 HandlerMapping 可以找到 Handler则退出循环;
-4DispatcherServlet 取出 HandlerAdapter 组件,根据已经找到的 Handler,再从所有HandlerAdapter 中找到可以处理该 Handler 的 HandlerAdapter 对象;
执行 HandlerExecutionChain 中所有拦截器的 preHandler() 方法,然后再利用
-5HandlerAdapter 执行 Handler ,执行完成得到 ModelAndView,再依次调用拦截器的
postHandler() 方法;
-6利用 ViewResolver 将 ModelAndView 或是 Exception(可解析成 ModelAndView)解析成View,然后 View 会调用 render() 方法再根据 ModelAndView 中的数据渲染出页面;
-7最后再依次调用拦截器的 afterCompletion() 方法,这一次请求就结束了。

项目中使用到了SpringSecurity在哪些地方?

重构了用户权限控制(之前用的拦截器)

什么是Quartz,特点,专业术语,项目应用

概念:quartz是一个开源项目,完全基于java实现。是一个优秀的开源调度框架。
特点:
1,强大的调度功能,例如支持丰富多样的调度方法
2,灵活的应用方式,例如支持任务和调度的多种组合方式
3,分布式和集群能力
专业术语:
scheduler:任务调度器 , scheduler是一个计划调度器容器,容器里面有众多的JobDetail和trigger,当容器启动后,里面的每个JobDetail都会根据trigger按部就班自动去执行
trigger:触发器,用于定义任务调度时间规则
job:任务,即被调度的任务, 主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job
misfire:本来应该被执行但实际没有被执行的任务调度
项目应用:定时的统计帖子分数(如何设置定时任务和trigger)

什么是Caffeine,如何缓存,项目应用

概念:Caffeine 是一个基于Java 8的高性能本地缓存框架
初始化cache:缓存保存的对象,使用Caffeine.newBuilder()创建,创建时设置缓存大小,过期时间,缓存未命中时的加载方式。
为什么只缓存热度帖子?答:因为不会经常变。