类仓储智能项目管理系统面试整理
本文最后更新于272 天前,其中的信息可能已经过时,如有错误请发送邮件到15578243672@163.com

本文用于整理面试过程中遇到的项目问题,随着面试过程更新

项目的架构体系

我负责的这个项目是一个基于SpringBoot和SpringCloud的微服务体系,核心思想是遵循高内聚低耦合。整个架构我可以按请求流入的顺序来介绍:

首先,网关层我采用了Spring Cloud Gateway作为全局唯一入口,负责路由转发、限流和降级,选型它是看中其高性能的异步非阻塞模型。

网关之后,请求会由Nacos注册中心进行服务发现,找到对应的服务实例。紧接着会进入认证层,这里使用SpringSecurity和JWT来实现身份认证与授权,并重点设计了令牌的刷新机制来保证安全。

通过认证后,请求就进入核心业务层将业务拆解为项目、工作流、定时任务等多个独立部署的服务。服务间通过OpenFeign进行声明式调用,并集成Sentinel来实现熔断和降级,避免服务雪崩。对于跨服务的分布式事务问题,采用了Seata的AT模式来保证数据最终一致性。

数据层,数据库是MySQL。缓存方面,使用Redis,不仅缓存用户信息,还用Lua脚本实现了原子性的计数器限流。在分布式锁方面,引入了Redisson及其看门狗机制来可靠地解决并发问题。异步和解耦则交给了Kafka消息队列,用它来处理诸如通知、日志等非实时任务。缓存策略上,采用了旁路缓存模式来保证数据库与缓存的数据一致性。

此外,还封装了基于模板模式的基础组件,避免重复开发。为了提升运维效率,集成了SkyWalking进行全链路追踪,通过TraceId可以快速定位问题。

认证层的理解

微服务的项目中,在经过Nginx的转发之后,请求都会到达我们的Gateway网关,由它转发到对应核心服务部分,网关有三个部分注册,路由,断言,过滤器。

假设来了一个请求URL,网关会遍历所有的路由配置,用断言匹配URL,找到匹配的URL之后,执行过滤器中的逻辑,结束之后网关会根据配置的URL转发到对应服务中,至于服务中如何转发,使用的是Feign。

在项目中,我定义了一个全局过滤器,黑白名单过滤器,只需要实现GlobalFilter, Ordered两个接口,但黑白名单我并不是全局过滤器,而是默认获得的过滤器并,对于全局过滤器重写其中的Filter方法,其中我加入了JWT验证,接口调用耗时,封装用户信息到请求头中,并且过滤器是一个链状的,只有我的鉴权过滤器向下游过滤器传递chain.filter(exchange)时,整个服务才能完整进行。白名单检查 → JWT验证 → 黑名单检查 → 其他处理。

耗时统计怎么完成的呢?
在过滤器中调用chain.filter传递exchange之前都是pre逻辑,在这之前记录开始时间,调用之后则为post逻辑,记录当前时间并相减即可。(后面发现SkyWalking就有耗时统计集成,完全可以不用自己写)

遇到了什么困难呢,如何解决的?

一开始我将网关的配置信息都写死在了配置文件中,如果要更改就不太方便,后面改用Nocas进行持久化修改了

Nacos持久化我们的路由配置信息是这么做到的?
首先Nacos的作用是让服务能找到彼此,并且能动态地获取配置,分布式的服务会部署到很多台计算机上的,传统的应用配置(如数据库连接、第三方API密钥、功能开关)都写在项目的 application.properties 或 application.yml 文件里。修改任何配置都需要重新打包、部署应用,非常麻烦,Nacos 提供了一个统一的中心来管理所有这些配置。我就可以在 Nacos 的网页控制台上轻松地修改配置。即便是后面的OpenFeign也会向Nacos询问对应可用的地址哦。

核心业务层的理解之TTL进行信息传递

首先先来明确一下线程池的作用,为了避免反复创建线程造成的开销。另外,当我们使用线程池的时候,我们就得把其抽象成为任务。“事件”(如用户点击按钮、收到消息)本身不是直接的任务。 而是这些事件触发了我的程序,程序随后创建出了一个对应的“任务”(一段代码),并把这个任务提交给了线程池。

TTL是为了在一个线程执行任务时创建出的子线程之间的信息交流使用,也就是上下文传递,在微服务的系统中,存在多个服务实例,而完成一个任务实例需要调用多个服务实例,服务实例之间的上下文传递靠的是OpenFeign中封装好的请求头。

其执行原理是在任务提交到线程池的时候TTL会保存一次上下文快照到一个对象中,接下来是三个步骤
回放 (replay):将任务中存储的快照设置到当前执行线程中。

执行 (run):执行原始的业务逻辑代码。此时业务代码从 TransmittableThreadLocal 中get到的值,就是之前捕获的值。

恢复 (restore):在 finally 块中,将当前线程的上下文恢复为执行任务之前的状态。这一步至关重要,它清除了本次任务设置的上下文,避免了污染后续执行的其他任务

在项目中怎么使用的TTL,有没有遇到什么困难?

在我的项目中,我在鉴权部分就会把请求头中的用户信息放入TTL中,这样在这个服务实例上的完成一个任务时创建的父子线程就可以实现上下文的信息传递。在用户登录之后,向我的服务发生请求时,请求会被我的鉴权过滤器拦截,除了必要的鉴权工作,在这个过滤器我还会缓存用户的信息和其它上下文到TTL中。

核心业务层的理解之链路追踪SkyWalking

在微服务体系中,A服务会调用B服务,B服务调用C服务,这样的一套流程下来完成一个业务逻辑。这样如果下游的服务出现了问题,整个流程就会失败,为了做出应对,有熔断机制和进行日志记录,日志记录中使用traceId来标识唯一链路,链路上的链接逻辑靠的是parentId找到上游服务。

SkyWalking是干什么用的,你是怎么检测自己项目的瓶颈的,怎么评价你项目的性能的,它是怎么帮助你优化的,能举出例子嘛

SkyWalking 是一个APM(应用性能管理)系统,在于通过无侵入式的字节码增强技术,自动采集、分析和可视化应用的各项性能数据,完全不需要自己去写复杂的逻辑。再接入SkyWalking后端之后,我一般会检测我设定的指标,如响应时间,吞吐量,错误率,数据库查询时间,在系统压力平稳时,我会记录下关键服务的核心指标作为“性能基线”,之后的性能评价就是依靠这部分实现的。通过检测一条调用链路上的响应指标进行问题排查,我只需要通过链路筛选慢请求,进行性能分析,之后就是正常优化工作了,同时我还设置了报警阈值,到达报警阈值之后会通知钉钉群

核心业务层的理解之微服务内部授权

这个部分的内容与网关的用户鉴权可不一样,这是关于用户是否有权请求某些权限,我在网关层实现的是用户能否登录的逻辑。在用户对象的创建中,还有一个很重要的属性就是权限列表,用户在完成登录操作之后,我会在redis中记录下用户的信息,token中存放着用户信息,在之后请求服务的时候,我通过注解加AOP编程的方式拦截请求,利用token中的用户信息去redis中查找用户权限进行权限鉴定

核心业务层的理解之服务熔断机制与服务限流

关于这个部分是为了保证下游服务的处理性能有限,但上游还在不断的给下游发送请求,可能会导致下游服务崩溃,在微服务架构中,如果链路中的一个调用出现问题,将会使得整个服务崩溃,所以为了保证服务不被大量请求打垮,我使用了三种方式来保证服务的可用性,一个是熔断机制,另一个是计数器机制,最后是消息队列的削峰效应。

熔断机制的业务场景是这样的,如果下游服务或者我们调用的第三方服务,比如钉钉通知,微信通知等服务因为网络问题或者其它情况超时或者失败的话,如果不进行处理的话,我们线程中的资源会被没有返回的下游服务耗尽,导致我们整个应用崩溃。为了防止这样情况,需要配合熔断机制和服务降级完成,我采用Sentinel来实现熔断机制,因为其有基于注解的声明式使用,方便我们开发,Sentinel会监控我声明的OpenFeign发送的请求,当异常比例超过50%时,熔断器就会打开,后续请求会快速失败并触发降级,就会使之后的请求都快速失败(一段时间之后会是半开放的模式尝试放行小部分的请求),返回fallback提示,而在fallback提示中我按照对应的业务逻辑去返回合适的提示信息,服务降级的本质就是返回一个兜底的策略,而对于超时机制就由OpenFeign实现,其中的Feign是居于Http的声明式客户端,可以帮助我们进行微服务间的调用,上游服务在一段时间内没有接收到下游的返回就会断开这次连接,释放资源,返回fallback提示

但计数器机制是redis配合LUA脚本实现的,其颗粒度更细,在对服务方法进行访问的时候,限流更适合防止特定接口被频繁调用(如登录接口、短信验证码接口等),这里我使用注解配合AOP编程配合LUA脚本实现,使用LUA的原子是基于其的原子性并且能够减少redis的通信次数,减少网络开销,如果一段时间内超过设定的计数器阈值,后续的请求都会返回失败,后续使用Jmeter测压也是验证了我的计数器设置成功

消息队列的削峰作用是让消费者根据自己的消费能力来处理事务,即便通过了限流的限制,达到的请求数可能会因为网络状况等导致处理能力下降到平常水平以下,这时候可能这些平常请求也会击垮我们的服务器,所以我使用消息队列的生产者-队列-消费者形式,消费者根据自己的能力来决定消费队列中的内容,其消费模式是广播消费模式,对于请求有特点的路由匹配进行消费,这样方便维护,另外为保证消息的可用性,我要求消费者消费后手动ACK,不然消息不会从队列中删除,生产者我采用PulishComfirm形式,只有得到队列的确认消息之后才会,生成者才会认为这个消息发送成功。

核心业务层的理解之分布式下的事务一致性保证

微服务下的事务的一致性,假如有一个任务创建了,我会调用另外两个微服务,一个是创建任务记录,另一个则是在工作流中创建同步更新用的审批流。这个部分涉及两个微服务,如果事务出现问题,想要回滚的话,就得把这两个微服务中的事务都回滚,要保证不同服务器上的事务都会滚成功,我采用了Seata中的AT模式,其设计三个重要的内容,RM,TM,TC。
TC (Transaction Coordinator) – 事务协调器:Seata的独立服务端,负责维护全局事务的状态,驱动全局提交或回滚。这是需要单独部署的。

TM (Transaction Manager) – 事务管理器:嵌入在发起全局事务的微服务中(比如‘任务服务’)。它负责开启、提交或回滚一个全局事务。

RM (Resource Manager) – 资源管理器:嵌入在每一个参与全局事务的微服务中。它负责管理分支事务,向TC注册分支状态、汇报资源状态(如锁),并执行TC的指令来提交或回滚分支事务。

两个阶段的执行过程:

  • 第一阶段
    • TM向TC申请开启一个全局事务(XID)。
    • ‘任务服务’调用本地方法更新任务状态。此时,Seata的RM会先拦截SQL,查询更新前的数据镜像(before image),用于回滚。
    • 执行业务SQL,更新数据。
    • 之后,RM再次查询更新后的数据镜像(after image),并将前后镜像和回滚SQL(这里是update语句)作为一条undo_log记录,插入到当前服务的业务数据库中。这正是AT模式实现回滚的关键
    • 最后,RM向TC注册一个分支事务,并报告状态。
    • 审批服务、消息服务重复类似的过程。
  • 第二阶段
    • 如果所有分支都成功:TM会通知TC进行全局提交。TC会快速异步地通知所有RM删除对应的undo_log记录即可,因为第一阶段数据已经提交了。这是一个非常高效的操作。
    • 如果任何一个分支失败:TM会通知TC进行全局回滚。TC会向所有已成功的RM下发回滚指令。各个RM根据之前保存的undo_log中的before image,执行反向SQL将数据还原,然后删除undo_log。
    类仓储智能项目管理系统面试整理 success
    暂无评论

    发送评论 编辑评论

    
    				
    |´・ω・)ノ
    ヾ(≧∇≦*)ゝ
    (☆ω☆)
    (╯‵□′)╯︵┴─┴
     ̄﹃ ̄
    (/ω\)
    ∠( ᐛ 」∠)_
    (๑•̀ㅁ•́ฅ)
    →_→
    ୧(๑•̀⌄•́๑)૭
    ٩(ˊᗜˋ*)و
    (ノ°ο°)ノ
    (´இ皿இ`)
    ⌇●﹏●⌇
    (ฅ´ω`ฅ)
    (╯°A°)╯︵○○○
    φ( ̄∇ ̄o)
    ヾ(´・ ・`。)ノ"
    ( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
    (ó﹏ò。)
    Σ(っ °Д °;)っ
    ( ,,´・ω・)ノ"(´っω・`。)
    ╮(╯▽╰)╭
    o(*////▽////*)q
    >﹏<
    ( ๑´•ω•) "(ㆆᴗㆆ)
    😂
    😀
    😅
    😊
    🙂
    🙃
    😌
    😍
    😘
    😜
    😝
    😏
    😒
    🙄
    😳
    😡
    😔
    😫
    😱
    😭
    💩
    👻
    🙌
    🖕
    👍
    👫
    👬
    👭
    🌚
    🌝
    🙈
    💊
    😶
    🙏
    🍦
    🍉
    😣
    Source: github.com/k4yt3x/flowerhd
    颜文字
    Emoji
    小恐龙
    花!
    上一篇
    下一篇