谢飞机面试大厂Java岗:从音视频到AI大模型,一场“水”与“火”的较量
面试官端坐在会议室里,面前放着一台笔记本电脑,屏幕上已经打开了在线面试系统。他抬眼看了看走进来的年轻人——谢飞机,穿着一件印着“Hello World”的T恤,背着一个双肩包,看起来还算精神。但面试官隐约感到,这位求职者可能不简单。
“请坐,谢飞机同学。我们开始吧,先做一下自我介绍?”面试官语气平淡,但眼神犀利。
“面试官您好,我叫谢飞机,三年Java开发经验,熟悉Spring全家桶,会用Redis缓存,也会写点JUnit测试,平时喜欢研究JVM调优,比如-Xmx和-XX:+UseG1GC这些参数,我都能背出来。”谢飞机搓了搓手,自信满满。
面试官点点头,翻开简历,开始了第一轮提问。
第一轮:音视频场景下的基础功
“既然你提到熟悉Spring全家桶,那我们结合一个实际业务场景,比如一个短视频App,用户上传视频后需要转码、生成缩略图,还要支持弹幕实时推送。你会怎么设计后端技术架构?”面试官抛出了问题。
谢飞机心想:这还不简单?我背过八股文。
“这个嘛,首先用Spring Boot搭个服务,然后接收上传,用线程池异步处理。转码的话,可以用FFmpeg,不过我是Java工程师,一般调用命令行。生成缩略图可以用Java的ImageIO。弹幕推送嘛,用WebSocket就行,Spring支持得特别好。”谢飞机回答得很流畅。
面试官追问:“那如果上传量特别大,比如像抖音那样,每秒有几千个视频上传,你的线程池和单机服务扛得住吗?你考虑过消息队列削峰吗?还有,视频转码是个CPU密集任务,你怎么做资源隔离?”
谢飞机愣住了,支支吾吾:“啊……这个……消息队列,我听说过Kafka,但自己没真正在线上用过。不过我知道Kafka是分布式消息队列,吞吐量特别高!资源隔离的话……Docker容器算不算?”
面试官微微皱眉,又问:“那么,视频转码完成后,用户要知道状态。你打算怎么通知客户端?是轮询还是长连接?”
“用……用WebSocket!刚才说了,可以推给用户。”谢飞机赶紧答道。
面试官不置可否,继续问:“那如果转码失败,需要重试。你怎么保证消息不丢失?Kafka的ack机制和offset提交你了解吗?”
谢飞机额头冒汗:“呃……这个……我大概知道Kafka有ack=all,能保证不丢,但具体怎么配置,我得查一下文档。offset……是消费的位置吧?我一般是自动提交的。”
面试官沉默了几秒,换了个问题:“那缓存方面,视频的元信息、点赞数这些热点数据,你会怎么设计缓存?Redis具体用什么数据结构?”
谢飞机稍稍松了口气:“Redis用String存点赞数,用Hash存视频元信息。过期时间可以设置成热点视频永久不过期,普通视频24小时。用Spring Cache的@Cacheable注解,非常方便。”
面试官追问:“那缓存穿透、击穿、雪崩,你分别怎么解决?”
谢飞机终于来了精神:“缓存穿透用布隆过滤器,缓存击穿用互斥锁或者逻辑过期,缓存雪崩就是加随机过期时间,还有限流降级。这个我熟!”他差点把“九阳真经”背出来。
面试官点点头,但没夸他,而是问了一个更实际的问题:“好,那如果Redis集群中某个节点挂了,主从切换时,数据会不会丢?你怎么配置Redisson的分布式锁?”
“这个……”谢飞机卡壳了,“主从切换……可能会丢一点数据,因为异步复制。但Redisson锁默认是用的看门狗续期,嗯……只要设置他的主从节点都加锁,就不会有点问题?但我不太记得具体API了。”
面试官看了看时间,说:“第一轮先到这里。我们进入第二轮。”
谢飞机擦了擦汗,心想:这面试官怎么总问细节?我那些模板答案怎么不管用了?
第二轮:电商与本地生活服务的微服务挑战
面试官调整了一下坐姿,说道:“我们公司除了音视频,还有电商和本地生活业务。假设你现在要做一个外卖下单系统,涉及订单服务、库存服务、优惠券服务、支付服务。你如何做微服务拆分?服务间怎么通信?”
谢飞机这次学乖了,先想了想:“用Spring Cloud Alibaba,服务发现用Nacos,服务间用OpenFeign。每个服务独立数据库,用Seata处理分布式事务。”他偷偷看了一眼面试官的脸色,好像没刚才那么严肃了。
“嗯,思路没错。那如果用户下单时,先扣库存,再减优惠券,最后创建订单,但支付环节超时了,库存已经扣了,怎么办?”面试官追问。
“这个……可以用本地消息表!就是先在一个事务里更新库存和写消息表,然后通过消息队列通知其他服务。或者用Seata的Saga模式,补偿性事务。”谢飞机记得这些名词,但解释起来有点乱。
面试官没有深究,接着问:“那高并发下,比如秒杀活动,瞬间有十万用户抢购。你的订单服务和库存服务怎么扛?限流怎么做?”
谢飞机来了劲:“用Redis预减库存!先把库存加载到Redis里,请求先走Redis判断是否还有库存,有的话再异步发送消息到Kafka,然后由订单服务消费创建订单。限流可以用Sentinel或者Resilience4j,支持滑动窗口、令牌桶算法。”
“那Sentinel的限流和熔断规则你具体配置过吗?FlowRule和DegradeRule的参数怎么设?”
“呃……我一般是在控制台配的那,比如设置QPS每秒100,超了直接拒绝。熔断就是错误率超过50%主动降级。”
面试官似乎勉强接受,继续问:“支付结果通知是异步回调。你怎么保证幂等性?”
“幂等性……用唯一订单号做唯一索引,回调的时候先查一下状态,如果已经是已支付就不处理。或者用Redis的SETNX锁,设置一个处理中标记。”
“那万一回调重复发送,第一个请求处理到一半,第二个请求也进来了怎么办?你用的是数据库唯一约束还是Redis锁?如果Redis锁过期了呢?”面试官连环追问。
谢飞机有点晕:“那……我就用数据库唯一约束,插入一个支付流水,如果主键冲突就说明已经处理过了。Redis锁过期,就在续期,用Redisson的看门狗……但具体源码我没深入。”
面试官不置可否,转个方向:“我注意到安全方面,你们的支付服务怎么签名的?OAuth2和JWT分别用在什么场景?”
“JWT是无状态token,用在登录认证。OAuth2是授权框架,第三方登录用比如微信登录就是OAuth2。签名的话,我们是用MD5加盐或者RSA私钥签名,然后对方用公钥验签。”谢飞机这次回答总算没太跑偏。
面试官终于点了点头:“嗯,JWT和OAuth2的区别你说得大致对。那Spring Security的过滤器链你用过吗?比如在网关层的Token校验怎么实现?”
谢飞机迟疑了几秒,说:“Spring Security的过滤器链……我大概知道有UsernamePasswordAuthenticationFilter、JwtAuthenticationFilter这些,但让我自己写一个,我可能需要查一下。网关用的Spring Cloud Gateway,可以写GlobalFilter去校验token。”
面试官说:“行,那我们就最后一轮吧。”
谢飞机心里一紧:还有一轮?
第三轮:AIGC与智能推荐——面试官终于露出了獠牙
“你已经扛过两轮了。最后一轮,结合当前热点,我们做AIGC内容社区。用户输入一段文字,后端调用大模型生成图片,同时根据用户行为做个性化推荐。你的技术选型?”面试官眼神中闪过一丝兴奋。
谢飞机深吸了一口气:“这个我了解!最近很火。大模型调用可以用OpenAI的API,或者用HuggingFace的模型。Java的话调用Python服务,可以用gRPC或者HTTP接口。推荐的话……用协同过滤算法?但我没实际写过。”
“不考虑Python服务,纯Java生态。你了解Spring AI框架吗?或者LangChain4j?如果不用这些,你怎么实现和模型交互?”面试官步步紧逼。
“Spring AI?哦,好像是一个新项目。我没实际用过,不过我们知道它是用来对接OpenAI、Azure这些模型API的。LangChain4j也听说,但没深入。纯Java的话,我可以直接用RestTemplate调用HTTP接口,把prompt发过去,然后接收生成的文本或图片URL。”谢飞机勉强挤出几句。
“那生成的图片,用户可能会频繁生成,比如一天几十次。这些数据量怎么存入数据库?图片的元信息、模型参数、提示词,你如何设计表结构?如何做分库分表?”
谢飞机有点崩溃:“这个……我虽然知道分库分表有ShardingSphere,但没真实搞过。就先用MySQL存,然后加索引呗。如果量太大再用ES存?”他自己都觉得不靠谱。
面试官笑了笑:“那内容推荐怎么做?比如用户A收藏了一幅赛博朋克风格的图片,用户B也喜欢。你怎么在Redis里存储他们的偏好,然后推荐相似内容?”
“可以……用Redis的Set存用户偏好标签,然后做交集,找到相似用户,再推荐他们喜欢的图片。用ZSet存图片热度,按分数排序。还可以用Caffeine做本地缓存,减少Redis压力。”谢飞机居然说出了一个可行的思路。
面试官追问:“那把热门图片的详情放Caffeine,冷数据放Redis,再底层内存MySQL,你如何保证Caffeine和Redis一致?如果后台更新了图片,怎么让Caffeine失效?”
“用Spring Cache的@CacheEvict,在更新图片后调用一下,就会删除缓存。Caffeine可以设置一个很短的过期时间,比如几秒,这样不一致窗口很小。”谢飞机回答得倒是挺快。
面试官点了点头,又抛出一个杀手锏:“大模型生成的Prompt,用户可能输入恶意文本。你需要做内容安全过滤。你如何用Java实现一个敏感词过滤?如果调用第三方审核API,超时了怎么办?熔断降级怎么做?”
“敏感词……用AC自动机?把敏感词库加载到内存,构建Trie树,匹配效率是O(n)。如果第三方API超时,我可以设置HttpClient的连接超时和读取超时,比如2秒,如果超过就用Resilience4j的TimeLimiter和CircuitBreaker熔断,直接返回默认审核结果或者人工审核。”
面试官终于露出了一点赞赏之色:“你居然知道AC自动机。那你能手写一个吗?”
谢飞机挠了挠头:“手写……我只能写个大概,就是先建Trie树,然后失败指针,然后匹配。但具体代码我记不全了。”
面试官笑了:“好,谢飞机同学,今天的面谈就到这里吧。你的知识面还可以,但很多技术细节掌握得不够扎实。我们需要一个能深入原理、解决实际问题的工程师。你先回去等通知吧。”
谢飞机站起来,心想:“果然是大厂,问得真细。虽然我凉了,但学到了不少。回去得把Kafka和Redisson源码啃一遍。”
他正要出门,面试官又叫住他:“对了,你简历上写的‘熟悉分布式事务’,能说说Seata的AT模式具体怎么实现的吗?”
谢飞机愣住了,张了张嘴:“那个……就是……通过全局事务管理器,呃……生成前后镜像,然后……嘿嘿,面试官您等着,我回去一定把答案整理出来,下次再来!”
面试官默默关闭了面试房间。
附:完整答案详解——每一轮的技术点和业务场景梳理
为了让像谢飞机一样的小白能真正学到东西,我们把面试中的问题一一拆解,结合业务场景,给出清晰的答案。
第一轮:音视频场景
场景描述:短视频App,用户上传视频,后端需要转码(格式转换、压缩、生成缩略图),支持弹幕实时推送。涉及高并发上传、异步处理、缓存热点数据。
问题1:怎么设计后端技术架构?
答案:
- 上传服务:使用Spring Boot构建一个HTTP接口,接收multipart文件,但不要把大文件直接给后端。通常采用分片上传+断点续传,客户端把文件切成5MB左右的分片,上传完成后由服务端合并。分片信息存在Redis中,比如用Hash记录uploadId -> 分片序号 -> ETag。
- 异步处理:因为转码是耗时任务,收到完整文件后,把转码任务(包括视频路径、分辨率、码率等参数)发送到Kafka。Kafka的Topic可以叫
video-transcode,消费者端部署独立的转码服务(可以单独用Docker容器或K8s Pod),资源与Web服务隔离。 - 转码实现:Java调用FFmpeg命令行,或者构建微服务用ProcessBuilder执行
ffmpeg -i input.mp4 -c:v libx264 -preset fast -b:v 2000k output.flv。生成缩略图可以用FFmpeg的-ss 1 -vframes 1提取某一帧。 - 弹幕推送:使用WebSocket,Spring Boot支持
@ServerEndpoint,但在高并发下需要做集群,解决连接会话在哪个节点的问题。可以用Redis发布订阅或Kafka,将弹幕消息广播到所有WebSocket节点的内存队列,再推给连接在本节点的用户。
问题2:上传量大,消息队列有什么用?如何资源隔离?
答案:
- 消息队列削峰:上传请求先进入网关,写入Kafka,然后立即返回“上传成功,正在处理”。转码服务按自身消费能力拉取消息,即使瞬间产生十万条转码请求,也不会打垮转码服务。
- 资源隔离:Docker容器限制CPU和内存,比如
--cpus=2 --memory=4g。Kubernetes里给转码服务设置resources.limits.cpu: "4"。这样Web服务堆积,也不会影响转码服务;反过来转码服务高负载,也不会拖垮Web服务。
问题3:转码进度如何通知客户端?
答案:
- 推荐使用WebSocket主动推送。客户端建立连接时带上videoId,后端在转码的每个阶段(已上传、转码中、转码成功、转码失败)通过WebSocket发送消息,消息体包含状态码和进度百分比。
- 另外可以提供一个REST API
GET /videos/{id}/status供客户端轮询,但轮询有延迟且浪费资源。优先使用WebSocket,若客户端不支持则降级为轮询。
问题4:Kafka消息不丢失,ack和offset怎么配置?
答案:
- 生产者不丢消息:设置
acks=all(或者-1),只有分区所有ISR副本都收到消息才返回成功。同时设置retries=3和enable.idempotence=true(幂等性)。 - 消费者不丢消息:关闭自动提交(
enable.auto.commit=false),在业务逻辑处理完成后手动提交offset。比如消费转码消息,先执行FFmpeg命令,然后consumer.commitSync()。如果执行失败,不提交offset,下一条消息拉取时会重新消费。 - Broker不丢消息:复制因子
replication.factor=3,min.insync.replicas=2,避免leader故障时数据丢失。
问题5:Redis缓存设计,穿透、击穿、雪崩
答案:
- 缓存结构:视频元信息用Hash存储,字段如
video:123-> {title, author, coverUrl, duration}。点赞数用String存整数字符串,用Redis的INCR/DECR操作。也可以把点赞数写成video:likes:123。 - 缓存穿透:查询一个不存在的videoId,缓存和数据库都没有。解决:布隆过滤器(把所有存在的videoId提前放入),或缓存空值(设置短过期时间,如5分钟)。
- 缓存击穿:某个热点视频过期,大量请求同时打到数据库。解决:互斥锁(
SET lock:video:123 NX EX 10)、热点数据逻辑过期(在value中设置逻辑过期时间,异步刷新)。 - 缓存雪崩:大量key同时过期。解决:过期时间加随机数(比如300~600秒之间),或使用多级缓存(本地缓存+Redis),或对数据库限流降级。
问题6:Redis集群主从切换数据丢失,Redisson分布式锁如何设置?
答案:
- 主从切换丢数据问题:在Redis主从复制是异步的,若主节点故障,未同步到从节点的数据会丢失。使用Redisson的
RedissonMultiLock(联锁)可以同时锁主节点和所有从节点,只要全部加锁成功才算成功。配置方法:创建多个RLock,然后multiLock.lock()。 - 但实际生产更推荐使用Redlock算法(实现于Redisson),它建议对5个独立Redis节点加锁,超过3个成功才算获取锁。确实能提高安全性,但也不能100%保证(存在时钟漂移等场景)。简答时可以说“使用Redisson的联锁,并对关键操作结合Zookeeper或数据库乐观锁做兜底”。
第二轮:电商与本地生活服务
场景描述:外卖下单系统,包含订单、库存、优惠券、支付服务。典型的微服务分布式事务问题、高并发秒杀问题、支付幂等性问题、安全认证问题。
问题1:微服务拆分与服务间通信
答案:
- 按业务域拆分为:
order-service、inventory-service、coupon-service、payment-service、user-service。每个服务独立数据库,避免跨库关联查询。 - 服务间通信:同步调用用OpenFeign + Spring Cloud LoadBalancer(或Ribbon);异步调用用Kafka/RabbitMQ。比如下单时,订单服务调用库存服务扣减库存,同步;扣减成功后再向订单状态变更Topic发送消息,由支付服务监听触发支付流程。
- 分布式事务:优先使用最终一致性方案,如本地消息表、事务消息(RocketMQ)或Seata的AT/TCC模式。严格一致性场景(如支付扣款)用TCC或Saga。
问题2:支付超时,库存已扣了怎么办?
答案:
- 本地消息表方案:在订单服务里,将“扣库存”和“写消息表”放在同一个本地事务中(步骤:更新库存扣减量,插入一条状态为NEW的消息)。然后后台任务把消息发送给MQ,消费者处理后续逻辑;如果处理失败,MQ重试;重试多次失败则调用补偿接口(如释放库存)。
- Seata Saga模式:定义正向服务(扣库存、减优惠券、创建订单)和反向补偿服务(回滚库存、恢复优惠券、取消订单)。通过状态机编排,任何一步失败,自动执行已经完成步骤的补偿操作。
- 实际面试时不能说“用Seata就完事”,要具体说:Saga适合长事务,但无隔离性,需要业务层防脏读。例如扣库存时加“冻结库存”,支付超时后解冻。
问题3:秒杀场景高并发,限流怎么做?
答案:
- 四层防护:
- 前端:按钮置灰、随机延时、验证码。
- 网关:Sentinel或Spring Cloud Gateway全局限流,按用户ID限流(如每秒1次)、按IP限流(如每秒10次)。
- Redis预减库存:秒杀前把库存预热到Redis(
SET seckill:stock:1 100)。请求进入时执行Lua脚本原子性操作:if redis.call('GET', key) <= 0 then return 0 end return redis.call('DECR', key)如果返回0,直接返回“已抢光”。减库存成功后再把用户ID和数据写入Kafka,异步创建订单和扣减DB库存。
- 数据库限流:防止超卖,
UPDATE inventory SET stock = stock - 1 WHERE id = ? AND stock > 0,影响行数为1才成功。
- Sentinel配置:FlowRule设置资源名、QPS阈值(例如200)和拒绝策略(直接拒绝或预热)。DegradeRule设置熔断条件(如异常比例超过0.2,熔断10秒)。
问题4:支付回调的幂等性
答案:
- 支付平台(如微信、支付宝)会多次异步回调,同一订单可能最多发几十次通知。服务端必须幂等。
- 方案:在
payment_notify表创建唯一索引(order_id, transaction_id)。回调处理器首先查询该表:- 如果存在,说明已处理,直接返回“SUCCESS”。
- 如果不存在,则尝试插入,若插入成功,则继续更新订单状态;若插入时发生主键冲突,并发重复请求到了,则回滚并进行查询,确认已经处于处理状态后返回成功。
- 也可以使用Redis的
SET order:pay:123 1 NX EX 120,但Redis锁过期可能造成并发问题,最好持久化唯一约束作为兜底。
问题5:JWT、OAuth2和签名
答案:
- JWT:一种无状态Token,由Header.Payload.Signature组成。适合单点登录,但无法主动失效,因此需要配合黑名单或短过期时间。用于认证“你是谁”。
- OAuth2:授权框架,解决第三方应用如何获得用户授权。比如微信登录:用户跳转微信授权页,微信返回code,后端用code换取access_token。用于授权“允许谁访问”。
- 两者关系:OAuth2可以用JWT作为access_token的格式。Spring Security OAuth2里,JwtBearerTokenAuthenticationConverter可以解析JWT。
- API签名:商户平台对接支付网关时,通常用私钥对请求参数(按字典序拼接,加nonce和时间戳)做RSA签名,支付网关用公钥验签,防止篡改和重放。MD5加盐更弱,推荐RSA/SHA256withRSA。
问题6:Spring Security过滤器链和网关Token校验
答案:
- Spring Security的过滤器链核心是
FilterChainProxy,常见过滤器有:SecurityContextPersistenceFilter(恢复上下文)、UsernamePasswordAuthenticationFilter(处理表单登录)、JwtAuthenticationFilter(自定义,解析Token)、ExceptionTranslationFilter、AuthorizationFilter(鉴权)。可以自己实现一个OncePerRequestFilter,在doFilterInternal里解析HTTP Header中的Authorization: Bearer xxx,把Authentication对象保存到SecurityContext。 - 在微服务网关(Spring Cloud Gateway)中,可以使用
GlobalFilter+Ordered。实现思路:获取请求头token,调用用户服务校验(或本地JWT解析),如果合法则把用户ID写入请求头转发给下游,不合法则返回401。同时网关也可以做白名单校验(如/login、/register不用token)。
第三轮:AIGC与智能推荐
场景描述:AIGC内容社区,用户输入prompt,后端调用大模型生成图片;同时根据用户行为做个性化推荐。涉及调用外部AI服务、内容安全、缓存一致性、推荐算法基础。
问题1:Java如何调用大模型?Spring AI和LangChain4j是什么?
答案:
- Spring AI:Spring官方提供的AI框架,类似Spring生态的AI客户端。支持ChatModel、TextToImageModel等。比如:
@Service public class AiService { private final OpenAiChatModel chatModel; public String generateImage(String prompt) { return textToImageModel.call(prompt); } }通过自定义
RestTemplate或WebClient,调用OpenAI的/v1/images/generations接口,把prompt和参数(size、n)放进JSON请求体,解析返回的图片URL。 - LangChain4j:Java版LangChain,支持面向AI模型的可组合能力、RAG、记忆等,但实际生产中也还是走HTTP。
- 如果不用框架,可以用
RestClient来发HTTP请求,再配合Jackson解析JSON。需要注意超时:连接超时2秒、读超时30秒(因为图片生成慢)。 - 为了不阻塞主线程,应该用异步
WebClient调用,或者把请求抛出到消息队列,由专门的工作线程池处理,再由WebSocket推送生成结果给用户。
问题2:数据量大、分库分表怎么设计?
答案:
- 表设计:
prompt_record(提示词记录):id、user_id、prompt_text、model_name、parameters_json、create_time。image_record(生成图片记录):id、record_id、object_key(OSS地址)、width、height、size、status(生成中/成功/失败)、audit_status(未审核/通过/拒绝)、create_time。
- 分库分表:用户量大后,按
user_id对数据库分片(比如32个库,每个库64张表,库号 = user_id % 32,表号 = (user_id / 32) % 64)。同时create_time作为二级索引的分页查询如果用ES,就能避免跨库分页问题。 - 不要用
SELECT *,尽量只查热点字段。元信息放MySQL,大文本(prompt原文、生成参数JSON)可以存到MongoDB或OSS,MySQL只存关联ID。
问题3:基于Redis做个性化推荐
答案:
- 用户偏好标签:用户收藏图片时,给图片打标签(如“赛博朋克”、“落日”、“机械姬”),在Redis中维护一个
Set:user:tags:123里添加cyberpunk、anime等。 - 相似用户:用
SINTER user:tags:123 user:tags:456获取共同标签,定义相似度为交集大小。然后找最相似用户的喜欢图片,也存成Set:user:likes:456。 - 候选推荐:把相似用户喜欢的图片ID取出来,过滤掉当前用户已看过的(可以用
SISMEMBER),然后给图片分数排序。分数可以用ZSet:recommend:images:123中每个图片的分数按喜欢人数×0.6 + 标签匹配度×0.4计算。 - 本地缓存:热门的图片详情用Caffeine缓存,冷数据和用户行为用Redis,底层MySQL落库。注意,Caffeine缓存和Redis之间的数据一致性:在更新图片时,使用
@CacheEvict删除本机Caffeine,同时发布Redis消息,其他服务监听到后删除各自的Caffeine缓存。或者设置Caffeine很短的过期时间,比如5秒,降低不一致窗口。
问题4:敏感词过滤和第三方审核API熔断
答案:
- 敏感词过滤:使用AC自动机(Aho-Corasick)。将敏感词库建立Trie树,每个节点有fail指针指向失配时跳跃的位置。匹配时遍历文本字符,根据fail指针跳转,可以在O(n)时间内找出所有敏感词。Java实现伪代码:
class AcNode { Map<Character, AcNode> children = new HashMap<>(); AcNode fail; boolean isEnd; // 是否为一个敏感词的结尾 int length; // 词长 } public boolean containsSensitive(String text) { AcNode cur = root; for (char ch : text.toCharArray()) { while (cur != root && !cur.children.containsKey(ch)) { cur = cur.fail; } cur = cur.children.containsKey(ch) ? cur.children.get(ch) : root; for (AcNode p = cur; p != root; p = p.fail) { if (p.isEnd) return true; } } return false; }实际可结合第三方审核(如阿里云内容安全)做图片和文本的机审+人工审核。
- 超时熔断:使用HttpClient时设置
connectTimeout(2s)、readTimeout(2s)。使用Resilience4j:TimeLimiter设置最大调用时间2秒;CircuitBreaker设置滑动窗口大小(比如10秒内50%请求失败则熔断);熔断后返回本地默认结果,比如auditResult="pass"(低风险场景)或转人工审核队列。Bulkhead(隔离)限制并发调用数量,比如最大并发10个,超出就排队或快速失败。
问题5:AC自动机能手写吗?
答案:
- 面试官不要求你立刻写500行代码,但你需要说出构建过程:
- 构建Trie树:插入每个敏感词,路径上的字符作为分支。
- 构建失败指针:BFS遍历Trie树。根节点的孩子fail指向root;其他节点:假设当前节点为p,父节点为f,p是f的孩子字符为c。则看f.fail有没有字符为c的孩子,有则p.fail = f.fail.children.get(c),否则继续向上。直到root。
- 匹配:从root开始,遍历文本字符,如果当前节点有该字符的子节点,就往下走;否则跳到fail,直到找到匹配路径或回到root。每次到达节点,从该节点沿fail链检查是否有isEnd,如果有则说明命中敏感词。
- 总结:用空间换时间,适用于敏感词量不大的场景(几千条)。如果几百万词条,则考虑更高效的双数组Trie(Double-Array Trie)。
面试官最后一句“回家等通知”说明了什么?
谢飞机虽然表现了不少亮点(能说出AC自动机、Respire4j)但总体深度不足。面试官在结束时补充的那个问题其实就是一种含蓄的否决:“你简历上写的熟悉分布式事务,但我一问细节你就答不上来。” 所以,大家在校招或社招面试时,不要背干巴巴的八股文,一定要掌握技术背后的原理,并且结合项目/场景去说清楚。否则下一轮谢飞机就是你。
不过没关系,谢飞机回去之后,把这些问题都整理成了文档,又买了几本《Kafka权威指南》《Redisson源码解析》,开始扎扎实实地啃源码。下次面试,他一定能挺进最后一轮!祝他好运。