
谢飞机大闹大厂面试从音视频到高并发一场Java技术栈的极限拉扯“下一个谢飞机。”面试官老马推了推眼镜看着手里的简历又看了看眼前这个头发比代码还乱的小伙子。谢飞机两年前端“切图仔”自称转战Java后端简历上写着“精通”两个字后面跟了长长一串技术名词从Java SE到Kubernetes从Hadoop到Web3.0仿佛整个互联网的技术栈都写进了这页A4纸。“坐吧。先做个自我介绍。”老马语气平淡。“面试官好我叫谢飞机之前主要做前端后来觉得后端才是技术的灵魂尤其是Java所以自学了Spring Boot、Redis、消息队列等等。平时也爱研究JVM调优什么G1、ZGC都略懂一二。我的目标是成为架构师所以各种技术都有涉猎。”谢飞机挺直腰板眼神里闪烁着迷之自信。老马微微点头没有急着拆穿而是从最基础的问题开始“行那就先聊聊你熟悉的Java SE。项目里你用的是Java 8还是11或者17这几个版本你了解哪些重要的更新”谢飞机眼睛一亮这题我会啊“我用过Java 8也看过Java 11的文档。Java 8有Lambda表达式和Stream流特别好用现在写代码都离不开。Java 11好像在垃圾回收上有些优化Java 17是LTS版本好像……加了一些新特性但具体我记不太清了。不过我平时都是写Java 8的代码所以对11和17的新特性没有深入实践。”谢飞机挠了挠头有点心虚。老马没有继续追问而是顺着他的项目经验往下问“我们有个业务场景是做音视频处理比如用户上传一个视频后台需要转码、切片、生成缩略图还要记录任务进度。这种场景如果用Java做你会怎么设计线程池你了解吗怎么避免线程池里的任务堆积把内存撑爆”“哦这个我熟处理视频可以用FFmpegJava里可以调命令行。线程池的话我用过ThreadPoolExecutor核心线程数、最大线程数、队列容量这些参数我都设置过。防止内存爆掉的话……可以把队列设置成有界队列放不下的任务就拒绝或者用CallerRunsPolicy让提交任务的线程自己执行。嗯大概就是这样。”谢飞机说得头头是道但眼神里有一丝闪烁。老马又追问“如果任务真的非常多队列满了而你又不想丢任务除了拒绝策略还有什么办法比如用消息队列削峰填谷Kafka或者RabbitMQ了解吗”谢飞机愣了一下“消息队列我了解Kafka高吞吐RabbitMQ消息可靠。可以把视频转码任务先发到Kafka然后消费者慢慢处理。但是……Kafka的消费者组是怎么分配分区的我有点记不清好像有Range和RoundRobin策略吧反正能消费就行。”他打了个哈哈试图蒙混过关。第一轮面试在“懂但又不完全懂”的氛围中结束了。老马心里已经有了初步判断这兄弟基础不扎实但至少有些概念。于是进入第二轮这次他换了一个场景。“我们还有个电子商务平台双十一大促的时候流量非常高用户下单、库存扣减、订单状态更新。你会怎么设计缓存来抗住高并发Redis你知道吧你用Redis存什么怎么解决缓存穿透、击穿、雪崩”谢飞机听到“电商”两个字立刻精神了“用Redis做缓存把热点商品数据提前放进去用户请求先查缓存缓存没命中再查数据库。缓存穿透可以……用布隆过滤器对把存在的ID都放到布隆过滤器里不存在的ID直接被过滤掉。缓存击穿就是热点key过期可以在过期时间上加上随机值或者用互斥锁只让一个线程去查数据库。缓存雪崩就是大量key同一时间过期可以设置不同的过期时间或者用集群、哨兵来保证高可用。这些我都知道”谢飞机说得眉飞色舞自以为答得完美。老马没有夸他反而问“那么Redis用了分布式缓存本地缓存还需要吗比如Caffeine或者Ehcache你们怎么组合使用如果Redis里的数据更新了本地缓存怎么同步还有Redis和数据库的数据一致性怎么保证先更新数据库再删缓存还是先删缓存再更新数据库”谢飞机被问得有点发懵他开始结巴“呃……本地缓存……我好像用过Ehcache但没在正式项目里用。一致性的话……一般都是先更新数据库再删除缓存吧但是如果有并发请求可能中间有短暂的不一致不过可以通过……设置很短的过期时间来解决对加个随机过期时间保证最终一致。”他越说声音越小自己也觉得不太靠谱。老马叹了口气转到一个稍微简单点的问题“那Spring的声明式事务你用过吗Transactional注解它的失效场景有哪些比如同一个类里方法自调用事务会失效吗”“啊这个我记得Transactional遇到异常回滚默认是RuntimeException如果是checked exception要配rollbackFor。自调用……好像会失效因为Spring的代理是通过AOP实现的自调用不走代理所以事务不生效。对就是这个”谢飞机激动地抢答道生怕自己表现得太差。老马点了点头“嗯这个知识点你倒是记住了。那再深入一点事务传播机制REQUIRED和REQUIRES_NEW有什么区别如果一个外层方法调用了内层方法内外都加了Transactional(propagation REQUIRED)内层方法出异常外层方法捕获了事务会回滚吗”谢飞机愣住了他张了张嘴半天憋出一句“REQUIRED……就是有事务就加入没有就新建。REQUIRES_NEW就是……新建一个事务挂起当前事务。异常被外层捕获的话……内层如果抛异常事务会被标记为rollback-only所以即使外层捕获了提交的时候还是会报错好像是这样的……我不太确定我当时用的时候没遇到这种情况。”“好了第二轮结束。我们进入第三轮。”老马看了一眼时间决定进行最后几个问题。“场景换一下。假设我们要做一个企业协同办公SaaS系统这里面有多个微服务比如用户服务、审批服务、文档服务。服务之间需要互相调用还需要实现服务注册发现、负载均衡、熔断降级。你们用什么组件Spring Cloud了解吗Eureka还是NacosOpenFeign怎么用Resilience4j怎么配置”谢飞机一听“微服务”又来了精神“我们项目用了Spring Cloud服务注册用Eureka后面好像不维护了也听说过Nacos。服务间调用用OpenFeign定义接口加注解就行。熔断的话……之前用过Hystrix后来停更了现在用Resilience4j吧它有个CircuitBreaker可以配置失败率阈值超过阈值就打开熔断器。但具体配置参数……比如滑动窗口大小、最小调用次数、慢调用比例这些……我有点记不清了。反正原理就是熔断、降级、限流让服务不被打挂。”谢飞机故作镇定。“好那再问一个关于安全的问题。我们SaaS系统里用户需要登录你用过Spring Security吧JWT怎么生成和校验OAuth2授权码模式流程是怎样的如果让你做一个单点登录你会怎么设计”“Spring Security……用过它有一套过滤器链可以配用户名密码登录。JWT就是jsonwebtoken用私钥签名公钥验签。OAuth2授权码模式……就是用户浏览器跳到授权服务器登录后授权然后拿到code再用code换token最后用token访问资源服务器。单点登录的话可以用一个独立的认证中心各个服务都去认证中心验证token用Redis存session或者token。大概……就是这么个流程。”谢飞机一口水都没喝硬顶着回答。老马面无表情地听完最后问了一个终极问题“好最后一个场景。我们有个物流系统每天产生海量的轨迹数据需要实时统计还要做智能路由分析。用Spark或者Flink处理过数据吗Elasticsearch用来做什么如果让你选你会怎么设计这个数据管道”谢飞机这下彻底蔫了“Spark和Flink……我只看过教程没有真正写过作业。大数据这块……我了解Hadoop的HDFS和Map Reduce但项目里没用到。Elasticsearch倒是用过一点就是把日志存进去用Kibana画个图。至于实时统计……应该用Flink吧……但具体怎么搞不清楚。”他低下头声音越来越小。老马合上简历喝了一口保温杯里的茶然后缓缓说道“谢飞机面试环节到这里就结束了。你的知识面确实很广从Java SE到微服务从Redis到Kafka张口就来这一点值得肯定。但是很多核心原理你只是一知半解在遇到真正的业务问题和技术挑战时很难给出可靠的设计方案。我们会认真评估也可能需要你回家等通知——具体结果下周之前HR会联系你。”谢飞机如释重负地站起来走到门口又回过头不死心地问“那……面试官您能告诉我那几个我答得模棱两可的问题正确答案到底是什么吗我想学习一下。”老马愣了一下居然露出了一丝笑容“行这个问题比前面所有回答都有诚意。那我就把今天的问题和答案系统地给你讲一遍。回去好好看说不定下次还有机会。”答案与详细解析第一轮Java SE与音视频任务处理1. Java 8 / 11 / 17版本核心区别Java 8里程碑版本引入了Lambda 表达式、Stream API、Optional、新日期时间 APIjava.time、接口默认方法、方法引用等。这也是目前绝大多数企业仍然使用的版本。Java 9引入了模块化系统JPMS但没有直接改变日常写业务代码的方式。Java 11属于LTS版本相对于Java 8的主要增量包括String 新增 repeat、lines、isBlank、strip等便捷方法。ZGC实验性低停顿垃圾回收器。HTTP Client 标准化HttpClient可以替代第三方OkHttp或HttpClient。移除 Java EE 模块如 JAXB、JAXWS需要单独引入依赖。Java 17也是LTS版本相对于Java 11的主要增量包括密封类sealed class限制继承的类集合。模式匹配 instanceof 改进可以简化类型检查和变量转换。switch 表达式增强从Java 14开始成为正式特性。新增Stream.toList()方法将流收集为不可变列表。恢复和增强向量 API、外部函数接口等孵化/预览。JEP 391macOS/AArch64 支持。在面试中至少要知道Java 8是主战场Java 11/17是LTS并且每个版本都有哪些关键功能尤其是String、Stream、集合上的便捷改进。2. 音视频转码任务的设计与线程池音视频处理是典型的计算密集型 IO密集混合任务。推荐思路使用消息队列作为任务缓冲Kafka / RabbitMQ实现流量削峰。前端上传视频后只把任务ID发送到MQ后台消费者订阅并处理。消费者接收到任务后启动一个线程池或者进程去执行 FFmpeg 命令行。注意FFmpeg本身是外部进程不是纯Java线程因此需要管理好进程生命周期防止僵尸进程。线程池参数核心线程数 CPU核数 1计算密集型或 2 * CPU核数IO密集型。最大线程数根据可接受的最大并发任务数设置。队列使用有界队列如ArrayBlockingQueue避免任务无限堆积导致 OOM。拒绝策略根据业务选择AbortPolicy抛异常一般不用。CallerRunsPolicy让提交任务的线程如消费者自己执行天然施加反向压力。DiscardOldestPolicy丢弃最老的任务适合允许丢任务的场景不适用转码。进一步防止内存爆炸不要把所有任务都加载到内存利用数据库或Redis记录任务状态只有任务内容很小才放入内存队列。使用背压机制如果MQ消费速度远快于处理速度可以通过暂停消费如暂停Kafka Consumer或动态线程池来调节。3. Kafka 消费者组与分区分配Kafka中一个消费者组内每个分区只能被一个消费者实例消费。分区分配策略主要有三种Range范围分配按照主题的分区按序号排序消费者按顺序取一段区间。默认策略可能导致分配不均衡。RoundRobin轮询分配把分区逐个轮流分配给消费者均衡性更好。Sticky粘性分配在保持上次分配结果的基础上尽量均衡避免rebalance后大量分区移动。实际生产中分区数量应大于消费者实例数才能充分利用并行性。配置partition.assignment.strategy可以选择策略如果消费者数量变化会触发rebalance。第二轮电商缓存与事务一致性1. 缓存穿透、击穿、雪崩的解决方案缓存穿透查询不存在的key每次都落到数据库。解决方案缓存空值设置较短过期时间如5分钟。布隆过滤器将所有可能存在的主键ID存放在Bitmap中查询前先经过布隆过滤器判断一定不存在的直接拒绝。布隆过滤器缺点有误判率判断存在时可能不存在判断不存在时一定不存在。缓存击穿某个热点key过期瞬间大量请求打到数据库。解决方案互斥锁分布式锁只让一个线程重建缓存其他线程等待。逻辑过期不设置物理过期时间而是存储一个逻辑过期时间异步更新。提前续期后台定时任务刷新热点key的过期时间。缓存雪崩大量key同时过期或Redis宕机导致数据库压力暴涨。解决方案过期时间加随机值避免同一时刻集中失效。多级缓存Redis 本地缓存Caffeine/EhcacheRedis挂掉时本地缓存还能撑一阵。Redis高可用主从哨兵或Cluster集群。熔断降级数据库不可用时直接返回默认值或缓存旧值。2. 本地缓存与Redis的同步本地缓存Caffeine位于应用内存访问极快但存在多实例间数据不一致的问题。组合策略一级缓存本地Caffeine二级缓存Redis。查询时先查本地Miss后查RedisRedis Miss再查数据库。更新策略在一个服务实例上更新本地缓存后通过消息广播如Redis Pub/Sub通知其他实例删除对应key。Redis Pub/Sub可监听失效通知但不可靠更好的方案是在Redis中存储一个版本号每次更新数据库时递增版本号本地缓存带有版本发现不匹配则失效。常见框架Spring Cache 配合 Caffeine Redis 做二级缓存但默认没有同步机制需要自己扩展。3. Redis与数据库的数据一致性主流方案Cache Aside Pattern旁路缓存。读先读缓存Miss后读数据库然后填充缓存。写先更新数据库再删除缓存。为什么不是先删缓存再更新数据库因为先删缓存在更新数据库之前有旧请求会把旧数据读进缓存导致缓存是旧的而先更新数据库再删除缓存即使删除失败可以通过延迟双删先删缓存再更新数据库睡眠一小会儿再次删缓存来兜底。更可靠的方式订阅数据库的binlog如Canal异步删除缓存或者通过可靠消息重试。最终一致性这里要求的是最终一致无法保证强一致。如果业务强一致要求高不应该使用缓存直接读数据库或者使用分布式事务但这会降低性能。4. Transactional 事务失效场景与传播机制失效场景常见有方法不是publicSpring AOP默认基于动态代理无法拦截非public方法。类没有被Spring管理没有Component等注解。方法自调用同一个类里this.a()调用带事务的b()不经过代理对象。异常被捕获且没有抛出。抛出的不是RuntimeException且没有指定rollbackFor。数据库引擎不支持事务MyISAM。使用了final修饰导致CGLIB无法生成子类代理。传播机制重点REQUIRED默认如果当前存在事务则加入否则新建。内外都标注REQUIRED时两者在同一事务中。内层方法抛异常即使外层catch住由于事务已经被标记为rollback-only外层提交时仍会抛UnexpectedRollbackException并回滚。REQUIRES_NEW挂起当前事务新建一个独立事务。内层事务的回滚不会影响外层事务提交因为事务是独立的。但如果外层在调用内层方法之前已经做了数据库操作内层独立提交外层之后回滚那么内层的数据仍然会提交——这一点需要特别小心。第三轮微服务、安全与大数据1. 服务发现注册与负载均衡EurekaSpring Cloud Netflix经典组件服务启动时向Eureka Server注册自己的IP和端口其他服务从Eureka拉取服务列表基于客户端发现方式通过Ribbon或Spring Cloud LoadBalancer做客户端负载均衡。Eureka 2.0停止开发目前主流使用Nacos阿里或Consul。Nacos支持服务注册发现、配置管理支持CP和AP模式切换。国内使用率高。OpenFeign声明式REST客户端定义接口并加FeignClientFeign会动态生成代理类通过HTTP调用远程服务。底层整合了负载均衡器可以自动从注册中心拉取服务列表。Consul类似Eureka但基于Raft一致性协议支持健康检查、KV存储。2. Resilience4j 熔断降级Resilience4j是一个轻量级容错库包含CircuitBreaker断路器统计一定时间窗口内的失败率、慢调用率超过阈值则熔断。关键参数failureRateThreshold失败率阈值默认50%。slowCallRateThreshold慢调用率阈值默认100%关闭。slowCallDurationThreshold慢调用定义如超过1秒。slidingWindowSize滑动窗口大小控制统计的最近请求数。minimumNumberOfCalls触发熔断前的最小调用次数。waitDurationInOpenState熔断后到半开状态的等待时间。Bulkhead隔舱限制并发调用数防止一个服务耗尽线程池。RateLimiter限流限制每秒调用次数。Timelimiter超时设置调用超时时间。替代Hystrix的另一个可选库是Sentinel阿里开源的流量控制组件。3. JWT 与 OAuth2JWTJSON Web Token由三部分组成Header算法、Payload声明如用户ID、过期时间、Signature签名。一般流程服务器用私钥签名客户端持有token。客户端每次请求携带Authorization: Bearer token。服务器用公钥验签验证通过后从token中解析用户信息。优点无状态适合分布式系统缺点无法主动注销需要搭配黑名单或短过期时间refresh token。OAuth2授权码模式适用场景第三方应用代表用户访问资源。用户访问第三方应用第三方应用重定向到授权服务器如Keycloak。用户登录并同意授权。授权服务器返回授权码code重定向回第三方应用。第三方应用用code clientId clientSecret 去授权服务器换tokenaccess token refresh token。第三方应用用access token访问资源服务器。单点登录SSO通常使用一个认证中心所有服务统一跳转到认证中心登录认证中心发放全局会话token或Session其他服务通过验证token来判断用户是否已登录。可以用JWT 密钥共享也可以用Redis保存session统一校验。Spring Security OAuth2 Keycloak可以方便地搭建SSO。4. 大数据处理与物流数据管道数据管道设计数据采集日志采集使用Logstash/Flume/Filebeat或者直接从消息队列接收业务数据。消息缓冲海量轨迹数据先发送到Kafka以支撑高吞吐。实时计算使用Flink消费Kafka做实时统计如车辆位置、轨迹里程、异常停留等。Flink提供事件时间处理、窗口、状态管理和精确一次语义Exactly-Once。批量计算如果要做历史数据清洗可以使用SparkDataFrame / Spark SQL适合离线数据仓库。存储与查询Elasticsearch用于快速全文搜索、地理位置搜索Geo适合物流轨迹的检索和分析。Cassandra分布式NoSQL适合大量写、读场景的物联网轨迹数据。HBase / ClickHouse分别适合随机实时读写和聚合分析。Spark vs FlinkSpark是基于批处理的微批Micro-batch处理延迟通常为秒级适合离线与延迟要求不高的实时分析。Flink是真正的流式处理低至毫秒级别的延迟支持事件时间和窗口适合实时风控、实时大屏、实时预警等场景。5. 其他衍生知识DTO属性映射可以使用MapStruct在编译期生成映射代码比反射的BeanUtils性能高很多。参数校验Spring Boot中常用Validated 注解如NotNull、Size。连接池选择HikariCP是目前最快的JDBC连接池Spring Boot默认使用。C3P0是古老产品配置复杂性能较低已不推荐。数据库迁移Flyway和Liquibase可以管理数据库脚本版本推荐在项目中用于自动执行DDL/DML变更。模板引擎Thymeleaf适合服务端渲染HTMLFreeMarker和Velocity更灵活可以生成代码、邮件模板。使用JSP在Spring Boot中相对冷门因为嵌入式Tomcat支持不如独立部署方便。REST文档使用Swagger/OpenAPI自动生成API文档springdoc-openapi是Spring Boot 3中的常用库。测试JUnit 5 Mockito 做单元测试AssertJ 提供更加流式的断言Cucumber 做BDD行为驱动测试Selenium 做Web前端自动化测试PowerMock 可以mock静态方法、构造函数但由于绕过JVM访问限制建议尽量使用Mockito的 inline mockmaker。日志SLF4J是门面Logback是Spring Boot默认实现。Log4j2在性能异步和异常处理上优于Logback但要注意漏洞版本问题。Tinylog适合小项目或轻量应用。监控Spring Boot Actuator Micrometer暴露指标Prometheus抓取Grafana展示链路追踪用Jaeger或Zipkin配合OpenTelemetry。CI/CDJenkins 是经典流水线工具GitLab CI 内置在代码仓库GitHub Actions 更云原生化Docker容器化 Kubernetes编排是部署方案的基础能力。序列化Jackson是老牌JSON库Gson轻量Protobuf和Avro是二进制序列化用于RPC和数据传输体积小、性能高。gRPC默认用Protobuf。“怎么样谢飞机听明白了吗”老马在虚拟的屏幕后问道。谢飞机擦了擦汗抱着这些答案决定回家好好啃书。他暗下决心下次面试绝不能再让“回家等通知”成为唯一的结果。至于老马会不会再给他机会谁知道呢也许这是他职业生涯里最值得的一次失败。