前言
文章分两部分,第一部分做了一定的汇总和思路整理,第二部分记录不同公司的面试记录
(一)
1.问题回答思路小结
1.场景题
1)开发方案设计:
讲一下实现逻辑,选几个点讲一下实现细节
2)线上生产问题排查和优化:
第一步,先讲清楚临时的解决方案,先把临时出现的问题搞定了
第二步,现场问题解决完毕后,讲一下如何排查问题发生原因,程序怎么优化
2.讲项目经验
1)简单介绍该项目的背景(别太啰嗦,面试官不爱听太复杂的背景),然后讲清楚如何去完成它的,完成过程中的每一个要点,要知道用了什么方法解决,讲清楚为什么这样做,逻辑要自洽
2)讲系统架构,和代码结构,可以考虑结合领域模型来讲
2.高频问题总结
高频问题领域:
分库分表,读写分离,数据一致性,高并发,更新问题等
1. 缓存的使用,原理,常见异常,缓存和DB一致性问题
- 应用场景:高性能读写,代替DB承担读流量,分布式锁,或其他高性能的临时性存储场景
- 运行机制:IO多路复用+单线程+纯内存操作+持久化
- 常见问题:
- redis抖动/网络异常(运维,监控,哨兵,主从)
- 大体积key
i. 问题表现:占用空间多,占用大量读写流量
ii. 问题解决:对象/集合拆分 - 热点数据
i. 问题表现:不同应用竞争流量,影响性能
ii. 问题解决:1.使用本地内存缓存;2.待补充 - 与DB数据一致性问题
i. 双写,先写db再删key
ii. redis根据风险设置过期时间,注意过期时间需要加随机值,避免大面积同时过期
iii. 定时任务,定时从DB扫描最近一段时间的update情况,主动更新到redis
iv. 应用写的时候只关心写DB,然后在DB端主动写入到redis,然后应用端从redis读(DB写入redis的方法待补充) - 缓存穿透
请求合法性检查 + 空值缓存 - 缓存并发
分布式锁 - 缓存雪崩
key加随机过期时间
2. 消息中间件(主要是KAFKA)
- 应用场景:削峰填谷,跨模块数据同步与解耦,分布式数据一致性补偿工具
- 运行机制:使用zk作为集群管理,topic的多分区主从冗余,磁盘持久化等
- 常见问题:
- 消息丢失
- 消费者不会丢消息,因为有group和ack作为消费保证
- 生产者可能会丢消息,宕机或crush等,发送消息前将数据保存到DB
a) 可以设置一个发送状态,定时任务扫描发送失败的数据重发
b) 提供一个检索接口,下游服务主动检查到消息丢失,通过检索接口重新获取
- 消息堆积
- 基本思路:消息堆积算生产问题,先从临时解决堆积问题开始,然后再考虑代码优化问题
- 消费者扩容,临时加强消费能力
- 查看代码逻辑,查看系统运维信息,看看堆积消息的时间段发生了什么
a) 可能的原因:消费者消费慢,DB请求打满了,长事务多导致DB响应慢,消费者在消费事务中是否涉及RPC,等等
- 消息幂等
- 生产者端的通用性幂等(就是防重发),每个生产者被分配了一个productId,加上消息默认内置一个序列号,在生产者和kafka之间双端同步,以此防重
- 消费者端,可能是业务上或者程序上重复发,通过唯一索引防重
- 消息的消费顺序保证
- 基本思路:同一个分区只能被一个消费者消费
- 基于1),设置分区参数与消费者集群size相等,保证一分区对应一消费者成员
- 基于2)可以一个消费者成员对应多个分区,只需要保证一个分区对应一个消费者成员即可
- 消息丢失
3. 数据库问题
- 索引
- 索引设置
- 索引命中(生效问题,枚举列问题,最左原则,注意最左原则没有条件的顺序问题,优化引擎会进行检索计划的优化,只需要命中了最左边的索引即可)
- 索引对于性能的影响(加强读性能,但造成了写放大)
- 索引结构(B+树,聚簇索引与非聚簇索引,回表问题)
- 分库分表
- 分库分表要基于业务场景和需求来谈,比如说分订单表,最简单的按用户分,按商家分,等等思路,然后为了适应需求将订单冗余3份,比如说买家表存一份,商家表存一份,最后运营存一份
- 比如说分用户表,可以按地区分,或者单纯按用户ID哈希分,等等
- 哈希分表保证均匀,然后为了适配读的需求做冗余,参考1)
- 悲观锁和乐观锁
4. 读写分离,多用于分散负载,通过主从或者别的什么方式,将写的节点和读的节点分离开
5. 跨系统数据一致性问题
- 异常有限重试
- 定时任务重试
- MQ补偿重试
6. DDD领域模型
7. 限流熔断场景,相关的实现方式
8. 分布式系统中,系统的分层怎么分,分层的依据和考虑是什么?
9. 业务服务中的分块,基于什么标准,或者说思想去分块?讲讲项目中分块的现状,想法和理由,聊聊你对业务分层的认识
- 用自己系统的业务现状作为基准,聊聊怎么通过需求,去区分业务领域的边界,模块的职能
- 基于A,再聊聊这样分模块之后,模块之间的互动,最好可以说出分块的好处,和存在的问题,存在问题能不能有更好的方法解决
- 结合对业务/系统一定的发展的预见性,来聊聊这样分块是为了适应怎样的发展
3.病发编程笔记
- 线程的先后执行等待:最简单的方法用join就行;
- 线程一等多,CountDownLatch,等计数到 0 的时候,调用await方法等待的线程会被唤醒;
- 多个线程在某个节点等待同步执行,CyclicBarrier,多个线程执行await的点会等待,等计数器到 0 时会全部往下执行
- 多个线程占用有限资源,Semaphore信号量,当信号量计数器为 0 ,则后续申请的线程会被阻塞,直到有别的线程释放资源
- 异步获得返回结果,FutureTask+Callable
- sleep 是thread类静态方法,不会释放锁而是阻塞等待
wait是对象的方法,会释放锁,只能在sync代码块内使用(同理notify也是)
4.阶段性面试小结
- 工作中常用的设计模式,不能是下意识地觉得这个场景可以用,应该是可以清晰地说明为什么这个场景会用设计模式,用了有什么好处,最好可以说出别的设计模式其实也可以代替,只是因为什么原因(个人习惯,个人喜好,领导喜好)而采用了现在的设计模式
- JUC包详细再复习一遍,最好可以手撸AQS
- 常用中间件(REDIS,KAFKA),应该对应用中可能出现的异常情况有清楚的认识,并对大部分的异常场景有对应的解决/补偿方案
5.限流熔断设计方案
限流:
- 在入口配置一个filter作为统计
- 定义一个时间轮,时间轮可以用数组,根据用户配置的限流时间单位(10秒,5分钟,10分钟等)配置,一般可以考虑以一个小时作为时间轮跨度,
比如说配置为5分钟为一个限流时间单位,那就创建一个60分钟/5分钟=12的长度的数组 - 设置一个atomicInteger作为时间轮下标,通过一个timer作为计时触发器,根据配置时间(5分钟),每次下标+1
- 数组里每个元素也是一个atomicInteger计数器,根据用户配置的边界值,当前时间单位的计数器达到峰值后,后面进入的请求都直接抛异常打回
熔断:
解释原因:之所以有限流保护的同时还需要熔断,是因为限流也无法完全保证安全,因为有可能DB承担的压力过大,应用与DB的交互遇到大量长等待或者超时,
同时另一个原因是可能热点流量大量命中长事务,造成大量响应慢甚至超时,此时需要触发熔断保护系统
- 在限流的基础上,给每一个进入的请求添加一个监视器,用一个集合保存这些监视器,同时设置一个异常请求统计器
- 异常请求统计器会统计每一个返回异常的请求,统计数字考虑有超时淘汰策略(比如说这一分钟发生了10次异常请求,10分钟后就减10淘汰这些过时统计)
- 根据需求/配置,每当集合中的监视器size,或者异常统计器的异常数字超出了安全线,就马上通知服务注册中心将本应用下线
- 待大量滞留的流量处理完毕后,重新通知服务注册中心上线本应用
6.算法相关
完全没基础或者全忘了的,去复习/学习大学教材的《数据结构与算法》,有基础有印象的,LeetCode上做easy和medium级别的题目,做个三五十题有感觉了,基本上大部分公司的一面二面没什么问题。