admin管理员组

文章数量:1570421

文章目录

  • 前言
  • 一、论坛项目经典话术
  • 二、请你介绍一下你最近的项目吧
    • 2.1 话术1
  • 三、你的公司的开发环境是怎么搭建的?
  • 四、登录你们是怎么做的?
    • 4.1 账号密码登录
    • 4.2 手机验证码发送
      • 4.2.1 手机验证码发送
      • 4.2.2 手机验证码登录
  • 五、用户行为限流是怎么做的?
  • 六、续约Token怎么设计的?
  • 七、前后端交互是怎么配合的
  • 八、你们前端是怎么部署的?
  • 九、文章查询流程简单讲一下?
  • 十、ThreadLocal在项目中有用到过吗?
  • 十一、文章发布流程
  • 十二、文章审核流程
    • OCR识别
    • DFA算法
    • XXL-JOB
    • 微服务调用
    • 人工审核流程一直不结束,难道要一直等着吗?
    • ID生成策略怎么选择的(技术亮点:雪花算法)
  • 十三、自媒体文章上下架实现流程讲一下(适用于Kafka在项目中哪里用到过)
  • 十四、文章搜索流程详细讲一下(同样适用于:ES在项目中哪里用到过)
  • 十五、文章搜索历史是怎么实现的(MongoDB)
    • 保存搜索记录-实现思路
  • 十六、联想词功能你们是怎么设计的
  • 十七、用户行为数据你们是怎么采集的
  • 十八、文章热度你们是怎么计算的(技术亮点:XXL-JOB、Redis、Kafka Stream)
  • 十九、你们项目中的持续集成是怎么实现的
  • 二十、你们项目是如何部署的
  • 二十一、项目部署后如何监控微服务的日志?
  • 二十二、点赞、阅读、不喜欢功能如何实现?
  • 二十三、文章评论如何实现?(MongoDB)
  • 二十四、用户评论时携带的IP是怎么查询的?
  • 二十五、实名认证是怎么做的?
  • 二十六、自媒体图文数据统计怎么做的?
  • 二十七、头条项目功能盘点?
  • 二十八、头条项目简历编写?


前言

自己学过的项目如何梳理、将每一个项目的核心业务梳理出来,整理成话术。以及发现项目汇总的技术亮点,详细讲解技术亮点的实现思路。


一、论坛项目经典话术

话术:将整个项目的各种业务、技术亮点,进行抽取,编成大白话,覆盖所有的核心业务、各种技术解决方案。真正做到,就算照着念,都可以给面试官讲明白。
解决面试问题:

  • 你介绍一下你最近/最熟悉的一个项目经验吧
  • XXX技术在这个项目中用到过是吧?
  • XX业务遇到XX问题,你们是怎么解决的?
    针对自己的项目经验

二、请你介绍一下你最近的项目吧

注意:一定要追求真实,一定要给一个真实背景。
项目三要素:项目介绍、岗位职责、业绩、技术亮点

2.1 话术1

我最近做的这个项目是融媒体项目,准确来讲是一个融媒体平台,项目是我们公司自研的,核心业务就是基于我们公司自好的面试官,有的媒体资源、社群、自媒体资源进行整合。基于我们研发的融媒体平台,构建了多款APP,分别针对不同的客户群体。公司业务规划走的就是融媒体矩阵,每个App项目分别针对不同的类目进行打造,各自做专业垂直类目的AP。说白了就是前端换了个皮肤,垂直类目有母婴、健康、旅游、以及各省市区域进行打造,只要运营需要,随时可以生产出很多APP。

项目的核心就是通过矩阵玩法,聚集不同类目下的用户,每一个项目都是一个垂直类目,吸引不同兴趣爱好的用户,说白了就是养一波铁粉,然后为后期打造私域营销、垂直类目精准客群推荐打好基础,后期公司计划就是走这个广告和垂直类目电商进行营利。

还有就是项目架构,我们首先是将整体系统拆分成多个分布式子系统,比如大数据计算和推荐、平台运营管理系统、还有就是我参与的这个融媒体平台项目。

我主要是负责融媒体平台端的一些功能开发,后台架构是用的微服务,便于业务的扩展和将来部署时动态的管理服务资源。

主要服务的话就是个人中心、文章服务、行为数据服务、任务调度微服务、评论服务、检索服务、自媒体管理服务、平台管理服务、图片管理服务、统一认证服务这些基本业务服务,大概有十几个,大部分服务模块相关的核心业务开发我都参与过,我们公司开发模式的话没有严格的划分具体负责哪些模块,都是根据月度下发的任务进行分配的。

然后业务层,就是每一个微服务的话,我们是用的SpringBoot、RabbitMQ、Elasticsearch这些技术MybatisPlus、Redis、MySOL、还是比较主流的;

我在项目中的话,主要是负责iava后端开发,算是我们小组的主力吧,项目也是做了有一年了,目前已经上线几个APR了主要独立负责的一些核心业务的话就是有用户推荐服务|ava端、个人中心、文章服务、任务调度微服务、评论服务、检索服务、统一认证服务这几个核心模块业务的设计与编码

这个项目我从项目立项那会就参与了,我刚进公司的时候这个项目刚开始做,所以这类型业务也是非常熟悉,当然我主要是做后端开发。

大概就是这个情况,面试官。


三、你的公司的开发环境是怎么搭建的?

我们公司的项目环境有开发环境、测试环境和线上生产环境,测试环境和生产环境是运维搭建的。

然后开发环境是我(如果你包装3年以下就说你组长搭建)搭建的,我们公司内部有专门的开发机,就是一台高性能的服务器,然后我在上面使用 Docker 部署了一些项目开发环境用的东西,比如:Redis、MySQL、Nacos Server端、Sentinel Server端、SkyWalking Server端,还包括一些其他中间件,Elasticsearch、RabbitMQ、XXL-JOB 这些中间件,还有ELK日志平台,这些都是我部署的。

因为开发环境的这个服务器性能足够满足我们日常开发使用,所以就部署在一台服务器上。主要是能够快速的重置、重启就可以了。

至于公司内部项目管理工具、GitLab、公司内部知识库这些不是我搭建的,因为这些都是公司平台自有的。

开发的时候,我们就是连接公司内网的开发机,所有的连接方式、账号信息,我都是写好文档,在公司内部的 wiki 平台上发布了,用的时候,其他同事去参考就行了。如果回家后需要加班的话,我们就配上 VPN 连接公司内部服务器就行了。


四、登录你们是怎么做的?

平台方、媒体端、用户端登录方式是不一样的,平台端和媒体端是支持账号密码、手机验证码登录。
用户端是支持账号密码、手机验证码、微信登录、微博登录多种登录方式。
平台端和媒体端是必须登录后才能使用。
用户端的话,可以匿名访问,但如果要点赞、评论、收藏或者查看个人中心等行为的话,还是需要登录的,App 页面会自动跳出来登录界面。关于登录这块都是我做的,我给您挨个说一下吧

4.1 账号密码登录

我们首先设计了用户表,用户表中包含了用户的账号、密码、手机号、头像、注册时间、是否身份认证这些字段。

当前端提交请求时,请求体中以 JSON 形式提交账号、密码、验证码这几个参数。请求先到后端 gateway 网关,网关处判断登录请求时不需要校验 Token 的,所以直接放行请求被转发到 user 用户服务这里,流程也很简单,先针对参数进行校验,非空、是否合法如果参数有问题,则直接返回登录失败信息。如果参数没问题,就判断一下账号密码是否频繁登录,这里我是借助redis 的 zset数据结构设计了一个时间窗口限流算法实现的。

如果没有限流,就查询用户数据,用户是否存在,如果不存在就返回登录失败。如果存在,就校验密码,我们是使用加随机盐的一个工具类 BCrypt 实现的,它的安全度更高。如果密码校验通过,就封装用户数据,比如用户名、用户头像,封装到JWT Token 的载荷中,返回给前端就可以了。
以后前端就带着这个 Token 访问其他资源。当然了,我在网关处,针对 Token 进行校验,比如访问受限资源,需要判断是否有 Token,判断 Token 是否有效,如果 Token 没问题,就将请求放给后面的微服务。

4.2 手机验证码发送

这个流程主要涉及的到3个接口,分别是:

  • 手机验证码发送接口,在common通用服务中
  • 滑块验证码接口,在common通用服务中
  • 手机验证码登录流程,在user通用服务中

4.2.1 手机验证码发送

首先用户在页面填写手机号码,前端校验手机号没有问题,就会自动向后端【发送登录短信)接口发起请求。

我们是设计了一个 common 服务,就是封装了一些通用功能的服务,将发短信、OSS 对象存储这些通用功能都放到这个通用服务中实现。

限流的条件是满足 5 分钟内发生3次发短信行为,就判定进行限流。如果没有达到限流阈值,我们直接调用三方的短信服务,发送短信。短信发送成功后,我就把短信验证码,缓存到 Redis 中,过期时间 TTL 设置为默认 5 分钟当然这些时间配置什么的,我们都使用统一的配置文件管理了,可以动态修改的。缓存在 Redis 中的 key 的格式为:USER:LOGIN:手机号值就是验证码的随机值。最后响应前端短信发送成功。

另外还有一个点就是限流的细节,我给您讲一下,我们分两级验证码限流的:第一级:用户5分钟内连续发3次短信,达到值后,使用滑块验证码限流,查询请求参数中是否携带滑块验证码参数,如果有,就查询redis 中的验证码,比对前端提交的这个参数是否正确,如果正确,就直接放过去。

第二级:用户 10 分钟内,连续发8次短信,达到值后,直接限流用户 10分钟后重试实现方式就是将用户拉黑,把用户的手机号设计为 key,比如:BLACK:LOGIN:手机号TTL 过期时间设置为 10 分钟,这样用户下次请求时,直接判断这个手机号是否在黑名单如果在的话直接就拒绝了。10 分钟后自然会过期,就又恢复正常发送短信了

4.2.2 手机验证码登录

首先是前端提交登录登录表单,包含手机号、验证码。

后端接收到请求后,先校验一下手机号是否合法。

然后根据手机号,查询 Redis 中缓存的真实验证码,查询完毕后销毁掉 redis 中的验证码.比对验证码是否正确,如果验证码不存在,或者验证码错误,那就直接返回前端失败信息。如果验证码正确,就根据手机号查询用户表中用户数据,如果用户存在,就以JWT格式封装用户 Token,返回给前端。

如果用户不存在,也没关系,直接默认自动注册,写入用户的手机号,关于用户名和头像用默认值代替。
用户自己可以在个人中心中,修改用户名、头像、密码等数据。


五、用户行为限流是怎么做的?

有的,这个必须做限流,我是利用redis 的 zset 结合时间窗口限流算法进行实现的。我们是这样考虑的,用户可能有很多行为,是无意义,或者非法的,比如:频繁发送短信、频繁修改个人信息、频繁的点赞、评论等等行为,都应该进行限流。所以我就针对这些频繁的行为进行限流,设计了一个通用的限流接口。思路上是使用时间窗口限流算法,具体实现我利用 redis 的 zset 进行实现。比如说,用户5分钟内只能发送3个验证码,或者 10分钟内只能发送8个验证码,于是我就将用户发短信的行为设计为 key,格式为【场景:行为:用户唯一标识(可以是手机号、用户名)】,score 分数值是时间戳,value值也是时间戳。


限流算法思路:

具体流程:

  • 当用户每次发生限流行为,都会记录这个行为,以Redis zset的方式进行记录。
  • 在业务处理流程中,使用java api进行查询判断,其实本质就是调用redis 的zcount命令,这个命令可以传入起始分值和结束分值。我就把当前时间戳作为结束分值,然后当前时间戳减去限流时间,比如说5分钟的毫秒值,求出来5分钟前的时间戳。于是根据这两个时间戳作为分值,范围查询 zset中出现的次数,就得到用户在5分钟内,这个行为一共触发了几次。
  • 后续的业务,就是不同场景中,根据不同的需求,进行校验就行了,比如说5分钟限流3次,10 分钟限流8次,这就是后面的业务代码考虑的事情了。

六、续约Token怎么设计的?

这个续约 token 在我们的项目中有设计过,恰好也是我做的。当时产品经理给到的需求是,要求用户可以保持一个长期登录或者自动登录的效果。避免用户状态频繁过期,频繁登录给用户带来不便我当时就使用双 Token 的方式进行设计的,这种方案提出来后是经过组长评估的,他也认为没问题,于是我就这么做了。

我给您说下我的大概实现思路吧。

首先是登录,在登录的时候,无论何种方式认证,最后都是返回Token 给到前端。在返回 Token 的时候,是生成两个 Token:

  • 一个是 AccessToken,我管他叫访问令牌,我处于安全考虑,比如防止令牌被恶意使用设置他的有效期为 3个小时,每次请求资源时携带这个令牌;
  • 另一个是 RefreshToken,我管他叫刷新令牌,这个令牌不能用来访问资源,只能用来刷新访问令牌,就是每当访问令牌过期,前端携带这个RefreshToken 获取新的 AccessToken这个刷新 Token 的有效期我设置为7天,当然这个可以改,这是写在配置文件中的;


当 Token 返回给前端后,浏览器端用的是localStorage 保存的,App 端的话有他们自己的地保存方式,将这两个 Token 保存下来。

然后就是访问资源的时候,我们在网关处会进行校验,如果访问的是受限资源,那么网关写了一个全局过滤器,校验是否携带 AccessToken,以及这个 Token 是否过期如果正常,则直接放行。

如果校验异常,可能是 Token 过期,也可能是 Token 数据被篡改或损坏,于是返回拒绝。前端判断拒绝的状态码为 AccessToken 无效后,会重新发起一次请求,携带 RefreshToken 重新请求续约接口,这个续约接口是不需要网关拦截的,然后续约接口针对 RefreshToken 进行解密后,校验签名没有问题,没有被篡改,于是重新颁发新的AccessToken,返回给前端。前端重新携带 AccessToken 发起请求就行了这里面有个重要的地方就是,我针对令牌使用 RSA非对称加密进行加密,目的就是防止被篡改数据。

这就是我设计的实现方案。

七、前后端交互是怎么配合的

我们是使用 Swagger、Knife4j快速生成后端的接口文档,然后给到前端,和前端进行配合的。

这个 Knife4j就是一个整合了 Swagger和 OpenApi 的工具。开发接口的时候,我们是使用 Swagger 提供了一波注解,标注在不同的类、方法、属性上面快速的生成在线文档。

然后 Knife4j进行了增强,不仅页面美化了,而且还可以在线调试,还支持导出离线的接口文档。

我当初就是在开发环境启用了 Knife4j,只有在开发环境中,前端能够访问到我们的接口,然后也可以直接导出离线版,发给前进行对接。另外我自己本地测试的时候,包括和前端进行面对面沟通的时候,我是习惯使用postman 进行测试,Knife4j提供的那个测试页面功能太少,没有 Postman 专业。包括我们项目的每一个接口,都是在 PostMan 中创建了团队,进行维护的。无论是前端还是后端,都可以直接在它的接口列表中直接调出来以前写好的接口,进行接口测试的。


八、你们前端是怎么部署的?

我们前端的话分为 App 端和 PC 端。

App 端是直接打包成应用的。

PC 端是需要将前端资源部署在服务器中的,我们是采用 nginx 作为前端项目的部署服务器。前端我们架构是使用 vue 写的,前端写完后会使用 webpack 进行编译构建,把 vue 文件转为编译后的js、css、html 之类的静态资源。然后把这些静态资源打包发布到 nginx 服务器。部署方式也很简单,将静态资源更新到 nginx的 html 目录下,然后修改 nginx配置文件,将root 目录指定到项目路径,这样前端请求域名根路径下的静态资源时,直接在nginx 端进行响应了

另外我们还配置了关于后端的 api 访问路由,凡是以 app 路径开头的资源,,全都转发到后端的网关微服务上,转发的策略用的是轮询。


九、文章查询流程简单讲一下?

文章查询我们是支持首页查询、频道栏目查询,另外还提供了关键字检索功能先说一下这个表结构吧,有3张表存储文章信息:文章基本信息表、文章配置表、文章内容表,他们之间都是一对一的映射关系。然后还有一个大数据推荐接口被我们调用。

ap_article 文章基本信息表

ap_artcle_config 文章配置表

ap_article_content 文章内容表

当初我在设计这三张表的时候,本质他们都是文章相关的字段,本应该在一张表里,但是考虑到后续随着数据量的增多,可能会单表带来很大的压力,所以进行了垂直分割。我按照查询的业务维度进行拆分的,比如:基本信息一张表,配置信息一张表,文章内容一张表进行分离。

先说首页查询吧,首页查询其实是推荐查询,推荐的依据主要是:

  • 用户自己在个人中心配置过的感兴趣标签。
  • 大数据系统根据用户行为数据分析得到的用户画像,这个是不断更新的;
  • 系统本身的推荐,如:管理员设置的批量推荐、广告推荐、以及随机的热点资讯;

用户在 App 中每次上拉表示刷新一波推荐内容,请求到达后端后,后端是直接远程调大数据提供的推荐接口,返回推荐给用户的 30 条资讯文章信息 id。然后拼接这 30 条文章信息,返回给前端,前端以每页展示 10 条的方式进行分页展示然后就是当用户下拉到第三页的时候,前端再向后端发起请求,再调用大数据推荐接口获取后30 条推荐的文章数据返回给前端。

这个查询就是按照频道,时间进行查询数据库中的数据的。

如果用户切换了频道栏目,频道可以自定义,比如:新闻、国际、军事、娱乐、民生、两岸等,那么查询的时候就不再是按照推荐算法查询,而是按照发布时间进行查询。比如:用户在手机 App 中,下拉页面,就会刷新一波最新的资讯,这个资讯时间是依据上次刷新的时间,进行查询的,查询上次刷新之后的最新资讯。如果说没有更新的,那么会返回已经是最新的列表。

如果用户进行下拉,就会按照时间进行反向分页查找,比如先返回最近发布的10条数据,当浏览到底部的时候,自自动去查询更早的 10 条数据,在后端说白了其实就是按照时间字段进行条件筛选以及分页查询实现的。

另外就是我在完成这块业务的时候有两个技术上的小亮点。一个是采用 Freemark 将文章内容静态化,就是按照预制的模版,生成为静态的 HTML资源因为文章内容数据不会经常改动,但如果每次请求都要重新查询 DB,以及重新渲染模版数据的话,会造成计算资源浪费以及延长了响应时间。所以我们进行页面静态化,就是把文章的内容数据,按照预先定义好的模版,填充到模版中,生成静态资源的HTML文件。这样在后期无论是返回给 PC 还是返回到 App,都是直接返回准备好的静态资源。

另外就是我在完成这块业务的时候有两个技术上的小亮点。一个是采用 Freemark 将文章内容静态化,就是按照预制的模版,生成为静态的 HTML 资源,因为文章内容数据不会经常改动,但如果每次请求都要重新查询 DB,以及重新渲染模版数据的话,会造成计算资源浪费以及延长了响应时间。所以我们进行页面静态化,这样在后期无论是返回给 PC 还是返回到 App,都是直接返回准备好的静态资源。另一个就是将这个渲染好的静态资源,直接上传到公司内部搭建的分布式文件系统中,是使用minl0 搭建的,将来用户点击文章标题,进入到文章详情页时,就直接从 MinlO 中获取提前渲染好的页面数据就好了,不用再查询 DB。

还有就是关于文章的检索,这个我们是搭建了 Elasticsearch 进行实现的,用 Elasticsearch 的主要原因是,MySQL 本身查询的话,是基于物理自盘,所以查询效率比较低,而且遇到关键字模糊查询,会导致索引失效。另外就是 Elasticsearch 它自己的优势很明显,比如支持分词检索,并且也提供了一套完整的语法,支持各种查询方式。
所以我们使用 Elasticsearch 进行实现的。


十、ThreadLocal在项目中有用到过吗?

有的,我们是使用 ThreadLocal保存用户的状态信息,这样就可以随时随地获取到用户信息了。实际项目流程是这样设计的:

首先请求先到网关,我在网关设计了一个全局过滤器,当它拦截下来请求后,将请求头进行解析,从请求头中拿到用户的访问 Token,这个 Token 经过解密后,转为 JWT格式的 Token,并校验签名是否正确、token是否过期,如果没有问题就获取到用户账号,并且将账号信息写到请求头中,转发给后面的业务服务。

业务服务中自定义了 SpringMVC 拦截器,从请求头中获取到用户信息,并存储到ThreadLocal 中,这样就可以在本次请求中随时随地获取到用户的状态信息。当请求处理完毕后,在拦截器后置处理方法中再将 ThreadLocal 中的数据清理掉就可以了。


十一、文章发布流程

题外话:文章相关的整个流程,我们分为:文章发布、文章审核2个流程。

文章发布:自媒体人在自媒体平台端发布文章,这是发生在自媒体微服务中的;当文章审核通过后,自媒体微服务远程调用文章微服务,将审核通过后的文章发送到文章微服务中进行文章的正式发布,当页面点击文章发布后,紧接着进行审核,审核可能通过,也可能不通过。

我们文章审核:使用 MQ 解耦当前请求与审核流程的耦合,这也是发生在自媒体微服务中的;

文章发布是在媒体端进行的行为,首先是媒体机构或个人通过机构审核或者个人实名认证后签署网络文明规范等协议后,才可以在自媒体平台端发布文章。当然,这个文章发布并非立刻就可以让用户看到,这里仅仅是自媒体平台端发布,在发布之后,还需要进行文章审核的,审核通过后,才可以让用户看到。大概实现思路是这样的:

  1. 在媒体平台系统中,前端我们是采用富文本的形式编辑文章,并且我们支持自动保存,就是每隔 10s,自动保存正在编辑的文章内容到自媒体文章表中。这个保存操作就是前端写了一个定时函数,将富文本中的内容转为 JSON 格式的文本,用异步的方式发送给后端的自媒体服务进行保存。后端保存文章信息到文章表、文章和素材的关联关系到文章素材中间表中。其中,文章信息主要包含:标题、所属频道、标签、文章内容、封面图片这些数据。


  2. 另外就是文章编辑的时候,还涉及到图片素材的管理,我们并没有把图片和文字混在-起,而是单独的针对图片进行管理。无论是编辑文章时,上传的图片,还是在单独的素材管理中上传的图片,都统一认作为素材库资源,我是使用 Minl0 进行存储的。在文章编辑时,需要用到图片,那么就会关联素材库中的图片素材,在数据库中是以中间表维护文章和素材的关联关系,就是说他们两个是多对多的关系。之所以没有把素材和文章放在一起实现,而是单独把素材拎出来管理的目的,就是说,一来便于人工检查、管理资源,另一方面就是实现素材复用。我们有一个素材管理模块,在页面中针对素材进行分门别类的整理,不同的素材放到不同的目录下,底层是利用 Minl0 进行实现的。这样,即便没有编写文章,自媒体人可以预先创建多个目录,提前上传好他准备好的图片素材。

  3. 最后,当媒体人编辑完毕,确认没有问题,就可以提交审核,提交审核通过后,再根据文章发布时的选项,决定是立刻发布,还是定时发布。审核流程比较复杂一些,涉及到微服务调用、阿里云内容服务调用还有 Kafka 服务解耦等技术。还有就是,无论是立刻发布还是定时发布,实现方案是一样的,都是使用定时任务调度技术,每间隔1分钟,定时扫描审核通过后的文章表实现的。因为我们业务上对文章发布没有非常强一致性的要求,所以即便最慢可能会有1分钟的延迟,也是可以接受的。这里的 MQ 调用、发布方式,属于审核流程中的问题,我们在下一个问题中详细说

这就是大概的文章发布流程。


十二、文章审核流程

这个文章审核流程比较复杂一些,它涉及到服务之间的调用,以及第三方接口的调用。当媒体人确认自己编辑的文章没有问题后,可以提交审核,当文章提交后,自动触发这个文章审核流程。

并且无论是第一次新增文章,还是修改文章内容,我们都会执行文章审核流程


首先,自自媒体平台端提交文章审核,由自媒体服务自己负责审核。由于审核流程涉及到敏感词过滤、OCR 识别、内容审核、图片审核等多个环节,所以响应时间比较慢,而提交审核这个请求本身从业务角度来讲,没有必要等待整个审核流程结束后,再返回响应结果。
所以我针对自媒体文章的状态,设置了提交待审核、审核失败、人工审核、审核通过等状态码进行标识。


当自媒体文章提交后,状态码为待审核,同时我会以JSON格式封装文章的基本信息,发送到MQ 中的审核队列中,由消费者进行异步审核。这个异步审核的详细流程是这样的:

  • 首先是 OCR 识别图片素材中的文字,防止有一些敏感词汇以图片的形式体现出来,我是利用 Tess4J实现 OCR 图片文字识别的,主要这个免费,而且也是比较准确的。识别出来的文字,我会把他列入到后续的敏感词过滤、阿里云内容审核的数据中,一起参与后续的内容审核。
  • 然后是敏感词过滤,将一些文章中不应该出现的敏感词汇进行筛选判断,我是提前创建了-张敏感词数据表保存用来保存敏感词汇:

我是利用 DEA 算法进行词项的匹配封装了一个工具类,判断传入的文章内容是否有敏感词汇。如果有的话,则直接判定为审核失败,并给出失败状态码和失败原因,下次媒体人登录平台查询时,就能看到审核失败的类型和原因了。

  • 敏感词过滤完了以后,其实也不能说一定没问题,还是得借助一些更加专业的内容审核服务过滤一下,我们是买的阿里云的内容审核服务,好像是千次 1块多钱吧,支持图片和文本的审核。
    我会分别调用阿里云内容审核服务的文本审核和图片审核如果审核通过,就修改文章状态为审核成功,剩下的交由定时任务进行文章的发布。如果审核没有成功,可能是失败,也可能是结果不确定,还需要进一步人工审核。所以如果审核失败的话,就把状态码改为审核失败的状态码,如果需要人工审核的话,就把状态码改为待人工审核的状态码。人工审核会有平台运营的人品登录平台端后进行人工审核。审核的流程很简单,直接查询自媒体文章状态码为待人工审核的文章,进行人工审核。人工审核完毕后,再给出审核通过或者失败的结果。

自媒体人可以在自媒体平台上,查询到自己发布的文章当前状态,比如:待审核、审核拒绝、人工审核、已发表等状态。

最后就是文章的正式发送,文章由自媒体服务发送到文章微服务中进行保存因为审核流程是异步执行的,本身业务上也允许一定时间的延迟,所以再结合文章的定时发功能,我就干脆,将立即发送和定时发送,统一使用定时任务组件 XXL-JOB 实现实现流程很简单,就是每间隔1分钟,扫描自媒体服务的文章表,如果状态码为审核通过中状态码使用8代表),并且设置的发布时间小于当前现行时间,那么就将此文章取出来用 OpenFeign 远程调用文章服务,发送到文章服务那边进行保存或者修改。文章微服务那边的话,如果保存或修改成功后,会重新基于 Freemark 生成文章的静态化资源,并发布到 MinlO 中。

并且还会发一条消息到 Kafka 中,通过 Kafka 同步数据到检索服务中更新 Elasticsearch 索引中的文档数据。

另外有一个细节就是,我们部署的时候是多个服务实例部署,所以为了避免重复发布,所以针对远程发布的流程,进行了加锁,防止多个自媒体服务重复发送相同的文章。加锁的话我是用的是基于 Redis 的一个 Java 组件 Redisson 实现的。


到此为止,大体流程介绍完毕了,但是这里面有很多细节,我们不能全都放在上面的流程中讲解,否则讲 20 分钟都讲不完的,极有可能面试官听的不耐烦了,就直接打断你。所以我在上面的话术介绍中,留了几个引子,等着面试官来问,比如:

  • 在讲解实现步骤的时候,我都会大概提了一下用哪些技术实现,如:OCR识别、DFA算法、阿里云内容审核服务、XXL-JOB、微服务调用等;面试官听到这些亮点后,极有可能会追着问这些东西的细节;
  • 还有一些业务上的细节,面试官追着问你有没有一些优化业务流程相关问题,我也准备了两个:文章发布到文章微服务的时候,怎么判断这是新增还是修改?人工审核流程一直不结束,难道要一直等着吗?

上面这些衍生的细节问题,我在下面都准备好了,足够面试使用了,至于 Elasticsearch 同步更新,我放到后面讲解。

OCR识别

识别就是一种光学字符识别技术,它是通过检测图片上的明暗度,确定文字的形状,然OCR后利用字符识别方法,将图片翻译成相应的文字。我们这个项目用的是 Tess4J,它是免费的,就是基于谷歌开源的技术进行二次封装的,使用的时候只需要导入 tess4i 依赖和中文字库就可以了,在项目中的话就是识别图片中的文字,判断这些文字是否是敏感词。

DFA算法

DFA 算法是一种确定有穷自动机的方式。

以前我们是使用的传统的字符串比较方式,就是遍历所有的敏感词,然后在每次循环将整片文章字符串传入,然后调用 contains 方法进行判断,这种方式效率非常中,差。所以我们改成基于 DFA 算法的实现方式。

在实际使用的时候,我是封装了一个工具类,一次性的把所有的敏感词存储到了多个 map中,然后和文章内容进行比对,map 中的某一个词项能够和文章中的词项比对上,就结束。

这种算法的原理,我大概理解就是将预先输入的词项,构建成一个类似索引的数据结构,匹配的时候也是按照顺序进行匹配,如果输入的内容能够匹配到某一个索引的终点,就代表某一个词项在文章中出现过。

XXL-JOB

XXL-JOB 是一个开源的定时任务框架它本身就是分布式架构,客户端就是每一个微服务然后单独运行一个 XXL-JOB 的服务端,我们只需要在服务端配置运行的任务规则、脚本等等,就可以实现集中式的定时任务调度。在我们项目中的话,比如:文章定时发布这个流程中,就用到XXL-JOB 了。

微服务调用

微服务调用过程中,注意3个问题:限流、异常、超时。

  • 限流指的是被调用的服务要配置限流的阈值,不可能 A服务调 B 服务,B 服务说你随便调吧,怎么可能,一定需要针对性的配置限流阈值。试想一下,B 服务如果扛不住其他服务的调用,那指定会异常、超时情况出现,极容易引起雪崩。这一点很多培训机构是不讲的,这个细节大家注意下,当然不用给面试官讲这个,只是博哥临时想到这个点,给大家提一嘴。
  • 异常、超时:在服务调用方,我们配置了降级类,主要是通过和 Sentinel 整合,然后在OpenFeign 的 FeignClient 注解中,配置了 fallback 降级子类,当异常或超时情况出现,长动切断服务调用,执行子类实现的方法,作为兜底的策略。这样就不用再把一些异常信息抛给上层调用者了。

人工审核流程一直不结束,难道要一直等着吗?

我们使用 XXL-JOB 配置了一个定时任务,每隔 10 分钟执行一次。定时任务的内容是,我们会扫描自媒体文章表,状态码为3的数据,表示正在等待人工审核。同时 AND 连接条件,WHERE 时间小于当前现行时间-1天的时间,则表示,提交1天后,任然是人工审核状态,则直接将状态码改为审核失败的状态。

ID生成策略怎么选择的(技术亮点:雪花算法)

在文章服务保存文章数据、文章配置信息、文章内容信息时,我们使用的是雪花算法实现的。
其他的数据量少的表,一般我们使用的是自增主键实现的。


十三、自媒体文章上下架实现流程讲一下(适用于Kafka在项目中哪里用到过)

提示:这里使用 Kafka 作为异步解耦的中间件,换成 RabbitMQ 也是一样的,主要看你对哪个消息中间件熟悉。

文章发布且审核成功后默认认为是自动上架的。媒体人可以在自媒体端修改文章的上下架状态。但是考虑到这个行为算是一个比较频繁的行为,且业务本身没有强一致性的要求。所以我们使用 MQ 进行解耦。

当自媒体人在选择上架或下架文章操作时,先判断文章是否存在、文章是否发布,如果状态为否,则直接结束这个流程,如果状态都正常,则执行修改文章的上下架状态。具体的操作就是向 Kafka 中发送了一条消息,消息内容是文章 ID 以及上架或下架的状态码。文章微服务端配置了一个消费者,监听队列中的消息,我们将 Kafka 的自动确认改为手动确认,当文章微服务的端本地数据库更新文章状态成功后,再确认消息队列中的消息。


十四、文章搜索流程详细讲一下(同样适用于:ES在项目中哪里用到过)

首先,我们先创建了文章索引,结合实际需求,我们自定义 Mapping 规则,将内容和标题设置为可以被检索的字段,并且引入了IK 分词器,让索引支持中文分词。


在我们项目中,我们设计了单独的检索微服务,提供关于检索相关的服务。当请求到达检索微服务的文章检索接口后具体的步骤是这样的:

  • 客户端请求先到达网关像这种检索文章的接口,我们是直接放行,不校验用户登录状态的;

  • 然后请求被转发到检索服务后,我会根据前端提交的参数拼接查询条件,比如:查询关键字、检索时间段的开始与结束时间、分页等参数拼接查询条件。另外还支持了按照发布时间排序、高亮显示关键字等效果。

  • 从 Elasticsearch 返回的数据中解析出来命中的数据,封装为预设好的 VO 对象,并返回给能端进行展示;

另外还有一个 Elasticsearch 相关的操作,就是关于 Elasticsearch 的数据同步问题。我顺便给您讲一下吧。

在文章服务中,触发了文章发布、文章修改以及文章上下架等操作,我们都会同时更新Elasticsearch 中的数据,由于这个操作在业务上来讲没有强一致性的需求,并且多少也有点并发的可能性,所以我使用 Kafka 进行解耦。简单说就是文章微服务作为消息生产者将更新文章的事件封装为消息,发送到Kafka 中。搜索微服务作为消费者订阅 Kafka 中的消息,将接收到的文章数据新增或者修改到 Elasticsearch索引库中。

更新方式也很简单,当初存储的时候,我们是以文章ID 作为 Elasticsearch 中的文档 id,所以再次更新的时候,根据文章ID 进行更新 Elasticsearch 中的数据即可。


补充细节:如果面试官提问:你是用的什么方式整合的 Elasticsearch?

我是引入了 Elasticsearch 官方提供的依赖实现的。具体使用的时候,先声明一个叫做RestHighLevelClient 的客户端对象,然后在具体使用的位置进行注入使用即可。它内部其实就是封装了一波 HTTP 查询,并且提供了一些 JavaAPI方法。

使用的时候调用 Java API方法进行条件封装,最终其实还是转为 Http 查询,查询到的结果,返回回来后,再封装为 SearchResponse 对象进行获取即可。


十五、文章搜索历史是怎么实现的(MongoDB)

文章搜索记录也是我做的,就是我们应用中会采集并保存用户的各种行为数据。然后根据这些用户数据进行大数据计算,分析用户的行为数据,得出用户的画像,并且不断地纠正用户的画像数据,这些操作是大数据部门进行实现的。而对于我们 Java 这边的话,也会根据用户的行为进行一些业务上的需求设计,例如:当用户在搜索框中进行搜索时,自动弹出下拉框,展示用户过去搜索的历史记录,方便用户重复利用历史搜索的关键字。

当时产品提的需求是这样的,展示用户的 10 条搜索记录,并且按照搜索关键词的时间倒序排序。支持删除搜索记录。默认只保存每一个用户的 10 条历史记录,超出的记录就会按照搜索时间删除,优先删除最久的历史记录。

当时在考虑这个需求的时候,我们首先排除掉了用 MySQL 关系型数据库存储。因为很明显这种需求绝对是一个并发的读写需求,后期随着运营时间增长,数据量也会不断上升,并且加载速度要快,当时就考虑换一种存储技术。

首先排除了传统的关系型数据库,在多个 NoSQL里面比较的话,Redis 做缓存组件还可以,但用来做数据查询还是非常不方便的,redis 只能支持基本的 key value 方式查询。Elasticsearch 也不合适,这个需求并不是做检索,不需要构建索引或者分词。最后还是选择了MongoDB,MongoDB 是一个NoSQL数据库,热数据是基于内存存储的,并且它也支持动态扩展,还有就是 MongoDB 支持条件查询,这就很方便了。

关于文章搜索记录保存的流程是这样的:
在执行文章搜索记录保存流程之前啊,我先做了一个判断,我先判断一下当前用户有没有登录,判断的依据就是请求头中是否携带用户 id,如果用户未登录,则不执行搜索记录保存流程,如果用户已登录,才执行搜索记录保存流程。假设用户已登录,首先要在 MongoDB 查询一下搜索记录,根据 userld+搜索关键字查询,如果存在以前搜索过的记录,就更新这条记录的时间为最新时间。如果不存在搜索记录, 查询当前用户的历史搜索记录,并且按照搜索时间降序排序继续判断返回的记录条数,如果小于10,则直接新增新的搜索记录。如果等于 10,就替换搜索时间最早的记录为当前的搜索记录。还有就是,这个文章搜索记录保存流程,操作没必要让当前请求同步等待,所以我直接设置为异步的方式实现,实现方式很简单,就是在方法上添加了@Async注解实现的。这就是我负责实现的搜索历史记录。

保存搜索记录-实现思路


十六、联想词功能你们是怎么设计的

联想词就是在搜索的时候,根据当前用户输入的关键字,智能匹配以前搜索过的包含当前关键字的搜索记录。

实现方式很简单,就是预先将联想词词库存储在 MongoDB 中,前端触发修改事件后,携带关键字向后端发起请求,后端从 MongoDB 中查询出来符合当前关键字的 10 条联想词并返回。而 MongoDB 中的联想词词库,主要是来源于两个方面,一个是公司以前维护过的一些词库我将这些数据导入到 MongoDB 中,创建了一个联想词表(ap_associate_words),另一部分数据就是来源于文章搜索历史记录表中。查询的时候,优先查询文章搜索历史记录表,如果搜索记录大于等于 10,就返回 10 条历史搜索记录作为联想词返回给前端。如果小于 10 条的话,再查询联想词表,补充够 10 条联想词数据后,返回给前端。


十七、用户行为数据你们是怎么采集的

备注:这里我们可以把大数据计算拉进来,虽然我们不懂,但是可以作为话术介绍一下,追求更加真实的推荐项目。把个性化推荐功能交给大数据完成,但是强调一下,我负责完成了实时计算的一个流程:热点文章推荐,是借助 Kafka Stream 实现的,把这亮点强调出来,面试官肯定不会问你大数据东西,但是可能会对这个实时计算感兴趣。

我们项目中主要收集了注册信息、登录地点(ip)、点赞和分享、评论和回复、收藏、关注广告点击、搜索行为、设备信息、页面停留时长等数据。这些数据有的是用户主动发生的行为,比如:点赞、分享、评论;有的数据是通过数据埋点针对用户行为数据进行捕获,例如:用户在广告页面、视频停留时长,文章页面停留时长等;

关于用户画像和智能推荐的,是由大数据小组完成数据埋点和采集的,以及推荐计算的。而我们 java 这边的话,是根据用户的行为进行统计,完成热点文章的计算与推荐因为不同的用户行为数据,对应的微服务是不一样的,比如收藏是在文章微服务,关注是在用户微服务。

所以为了能够集中式的采集用户行为数据,我们这边是利用 Kafka 进行实现的,当发生用户的阅读、评论、点赞、收藏这4种行为时,在对应的微服务业务流程处理完毕后,除了保存到数据库中以外,还会把用户行为数据发送到 Kafka 中用于实现实时计算热度文章流程。

所以为了能够集中式的采集用户行为数据,我们这边是利用 Kafka 进行实现的,当发生用户的阅读、评论、点赞、收藏这4种行为时,在对应的微服务业务流程处理完毕后,除了保存到数据库中以外,还会把用户行为数据发送到 Kafka 中用于实现实时计算热度文章流程。关于用户行为这块的业务,我们是设计了单独的用户行为微服务,依据这些用户行为数据完成相关的业务,比如我负责的热点文章的定时统计实时统计


十八、文章热度你们是怎么计算的(技术亮点:XXL-JOB、Redis、Kafka Stream)

是这样的,文章热度我们主要是根据用户的阅读、点赞、评论、收藏这4种行为进行计算。在文章表中,我们设计了相应的点赞数量、收藏数量、评论数量和阅读数量进行记录:


当用户针对某一篇文章发生了这4种行为后,不仅更新数据库文章表中的字段,同时还要根据这 4 种行为数据,进行文章热度的统计计算,求出来哪些文章是比较火。

统计的需求:针对5天以内发布的文章进行统计,统计普通频道和热点频道当中最热门的30 条文章数据。防止很久以前一些老的文章,长期占据榜首的情况出现。

统计的方式:我们分为定时计算和实时计算两种方式,不管是哪种方式,统计的结果是不会保存在 MVSQL,而是保存在 Redis,因为每天凌晨2点,都会重置热点数据,说白了每天都会重新查询 MySQL 重新开始新一轮的热点文章计算。所以说就没必要把文章热度数据存在MySQL 当中。

定时计算主要是借助 XXL-JOB,每天2点的时间,执行计划任务,进行统计,主要是针对 5天以内发布的文章做一个统计,作为每天新一轮的热点文章热度初始数据。

实时计算是借助 Kafka Stream 实现的,主要是针对当天新产生的用户行为,进行实时计算。业务上,我们认为当天发布的文章,发生的用户行为所占的权重分值比重会高一点,避免一些老的文章一直霸榜的情况出现。实时计算出来的分值,会基于每天定时计算的结果进行累计两种计算方式结合在一起,实现这个业务需求。

统计的权重:

  • 用户不同的行为,权重不一样:阅读权重:1,点赞权重:3,评论权重:5,收藏权重8
  • 当天发布的文章,对应的用户行为权重得分是非当天文章权重得分的3倍。这块实现方式是主要是在实时计算中实现的。

我给您分开详细介绍一下具体的定时计算和实时计算的实现方式吧我先给您说一下定时计算是怎么实现的。

每天凌晨两点,执行定时任务,针对前5天的文章,进行分值统计,这里的定时任务是使用XXL-JOB 实现的。

因为文章的阅读、点赞、评论、收藏这些数据都在文章数据表中存在的,所以这个计算逻辑特别简单:

  1. 按照发布时间字段,查询出来最近5天发布的文章,并且文章状态处于正常状态
  2. 根据文章的 4 项行为数据,结合它们对应的权重分值,做个加法计算,计算出来每篇文章的最重权重分值,并将所有文章的分值结果保存在一个 List集合中。
  3. 远程调用媒体服务,得到所有的频道集合,遍历频道列表,在每次循环中,再次使用流式编程的方式,遍历前面计算出来的文章分值集合,比如:调用filter方法进行过滤,将每-篇文章归类到对应的频道下。然后调用 sort 方法进行排序,按照每篇文章的分值进行降序排序。最后,判断一下当前频道下的文章数量是否大于 30,如果大于 30 篇文章,就截取前 30 篇文章,作为当前频道的热点文章,并将他们缓存到 Redis 中,把频道ID 作为key,把热点文章列表作为值。
  4. 求出所有文章热点排名前 30 的文章,并缓存到 Redis 中,这些数据作为【热点栏目】中的热点文章进行推荐。我们使用 Redis 进行缓存热点文章数据以后,再针对热点文章查询时,直接从 Redis 返回就可以了。包括后续实时计算的时候,针对当天的文章进行计算,只需要更新 Redis 中的数据即可。
  5. 最后,我设计了 XXL-JOB 定时调度,每天凌晨2点,执行定时任务,将前5天的数据重新计算,最终的热点文章重新缓存到 Redis。


我先给您说一下实时计算和定时计算的区别吧。

这两种方式,计算的数据,都不会持久化保存到 MySQL,而是不断动态变化的。

首先是每天凌晨2点执行定时计算,作为当天的基础数据,缓存在 Redis 中。

然后当天发生的新增用户行为,会被发送到 Kafka 的热点文章分值主题中保存。

最终,在文章微服务中,定义了一个Kafka Stream 负责针对队列中不断生产出来的消息,进行消费处理。

处理步骤大概是这样的:

  1. 重置消息 kv 格式:定义 Stream 对象时,重置关于消息的 key value 数据,将文章id作为 Key,将消息类型行为分值作为值,比如说:key是1234,value是likes:3,代表点赞行为加 3 分。
  2. 分组:在后续的处理中,我会按照 key进行分组,同一篇文章的行为数据,分到一个组里面,这样我就可以在聚合的时候,针对同一篇文章进行聚合。
  3. 聚合:聚合是发生在同一个分组内部的,也就是说,针对同一篇文章发生的不同行为进行聚合计算,比如:针对点赞加3分,如果用户取消点赞,那就减3分。这样就能算出来,在某一个时间窗口期内,用户针对同一篇文章,发生的各种行为,得出的最终分数。
  4. 输出:最后,再将聚合后的结果数据,转换为 key value 的形式,输出到热点文章处理完毕的主题中。

消息处理完毕后,还需要定义一个消费者,消费处理完毕的结果,这个消费者也是定义在文章微服务当中的,当接收到 Kafka Stream 输出的【文章统计结果消息】,则进行更新操作:

  • 首先,先更新文章数据表中的用户行为记录,比如:读、点赞、收藏、评论的数量
  • 然后我会判断一下当前文章是否是当天发布的文章,如果是当天发布的文章,则分数值乘以 3, 然后更新到 Redis 中。比如说,用户收藏了非当天发布的文章,分值权重加8分,而收藏了当天发布的文章,分值权重加 24 分。如果不是当天发布的文章,则分数值按照正常的分值进行存储。
  • 最后,我会将热点数据,更新到 Redis 缓存中,主要是更新两个项目,分别是:
    • 更新当前文章所属频道的热点文章数据。
    • 更新热门推荐栏目的热点文章数据。
      如果当前文章是热门数据,那么它一定在 Redis 缓存中,所以只需要更新 Redis 中的分值,做一个加法计算即可。
      如果当前文章,不是热门数据,但是它的分值经过计算,大于热门文章的最后一片文章的分值,那么当前文章就作为新的热点文章替换掉原先分值最低的热门文章就可以了

Kafka 流式计算进行统计,并且将最新的分值更新到 Redis 中,说白了其实就是基于每天定时统计出来的基础数据进行动态的调整。


十九、你们项目中的持续集成是怎么实现的

题外话:一般,如果公司做这种 CI/CD 的话,由专门的运维负责,普通java 开发,一般是不会接触的。但如果面试官问到这个问题,我们说不知道,也不合适,所以,这里就编一种最简单的实现方案,讲给面试官听就行了。我当然知道,可以搭建 K8S+Docker+Jenkins+Prometheus 等等各种技术,构建一套大型集群环境,直接一站式部署、动态扩容,非常方面,但这种规模一般公司是不可能采用的!一般公司,能整合一波 Jenkins,做到持续集成,把自动编译、打包、发布镜像、自动发布项目做好,就已经很不错了。
所以我们这里使用 Jenkins 的目的就是为了简化工作流程,没有别的目的。至于说动态扩容K8S 等东西,我们项目用不到。


二十、你们项目是如何部署的

这种关于项目中的部署问题,在面试中还是经常会问到的,比如:

  • 你们项目部署了几台服务器?(可以说公司自己搭建的机房,也可以说买的云服务器)
  • 是你部署的吗?部署环境是你搭建的吗?(这种问题很变态…我 TMD 是 Java)
  • 服务状态你们是怎么监控的?
  • 面对并发压力,你们是怎么扩容的?

这些问题,说法不一,这里按照课件的配置,编一套简单容易实现的方案话术,大家参考一下。

好的,面试官,我按照我们项目的访问顺序,挨个给您介绍一下吧。

  1. 首先是前置网关层,我们部署了两台 Nginx,基于 KeepAlive 搭建了主备模式的高可用方芽
    Nginx 主服务器绑定一个VIP(虚拟 IP),作为项目的入口,一旦主服务器挂掉,从服务器 KeepAlive 会感知到 Nginx 主服务器,自动提升从服务器作为主服务器,从服务器绑定 VIP继续作为项目的入口。我们项目的一些静态资源,比如前端编译后的静态资源,是部署在 Nginx 服务器的。当然我们也会额外买一些 CDN 资源。当用户请求到达 Nginx,Nginx 会根据提前配置好的规则,将请求转发给 Gateway 微服务网关。我们在 Gateway 网关处,通过调整同一个 group 分组下的路由权重,实现了灰度发布。
  2. Gateway 网关以及业务微服务和微服务组件,基本上是部署3个以上的服务实例。当然会根据实际情况动态调整服务实例的数量,微服务实例的部署方式就是利用提前写好的Jenkins 脚本实现的。
  3. 而关于 MySQL、Redis、Kafka、MongoDB、Elasticsearch 等软件,是单独独立部署,并且都做了主备或集群。

关于线上服务日志,我们是主要是分为监控微服务状态和服务内部日志两部分:

  • 监控微服务状态是通过 Skyalking 实现的,既可以监控某一个服务的实例信息,比如:JVM 参数、GC 工作情况、内存、CPU 利用率等,还可以监控微服务调用链路的异常、超时等情况;
  • 另外关于服务内部日志,我们是通过整合ELK,实现的。工作原理很简单,就是在每一个微服务中,引入 Logstash 依赖,并配置好关于 Logstash Server 端的配置。这样,在工作的时候,所有的日志输出,都会上报给Logstash Server 端,并输出到 Elasticsearch 中。我们配置了一个 Kibana 的 Discover 界面,可以直接检索每一个服务内部的运行日志。

实际运行过程中,我们会监控 SkyWalking 面板,观测服务运行的状态,当发现某些服务实例的资源利用占比一直非常高,首先我们就会通过动态上线新的服务实例,承担部分压力。然后继续观测监控面板,如果服务实例的资源利用占比下降了,并且 GC 回收不是那么频繁那就没问题了。如果说,即便上线了多个新的服务实例,依然解决不了问题,那可能是运维或开发中的某些环节出错。

如果要排查的话,首先单独看每一个服务实例的运行状态是否正常,网络是否畅通,包括客户端请求响应时间是否频繁阻塞超时等等。

如果运维这边没问题,那就考虑通过分析项目日志,以及 JVM GC 的内存快照,判断是否是业务代码有问题,比如:SQL查询较慢、微服务远程调用慢、是否有导致内存泄露的代码一点点的分析问题。


二十一、项目部署后如何监控微服务的日志?


二十二、点赞、阅读、不喜欢功能如何实现?

设计思路

使用 Redis 缓存用户的点赞工阅读、不喜欢等用户行为数据;
每隔 10 秒,异步刷新的方式,将最新一次更新的数据批量更新到 MySQL中;思路上的东西,在前面的业务流程介绍中都体现过多次了,并且对于现在阶段的大家来说,这两个思路上的东西应该很容易理解吧?

点赞、阅读、不喜欢等用户行为,是放在用户行为微服务中实现的,实现方式非常简单就是基本的 CRUD 操作,设计相应的中间表,记录用户对某一篇文章的点赞、阅读、不喜欢的情况。

当用户触发了点赞、阅读、不喜欢等用户行为时,由用户行为微服务处理,先更新到 Redis.我会使用 Zset 类型,将用户行为封装为 JSON 格式,作为值,将时间戳作为 score 分值。更新到 Redis 成功后,立刻响应前端,前端可以先标注点赞、阅读、不喜欢成功。可能会出现用户点赞后,立刻退出文章界面,点击个人中心,查询点赞记录,可能会出现几秒中看不到点赞记录的情况,但是无所谓,这种情况出现的可能性较少,就算有人故意点赞完毕立刻退出文章列表、打开个人中心、查看点赞记录,可能会看不到刚才点赞的结果,但这种情况我们业务上根本就不在意。

后续每隔 10 秒钟,使用 XXL-JOB 定时任务,以异步的方式将 Redis 中的数据进行处理即可,处理步骤分3步:

  1. 先更新到 MySQL 的数据表中,记录用户的行为的数据
  2. 将这 3种用户行为数据发送到 Kafka 中,因为在文章热度计算流程中,会监听用户的点赞和阅读行为数据,而不喜欢行为数据需要被大数据系统采集;
  3. 更新 Redis,我们使用 hash 类型,记录每一个用户对文章产生的行为数据。这样,当用户重新打开以前阅读过的文章,只需要査询 Redis,就可以获取到用户对应的行为数据,比如查询用户是否之前针对这篇文章点赞、不喜欢。直接在redis 返回,不需要查询 MySQL每次查询完毕后,重置用户在 Redis 的行为数据的 TTL过期时间,默认设置过期时间为30 天。用户长期不使用 App,就先把缓存中的数据自动清理掉,等下次查询没有时,重新从 MySQL 中缓存。
  4. 还有就是在用户发生点赞行为后,会同步更新一下用户的点赞列表缓存,使用的是redislist 类型实现的。
  5. 将来用户查询对某一篇文章的用户行为数据,以及用户的阅读记录,用户的点赞记录,直接从 Redis 中缓存返回就可以了,

题外话:其实这里用 Kafka 实现也可以,如果面试官问你,为啥用 Redis 不用 Kafka?我们在设计项目的时候,针对异步解耦复杂业务流程、跨服务的通信,我们会优先考虑 Kafka消息中间件进行实现。
而针对缓存、同一个服务内部的更新、数据非强一致性的场景,比如当前业务场景,用户发生点赞、阅读、不喜欢这些行为,处理这些行为的业务流程中,并不需要调用其他服务,并且在极端情况下,丢失一两条数据,也也无关紧要,所以我们就选择 Redis+XXL-JOB 定时任务进行实现了。


二十三、文章评论如何实现?(MongoDB)

文章评论是单独设计了评论微服务进行实现。所有的评论数据包括评论回复评论点赞,都是保存在了 MongoDB 当中。
选择 MongoDB 的主要原因是:

  • 文章评论算的上有点并发需求,最好基于内存实现以及非结构化数据实现,题外话: mongodb 的数据是存储在硬盘上的,只不过需要经常读取的数据会被加载到内存中,这样提高查询效率,所谓内存数据映射,所以 mongodb 本身很吃内存。
  • 我们需要设计单独的评论管理模块,如果单纯使用 Redis 这种简单的 KEY VALUE 方式实现,无法满足基础的 CRUD 操作,比如:评论基本信息查询、分页展示、时间排序、评论状态修改等等;
  • 查询评论时,往往会同时查询前几条评论的回复数据和点赞数据,在设计表结构的时候必然是一张评论表,张评论回复表,传统的关系型数据库实现的话,那必然是频繁的JOIN 连接查询,在并发场景下,这样操作非常消耗性能。

二十四、用户评论时携带的IP是怎么查询的?

实现方式很简单,我们提前购买了 IP 地址数据的省级离线库 htps://user.ip138/ip/lib/我们是在发布的时候,从请求头中获取到客户端IP 地址信息,然后查询数据库中的数据,得到发布的地点,随着文章或评论一起保存到数据库表中就可以了。


二十五、实名认证是怎么做的?

下面是话术:
我们项目是有做实名认证的,认证流程主要分两部分:

  • 一个是机构认证,机构入驻时,会要求机构上传公司的营业执照、法人信息、公户信息等等。我后台实现的时候,是直接调用天眼查的 API,将营业执照照片发过去后,天眼查会返回验证结果、机构的各种信息、以及经营范围。然后我会判断公司状态是否正常,经营范围是否包含它提交资料时,指定类目的经营范围,例如:公司是一家传统养殖公司,很明显不符合媒体服务相关的业务如果没问题,就修改机构资质表的认证状态为通过。
  • 还有一个就是个人实名认证,主要针对个人入驻,或者机构下的发布作者个人进行实名认证。认证方式就是前端要求用户录一段人脸视频,并且上传手持身份证正方面。我们后端会把视频保存下来,从视频中随机截取一张关键帧作为图片,进行校验。
    1. 首先将身份证照片进行 OCR 识别,截取出来身份证照片中的人名与身份证号码,并。且与表单中填写的姓名、身份证比对,判断用户在前端填写的数据是否与身份证照片一致,防止盗用他人身份证照片;
    2. 然后调用实名认证服务,将身份证号码、姓名传给阿里云市场第三方商家提供的实名认证服务,返回认证结果;
    3. 如果实名认证通过,就再次调用阿里云市场第三方商家提供的人脸识别服务,传入身。份证、姓名、人脸关键帧照片,第三方商家会调用公安部接口,进行校验,并返回认证结果。

二十六、自媒体图文数据统计怎么做的?


二十七、头条项目功能盘点?


用户移动端:内容推荐模块、内容搜索频道管理内容展示、内容社交、登录注册、个人首页、实名认证、个人中心、系统设置;
用户自媒体平台:内容发布、内容管理、粉丝管理、评论管理、权限管理、个人看板、粉丝画像、私信管理、素材管理、系统设置;
管理平台:用户管理、内容管理、数据统计、标签管理、公告管理、系统管理支撑系统:爬虫系统、广告系统、推荐系统、计算系统、知识系统

  • 用户个人中心服务:用户登录注册、个人信息查询修改、点赞、浏览历史查看等
  • 文章服务:文章发布、文章收藏、文章信息、热点文章计算、实名认证
  • 自媒体服务:自媒体文章 CRUD、文章审核、素材管理、自媒体数据看板(各项数据0&粉丝画像等等)、实名认证
  • 用户行为服务:点赞、阅读、不喜欢流程0
  • 用户检索服务:文章内容检索
  • 评论服务:评论发布、评论回复、评论点赞
  • 平台管理服务
  • 任务调度服务
  • 图片管理服务

业务服务:SpringBoot、MybatisPlus、XXL-JOB
数据层:MySQL、Redis、Elasticsearch、MongoDB
中间件:Kafka、MinlO、ELK
日志框架其他工具:Jenkins、Docker、GitLab开发环境:JDK8、MySQL8、Maven3、Redis5
前端:Vue、Echarts、uniapp、ELementUl

核心流程(推荐重点给面试官讲的流程)
登录
文章发布与审核
文章热度计算


二十八、头条项目简历编写?

TY 虎峪河职业技术学院校园文明平台(工作室项目)
2023.02-2023.06

Java 实习生

项目介绍
本项目为响应 TY 市文明办的精神文明创城办法,发起的文明校园创建活动。本项目由学院主任牵头组织学生工作室,完成文明校园线上文明创建平台。致力于发掘更多的文明事、宣扬文明活动、营造文明校园氛围、带动全校师生共创文明校园。项目分为文明管理平台、文明之窗、文明活动平台三大业务板块。核心业务模块:内容管理、用户管理、媒体平台服务、内容管理、文明校园公告、素材管理、热点推荐模块等;

技术架构:SpringBoot、MybatisPlus、Kafka、Redis、XXL-JOB、MongoDBElasticsearch、MySQL、Vue、ELementUl、Echarts

项目经历

  • 作为小组核心开发,负责与院办老师讨论业务需求,校园实地调研各岗位工作人员、学院师生,收集大家的热心建议,设计文明校园的核心业务
  • 主要负责用户管理模块、内容管理、热点推荐、媒体平台服务模块的核心业务流程设计与开发;
  • 围绕业务需求,设计出合理的 ER 图,编写最终项目交付使用手册

技术亮点

  • 使用 Elasticsearch 实现内容资源检索功能,并合理的设计索引Mapping 规则,引入IK中文分词器;
  • 整合 MongoDB,以非结构化数据存储用户评论、评论点赞、评论回复等数据使用 Redis 设计限流组件,针对用户行为,如:频繁点赞、评论、发送验证码登行为进行时间窗口限流;
  • 使用 XXL-JOB 设计定时任务场景,例如:定时文明文章发布、定时同步 Redis 数据到MySQL、定时计算热点文章等等,
  • 使用 Kafka 解耦同步业务场景,如:文章审核、用户行为采集等,并借助 Kafka Stream流式计算,设计了文章热点实时计算功能;

实习生同学,可以根据实际掌握情况,再自行调整,技术亮点写五六条就够用了,切记,技术亮点一定要结合业务场景来讲。不然你单纯说:使用Redis作为缓存组件,谁不知道Redis 可以作缓存呀,对不对?是不是感觉脱离需求谈技术,就像没说一样?

其实像这种校园项目,我不太建议用微服务,完全没必要,你也不可能准备太多东西,能够把自己负责的业务流程、实现细节讲的特别好就行了。而且我不认为大三的同学,对微服务技术有特别深入的理解。我个人是极度讨厌、憎恶疯狂内卷的 UP 主,大三毕业的同学我认为在他们完成自己主修的课程以外,java 方面能熟练掌握到 Springboot完成 CRUD,再会个redis 和 mq 就非常棒了。

有些 UP 主啊,自己当年入行的时候啥也不是,混到一把年纪了,再找工作没人要,积累点项目经验,就在小白面前炫耀自己的技能,是谁我就不点名了,多了去了!让实习生对标不可能达到的水平,纯粹居心不良!技术上的东西,有的时候真的不是说靠内卷能够速成的,真的是需要工作经验和时间进行沉淀的。单纯的理论上掌握了毫无意义。所以我就不按照微服务技术栈写了,我觉得强行让大三同学写一个校园项目还整微服务,有点离谱!(不认可我观点的喷子给我闭嘴!)


本文标签: 头条面试题经验项目