飞书一面(已约二面)面经

飞书一面(已约二面)面经
最新回答
旧事酒浓

2021-06-29 23:45:20

飞书一面(已约二面)面经核心问题及解答要点如下

1. MySQL事务实现及特性
  • 实现机制:MySQL通过InnoDB引擎的undo log(回滚日志)redo log(重做日志)实现事务。

    undo log:记录事务修改前的数据状态,用于回滚(保证原子性)。

    redo log:记录事务修改后的数据状态,用于崩溃恢复(保证持久性)。

    锁机制:通过行锁或表锁实现隔离性(如SELECT ... FOR UPDATE加排他锁)。

  • 事务特性对应

    原子性(Atomicity):undo log回滚未提交事务。

    持久性(Durability):redo log确保已提交事务的修改永久化。

    隔离性(Isolation):锁机制和MVCC(多版本并发控制)实现。

    一致性(Consistency):通过原子性、持久性和隔离性共同保证。

2. MVCC实现读已提交(RC)与读未提交(RU)
  • MVCC核心:通过维护数据的版本链(隐藏字段DB_TRX_ID记录创建版本的事务ID)和ReadView(当前事务可见的版本范围)实现。
  • 读未提交(RU):直接读取最新版本数据,不检查版本可见性,可能读到未提交的修改(脏读)。
  • 读已提交(RC):每次查询生成新的ReadView,仅读取已提交事务创建的版本,避免脏读但可能不可重复读。
3. 聚簇索引与非聚簇索引的区别
  • 聚簇索引

    数据行实际存储在索引的叶子节点中(如InnoDB的主键索引)。

    表数据按主键物理排序,一个表仅有一个聚簇索引。

  • 非聚簇索引

    叶子节点存储主键值而非数据行,需回表查询(如二级索引)。

    支持多列组合索引,查询效率依赖索引覆盖情况。

4. MQ不重复消费与不丢失的实现
  • 不重复消费

    消息唯一ID:消费者记录已处理消息的ID,处理前检查是否重复。

    幂等设计:业务逻辑支持重复操作(如数据库唯一约束)。

  • 不丢失消息

    生产者:确认机制(如RabbitMQ的confirm模式)。

    MQ服务端:持久化存储(如Kafka的日志文件)。

    消费者:手动提交偏移量(如Kafka的enable.auto.commit=false)。

5. Java引用的区别
  • 强引用(Strong Reference):最常见引用,垃圾回收时不会被回收(如Object obj = new Object())。
  • 软引用(Soft Reference):内存不足时被回收,用于缓存(如SoftReference<Object>)。
  • 弱引用(Weak Reference):下次GC时被回收,用于弱缓存(如WeakHashMap)。
  • 虚引用(Phantom Reference):无法通过引用获取对象,用于跟踪对象被回收的状态(如PhantomReference)。
6. Redis缓存雪崩解决方案
  • 原因:大量缓存同时失效,请求直接打入数据库。
  • 解决方案

    随机过期时间:避免缓存集中失效。

    多级缓存:本地缓存(如Caffeine) + 分布式缓存(如Redis)。

    熔断机制:数据库压力过大时临时拒绝请求。

    限流降级:通过Sentinel或Guava RateLimiter控制请求速率。

7. 用户登录限流场景题
  • 方案

    滑动窗口算法:维护一个时间窗口(如10分钟)内的错误次数计数器,每次错误更新计数器并检查是否超限。

    Redis实现

    键:user_id:login_errors,值:错误次数。

    键:user_id:lock_time,记录锁定时间(超限后设置)。

    逻辑:

    def check_login_limit(user_id): current_time = time.time() lock_key = f"{user_id}:lock_time" error_key = f"{user_id}:login_errors" # 检查是否被锁定 if redis.exists(lock_key): lock_time = float(redis.get(lock_key)) if current_time - lock_time < 600: # 10分钟锁定 return False else: redis.delete(lock_key) # 解锁 # 检查错误次数 errors = int(redis.get(error_key) or 0) if errors >= n: redis.setex(lock_key, 600, current_time) # 锁定10分钟 return False else: redis.incr(error_key) return True
8. CAP理论
  • 定义:分布式系统无法同时满足以下三点:

    一致性(Consistency):所有节点数据一致。

    可用性(Availability):每个请求都能收到响应。

    分区容错性(Partition Tolerance):网络分区时系统仍能运行。

  • 权衡:通常选择AP(如Cassandra)或CP(如Zookeeper),放弃另一特性。
9. 分布式事务实现
  • 常见方案

    2PC(两阶段提交):协调者统一决策提交或回滚(阻塞,性能差)。

    TCC(Try-Confirm-Cancel):业务层实现补偿逻辑(如扣款Try、Confirm确认、Cancel回滚)。

    Saga模式:长事务拆分为多个本地事务,通过事件驱动协调(允许回滚链)。

    本地消息表:事务提交后写入消息表,异步消费保证最终一致性。

10. HTTPS握手密钥交换过程
  • 核心步骤

    ClientHello:客户端发送支持的加密套件和随机数Client Random。

    ServerHello:服务端选择加密套件,发送随机数Server Random和证书。

    密钥交换

    RSA:服务端用私钥加密预主密钥(Pre-Master Secret),客户端用公钥解密。

    ECDHE:双方生成临时密钥对,交换公钥后计算共享密钥(更安全,支持前向保密)。

    生成会话密钥:客户端和服务端基于Client Random、Server Random和Pre-Master Secret生成Master Secret,进一步派生出对称加密密钥。

11. 循环依赖检测(手撕代码)
  • 问题:检测Java类或Spring Bean的循环依赖。
  • 示例代码(DFS实现):import java.util.*;public class CycleDetection { public static boolean hasCycle(Map<String, List<String>> graph) { Set<String> visited = new HashSet<>(); Set<String> recursionStack = new HashSet<>(); for (String node : graph.keySet()) { if (dfs(node, graph, visited, recursionStack)) { return true; } } return false; } private static boolean dfs(String node, Map<String, List<String>> graph, Set<String> visited, Set<String> recursionStack) { if (recursionStack.contains(node)) return true; if (visited.contains(node)) return false; visited.add(node); recursionStack.add(node); for (String neighbor : graph.getOrDefault(node, Collections.emptyList())) { if (dfs(neighbor, graph, visited, recursionStack)) { return true; } } recursionStack.remove(node); return false; } public static void main(String[] args) { Map<String, List<String>> graph = new HashMap<>(); graph.put("A", Arrays.asList("B")); graph.put("B", Arrays.asList("C")); graph.put("C", Arrays.asList("A")); // 循环依赖 System.out.println(hasCycle(graph)); // 输出: true }}