Day34 笔试+面经

快手外包

  1. KafKa的组成模块,怎么保证消息不重复推送

    Producer生产者,负责向KafKa Topic发送消息 Consumer消费者,从订阅的topic拉取消息 Broker服务节点,负责接受Producer消息,存储日志,响应Consumer请求 Topic主题,消息的逻辑分类 Partition分区,Topic的物理分片,有序不可变的消息序列 Replica副本,Leader Replica负责对外提供读写服务,Follower Replica负责故障转移 Zookeeper/Kraft 元数据管理 Controller控制器,集群中的特殊Broker 如何保证消息不重复推送?

    1. 针对每个消费者组,只会推送一次
    2. Idempotent幂等推送
  2. Redis分布式锁的实现原理,为什么选择使用Redis而不使用Zookeeper?

    核心就是 set key value px nx; zookeeper没接触过,我就答了一句zookeeper太重了,实际参考以下对比

维度RedisZooKeeper
性能⭐⭐⭐⭐⭐ 极高(10w+ QPS)⭐⭐ 较低(写操作需 ZAB 协议同步,通常 < 1w QPS)
延迟微秒级毫秒级(强一致性代价)
部署复杂度简单(单机/哨兵/Cluster)复杂(需奇数节点集群,JVM 调优)
资源消耗内存为主,轻量CPU + 磁盘 + JVM,较重
适用场景高并发、短时锁(如秒杀)强一致性、长时锁(如 Master 选举)
锁特性最终一致(AP 系统)强一致(CP 系统)
客户端支持丰富(Java/Go/Python 等)主要 Java(ZK 原生协议复杂)
  1. CPU飙高怎么排查
bash
复制代码
# 确认CPU飙高范围 top # 定位CPU飙高的进程 top -c # 查看高CPU进程里使用最高的线程 top -H -p <pid> # 或者 pidstat -t -p <pid> 1 3 #每秒采样,共三次 # 将线程ID抓换成16进制格式,使用jstack抓取线程堆栈 jstack <pid> | grep -A N <jpid>

其实可以直接arthas(当时我答这个,面试官说不行,给我唬住了)

bash
复制代码
# 一键定位高 CPU 线程 thread -n 3 # 显示 CPU 使用率 Top 3 的线程堆栈 # 直接查看方法耗时 trace com.example.Service method

常见的根因及方案(来源于AI)

根因现象优化方案
死循环线程 RUNNABLE,堆栈停在业务代码加 break 条件、限流
正则回溯Pattern.compile().matcher().find() 卡住java.util.regex 优化或替换为 DFA 引擎
频繁 Full GCjstat 显示 GC 时间占比高内存泄漏分析(jmap -histo)、调优 GC
HashMap 并发多线程 put 导致死循环改用 ConcurrentHashMap
日志同步打印堆栈卡在 log.info()改异步日志(Log4j2 AsyncAppender)
加密/解密堆栈在 Cipher.doFinal()缓存结果、用硬件加速
  1. JVM的内存模型了解吗?简单说说垃圾回收策略,为什么JDK11摒弃了标记-清除和标记整理,只用复制呢?
text
复制代码
┌───────────────────────┐ │ 方法区 (Metaspace) │ ← 存储类信息、常量、静态变量(JDK8+ 用 Metaspace 替代永久代) ├───────────────────────┤ │ 堆 (Heap) │ ← 对象实例分配区域(GC 主战场,分新生代 + 老年代) └───────────┬───────────┘ │ ┌───────────▼───────────┐ │ 虚拟机栈 (Stack) │ ← 方法执行的栈帧(局部变量、操作数栈) ├───────────────────────┤ │ 本地方法栈 │ ← Native 方法调用 ├───────────────────────┤ │ 程序计数器 │ ← 当前线程执行字节码的地址 └───────────────────────┘
text
复制代码
(三种)基础回收策略: 1. 标记-清除:标记存活对象,清除未标记对象,会产生大量内存碎片 2. 标记-整理:标记存活对象,把活着的对象往一边推,边界之外的就清理,解决了内存碎片化的问题,但耗时较长 3. 标记-复制:把内存分成两办,平时只用一半,标记存活对象往另一半移,未标记的对象留在原区域做清理,高效简单,但是利用率低 (两种)扩展回收策略: 1. 分代回收:新生代用复制,老年代用标记-整理/清除 2. 并发回收:

至于这第三问嘛,咱也不知道JDK11有啥变化,既然他问为什么用复制,我就把复制的优点讲了一下,也不知道对不对(感觉问得也不对吧,真的有抛弃标记-清除/整理吗?) 5. SQL慢查询,除了服务端开启慢查询日志外,你要怎么发现?(从JDBC层面)

text
复制代码
1. 开始是问我对慢查询做了哪些优化,是怎么发现慢查询的 答:索引优化,多表关联拆成内存处理,关联查询优化等,公司有监控工具,出现慢查询,大长事务等会发告警通知,以及在实际使用系统过程、测试过程中发现接口耗时长就会去找原因。 2. 如果不用公司监控呢,你要怎么做? 答:开启慢查询日志 3. 如果不用慢查询日志呢? 答:在查询开始之前取当前时间戳,查询结束之后取时间戳相减得到耗时,根据公司的慢查询执行时间定义判断是否为慢查询;如果想要无业务入侵,用AOP定义个切面就好了。 4. 如果这些手段都不能用,让你从JDBC层面做怎么办? 答:这个我不太清楚了,AI回答太长我就不粘了,自行查找吧
  1. 讲讲事务的隔离级别,串行化读怎么实现?

image.png

加S锁就行

总结

毕竟外包嘛,又是年底特殊时候,面试官的要求就不会有这么高了,而且看他也挺急着回家的样子,所以简单问几个草草了事,咱也不要太在意结果,外包就是刷面试经验的,没这么强的技术底气就以量取胜。


飞猪笔试

  1. 给出一组字符串,找到每两个w之间的字符串,并对结果做归并排序后输出(滑动窗口滑不动了[流泪]归并排序又给忘了)
java
复制代码
public class findAndSort { private static List<String> res = new ArrayList<>(); public static void main(String[] args) { String s = "yhswhuihijwabdjwjiad"; System.out.println(s.length()); List<String> andSor = findAndSor(s); System.out.println(andSor.stream().collect(Collectors.joining(","))); } public static List<String> findAndSor(String s) { find(s); return mergeSort(res); } // 归并排序没实现 private static List<String> mergeSort(List<String> list) { if (list.size() <= 1) { return new ArrayList<>(list); } int mid = list.size() / 2; List<String> left = mergeSort(list.subList(0, mid)); List<String> right = mergeSort(list.subList(mid, list.size())); return merge(left, right); } private static List<String> merge(List<String> left, List<String> right) { List<String> result = new ArrayList<>(); int i = 0, j = 0; while (i < left.size() && j < right.size()) { if (left.get(i).compareTo(right.get(j)) <= 0) { result.add(left.get(i++)); } else { result.add(right.get(j++)); } } while (i < left.size()) result.add(left.get(i++)); while (j < right.size()) result.add(right.get(j++)); return result; } private static void find(String s) { // 更简单的方式,直接用w隔开,然后丢弃头尾,其余的即是结果 /*String[] ws = s.split("w"); for (int i = 1; i < ws.length - 1; i++) { res.add(ws[i]); }*/ int slow = 0, fast = 0; int wTimes = 0; StringBuilder sb = new StringBuilder(); while (fast < s.length()) { char c = s.charAt(fast); sb.append(c); fast++; if (c == 'w') { wTimes++; } while (wTimes >= 2) { char del = sb.charAt(slow); slow++; if (del == 'w') { String str = sb.toString().substring(slow, fast-1); res.add(str); wTimes--; // 天塌了,我这里把sb重置了,寄 } } } } }
  1. 场景:现有10000个任务待执行,要你分成一百个批次去跑,每次跑100个任务
  • QuestionA:如果是单台机器跑,请你给出你能想到的所有实现

    我只实现了1,2

    java
    复制代码
    List<String> list = new ArrayList<>(10000); List<List<String>> partition = Lists.partition(list, 100); ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 100, 3000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); // 方式1 CyclicBarrier barrier = new CyclicBarrier(100); for (List<String> strings : partition) { for (String string : strings) { executor.execute(() -> { try { // runTask barrier.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (BrokenBarrierException e) { throw new RuntimeException(e); } }); } } // 方式2 for (List<String> strings : partition) { CountDownLatch latch = new CountDownLatch(100); for (String string : strings) { executor.execute(() -> { // runTask latch.countDown(); }); } latch.await(); } // 方式3 List<Future> futures = new ArrayList<>(); for (List<String> strings : partition) { CompletableFuture<Void> future = CompletableFuture.runAsync(()->{ // strings.forEach(ProcessTask); }, executor); futures.add(future); } CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); }
  • QuestionB:如果是集群方式跑,要怎么做,说出你的思路

    我写的使用数据库的方式,但应该用消息队列会好点

java
复制代码
// 1. 初始化:将10000任务发送到Kafka(100个分区) @RestController public class TaskInitializer { @Autowired KafkaTemplate<String, Integer> kafkaTemplate; public void sendTasks() { for (int i = 0; i < 10000; i++) { // key = i % 100 确保同一批次进同一分区 kafkaTemplate.send("task-topic", String.valueOf(i % 100), i); } } } // 2. Worker 消费(每个实例消费多个分区) @Component public class TaskConsumer { @KafkaListener(topics = "task-topic", groupId = "worker-group") public void consume(@Payload Integer task) { processTask(task); // 执行任务 } } // 3. 部署:启动10~100个Worker实例,Kafka自动分配分区
  1. 场景SQL 见图片 5cd7ef94e0aebaeaa39af34018b25fae.png
sql
复制代码
--以下是AI给的答案,但我觉得不太行 --索引 表A index(agentid, airplane, time) 表B index(city_code) SELECT agentid, airplane, DATE(time) AS date_key, GROUP_CONCAT(DISTINCT b.country_code ORDER BY b.country_code) AS dest_countries FROM 表A a JOIN 表B b ON a.dest_city_code = b.city_code GROUP BY agentid, airplane, DATE(time) ORDER BY agentid, airplane, date_key;
  1. 场景: 小明计划2025-04-07前往北京参加会议,于是他在平台上下了一笔机票订单 2025-04-05 12:00:00 CA123前往北京,并预订了04-05晚上的亚朵酒店,因为天气原因,该航班被迫取消,于是他进行改签下定2025-04-06 20:00:00 MU321,并把酒店时间也改成了04-06晚。(大概是这样,记不清了) 请你针对该场景,使用面向对象和DDD领域驱动设计的思想,完成伪码设计
java
复制代码
// 同样参考AI给的吧,我觉得自己写得太拉了,还没完全搞透DDD // ====== 值对象 ====== public record DateRange(LocalDate checkIn, LocalDate checkOut) {} public record Flight(String number, LocalDateTime departureTime, String destination) {} // ====== 聚合根:FlightBooking ====== public class FlightBooking { private String id; private String passengerId; private Flight originalFlight; private Flight currentFlight; private BookingStatus status; private String relatedBookingId; public FlightBooking(String passengerId, Flight flight) { this.id = UUID.randomUUID().toString(); this.passengerId = passengerId; this.originalFlight = flight; this.currentFlight = flight; this.status = BookingStatus.CONFIRMED; } public void cancelDueToWeather() { if (this.status != BookingStatus.CONFIRMED) { throw new IllegalStateException("Only confirmed booking can be cancelled"); } this.status = BookingStatus.CANCELLED; DomainEventPublisher.publish(new FlightCancelled(this.id)); } public FlightBooking rebook(Flight newFlight) { if (this.status != BookingStatus.CANCELLED) { throw new IllegalStateException("Can only rebook after cancellation"); } FlightBooking newBooking = new FlightBooking(this.passengerId, newFlight); newBooking.originalFlight = this.originalFlight; // 保留原始航班 newBooking.relatedBookingId = this.id; this.status = BookingStatus.MODIFIED; this.relatedBookingId = newBooking.id; DomainEventPublisher.publish(new FlightRebooked(this.id, newBooking.id)); return newBooking; } // getters... } // ====== 聚合根:HotelBooking ====== public class HotelBooking { private String id; private String passengerId; private String hotelName; private DateRange stayDates; private BookingStatus status; public HotelBooking(String passengerId, String hotelName, LocalDate checkIn) { this.id = UUID.randomUUID().toString(); this.passengerId = passengerId; this.hotelName = hotelName; this.stayDates = new DateRange(checkIn, checkIn.plusDays(1)); this.status = BookingStatus.CONFIRMED; } public void reschedule(LocalDate newCheckIn) { this.stayDates = new DateRange(newCheckIn, newCheckIn.plusDays(1)); DomainEventPublisher.publish(new HotelRescheduled(this.id, newCheckIn)); } // getters... } // ====== 领域事件 ====== interface DomainEvent {} class FlightCancelled implements DomainEvent { public final String bookingId; public FlightCancelled(String bookingId) { this.bookingId = bookingId; } } class FlightRebooked implements DomainEvent { public final String oldBookingId; public final String newBookingId; public FlightRebooked(String old, String newId) { this.oldBookingId = old; this.newBookingId = newId; } } class HotelRescheduled implements DomainEvent { public final String bookingId; public final LocalDate newCheckIn; public HotelRescheduled(String id, LocalDate date) { this.bookingId = id; this.newCheckIn = date; } } // ====== 领域事件发布器(简化版) ====== class DomainEventPublisher { private static final List<Consumer<DomainEvent>> listeners = new ArrayList<>(); public static void subscribe(Consumer<DomainEvent> listener) { listeners.add(listener); } public static void publish(DomainEvent event) { listeners.forEach(listener -> listener.accept(event)); } } // ====== 应用服务 ====== @Service @Transactional public class TravelChangeService { @Autowired private FlightBookingRepository flightRepo; @Autowired private HotelBookingRepository hotelRepo; public void handleWeatherCancellation(String passengerId, Flight newFlight, LocalDate newHotelDate) { // 1. 获取原订单 FlightBooking oldFlight = flightRepo.findByPassengerIdAndStatus(passengerId, BookingStatus.CONFIRMED); HotelBooking hotel = hotelRepo.findByPassengerId(passengerId); // 2. 取消原航班 oldFlight.cancelDueToWeather(); flightRepo.save(oldFlight); // 3. 改签新航班 FlightBooking newFlightBooking = oldFlight.rebook(newFlight); flightRepo.save(newFlightBooking); // 4. 调整酒店 hotel.reschedule(newHotelDate); hotelRepo.save(hotel); } } // ====== 枚举 ====== enum BookingStatus { CONFIRMED, CANCELLED, MODIFIED }
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
麋鹿
下载 APP