
上次面试还是两年前刚来深圳的时候。那时候一天能跑 4 趟面试,住在青年旅社,我想早点在深圳找工作然后租房子稳定下来,我记得还拿了挺多 offer ,真的挺佩服当时的自己的。
时隔两年,公司裁员了,现在要重新找工作,感慨万分,写下这篇文章。
面试前
面试地点是深圳第一高楼:平安金融中心
从购物公园地铁站一出来就是高楼大厦,正好是午休时间的,楼下的咖啡亭子都坐满了人,路过的行人都很有班味儿,端着咖啡,穿着黑色行政装。我感觉自己像条土狗。
随手拍的平安大楼图片
面试走廊
面试过程
这次面试20多分钟就结束了,感觉上是没戏了。因为他问了很多八股文,我几乎没准备。
面试问题概览:
- 介绍一下你项目,你主要负责哪些功能
- Redis 几种数据类型,Hash 类型应用场景,击穿,雪崩方案
- MQ 消息怎么保证不重复消费
- 数据库优化方案
- SpringCloud 常用注解
- Feign 超时时间,超时怎么处理
- 心跳机制
面试总结
自我介绍
自我介绍很磕磕绊绊,也可能太急于介绍项目了想到哪说哪,自我介绍没说的很完美。
自我介绍这块我得打个草稿提前过一遍。
Redis 几种类型,击穿,雪崩方案
实际上的回答
Redis 类型。我项目就用的 String 类型,需求是接口调用太久得缓存调用结果,key 是类名加方法名加方法入参,value 是方法返回值。关于其他类型我都没做太多了解,但是我都提了具体有哪些类型,然后他问了 Hash 类型应用场景,我说可能hash算法的原因随机访问快?(现在看起来太撤蛋了)他没往下问了。。。话说我这种情形用 String 应该没毛病,可能会污染key?比如 keys *
会弹出很多 key。他应该是想让我用hash存… 不过这都是后知后觉了。
我觉得满意的回答
主要有这些:String, Hash, List, Set, Sorted Set
String 类型
- 适合一些简单单一的公共的数据,因为太复杂会污染 key
- 适合的例子:计数器(网页访问量、点赞数、控制接口访问频率)
Hash 类型
- 适合结构化对象,因为可以局部更新不用整体写入,例如:
- key:学生id-field:班级-value:一班
- key:学生id-field:年龄-value:18
List 类型
- 支持两端操作,你可以理解为一根管子,另外有Range方法
- 可以适用的场景:
- 用作队列:先进先出(FIFO)
- 消息队列的好处:
- 异步消息:不需要等你完成,丢队列里面就行,需要延迟处理的任务放入消息队列,设置延迟时间,消费者在指定时间处理任务
- 任务调度:任务放入队列,定时调度任务,延迟消费,完成任务1才去完成任务2,按顺序处理待办任务
- 解耦
- 消息队列的好处:
- 时间轴:比如朋友圈查看,最新的放在左边开头,每次刷新就 lrange(0,页码);查看新动态
- 用作队列:先进先出(FIFO)
Set 类型
- 特点是无序且唯一,有 intersect 方法(求两个set的交集)
- 语法:
SADD key value
,SMEMBERS key
- 应用场景:
- 由唯一性会想到去重、不重复。例如,你往学生添加爱好,爱好不可以重复
sadd student1 [football,baseball,basketball]
- 无序想到随机抽取等
- 交集方法:查找两个班级学生名字相同的人
- 由唯一性会想到去重、不重复。例如,你往学生添加爱好,爱好不可以重复
Sorted Set 类型
- 有序set,语法
ZADD key score1 member1 [score2 member2 ...]
- 可以根据score排序这条记录
- 应用场景:排行榜
缓存问题及解决方案
Redis为了缓解数据库压力,设置缓存缓存数据防止查MySQL。但会出现缓存击穿(也叫穿透 Cache Breakdown)、缓存雪崩
缓存击穿:
- 出现原因:查询到了不存在的值
- 例如来了个请求查询id为 -999 的数据,MySQL没得,导致Redis缓存的是 -999 的查不到的数据。来多个-998 -997 这样非法的数据。导致很多缓存没被击中,也就是击穿
- 处理方案:在缓存前,若传过来一些非法的数据给他拦截提示非法数据,直接返回,如ID必须为正数,或者设置ip黑名单
缓存雪崩:
- 出现原因:突然一下子很多Redis key过期,导致来的请求直接走数据库
- 处理方案:随机过期时间、缓存预热(提前查一遍到缓存)、多级缓存
MQ 消息怎么保证不重复消费
实际上的回答
其实我都不知道面试官要问啥,我以为是幂等性的问题,虽然有部分相似。实际要理解 MQ 为什么会出现这种问题,因为我实际没遇到过这种问题,这种又是经典八股文。
我回答的是消费MQ的数据的时候带过来唯一的 ID(流水号之类) 先查 select 数据库数据是否存在,没有的话根据ID创建该业务数据,后面再执行后续的消费功能。
因为我之前所做过的项目的特点,数据最好留下痕迹,简单就是调用过程的数据多入库,我先入库了,数据就好排查问题。
我的回答就这么简短,就是一句话就说完了。面试官也没多问。。。但是其实还可以拓展。
满意的回答
消息队列(MQ)的设计初衷是确保消息不会被重复消费,但在实际运行中,仍可能发生一些情况导致消息被多次消费。
队列基本流程是:生产者 -> 交换机 -> 队列 -> 消费者
重复消费的原因:
- 消费者处理失败:消息未被正确确认(acknowledge),消息队列系统会重新投递该消息
- 消费者进程崩溃,同上
- 网络问题,同上
都是因为消费者未能及时确认消息,队列没收到ack,认为消息没处理。
解决方案:
缓存 ID 去重
- 每个消息附带一个唯一的 ID,放到静态变量或者缓存中,插入前先查询一遍缓存
数据库去重
- 每个消息附带一个唯一的 ID,通过唯一约束避免重复处理,把它在数据库字段设置成 unique 的字段
消息确认机制(Consumer Acknowledgements)
- 使用手动消息确认机制,basicAck 方法最后一个参数
- 确保只有成功处理的消息才会被确认,未确认的消息会被重新投递
- 虽然在 RabbitMQ 中,每条消息只会被一个消费者接收,但如果消费者并未及时确认,则消息可能会在消费者处理期间超时,并被重新投递,导致重复消费
1 |
|
- Redis 分布式锁
- 不同于简单的ID缓存,分布式锁可以防止并发问题
- 给key加锁:
- 加锁:使用 SETNX(SET if Not eXists) 命令尝试设置一个键,表示锁的存在,如果返回 1,说明加锁成功。如果返回 0,说明锁已经存在,加锁失败
- 设置过期时间:为了防止进程在获得锁后崩溃,导致锁无法释放,需要为锁设置一个过期时间
- 解锁:使用 DEL 命令删除锁,释放资源
数据库优化方案
满意的回答
SQL优化
- 能写在where限定的条件就不要写在having中
- 避免select *,应该只查询所需字段
- like 查询尽量加上左边确定的值,如 like ‘nameSpac%’
- 表太多大于3个最好不要连表了,把数据到应用层处理,挑出来要的数据再放SQL去查
索引优化
- 减少索引失效情况:
- 避免使用 is not null、or、not in、!=这样不会走索引
- like ‘%something’ 不会走索引
- 避免隐式类型转换
- 前导列失效:在联合索引中,如果查询条件中缺少前导列,索引将失效
- 对于查询慢的SQL,经常要用到的条件(select,where,order by,group by,distinct…)添加索引
- 一个表中最好不要超过6个索引
- 尽量选择区分度高的数据作为索引
- 条件中多个字段要创建索引的情况下,可以创建联合索引(减少回表)。如 age > 18 and hight < 183
- 当字段类型为字符串(varchar, text等)时,有时候需要索引很长的字符串,这会让索引变得很大,此时可以只将字符串的一部分前缀建立索引(前缀索引)
- select 很慢可以用覆盖索引,和联合索引差不多,不过解决的问题不同
表结构优化
- varchar 存储浪费空间,特殊定长字段用char
- 大表数据处理:
- 垂直分表(Union all):例如日志表,可以每个月一个日志
- 水平分表(建一个表,id关联)
SpringCloud 常用注解
满意的回答
@SpringBootApplication
- 这是SpringBoot的配置,主要是约定大于配置,自动加载一些配置文件
@EnableDiscoveryClient
- 开启服务注册发现功能,如让 Eureka,Nacos 能访问到你
@EnableFeignClients
- 用于启用 Feign 客户端,简化 RESTful 服务的调用
@FeignClient
- 加在 Feign 接口上,用于其他模块引用
1
@RefreshScope
- 动态刷新配置文件
- 例如:
@Value("${config.property}")
@EnableHystrix
- 熔断降级
Feign 超时时间,超时怎么处理
实际的回答
递归 exception.getCause instance of socketTimeoutException
去获取去识别的。
我们项目是catch这个异常转成业务异常,基本上所有的都是把能转成业务异常的都转业务异常,不能转的报联系管理员。
不过面试官想问的应该不是这个…
满意的回答
配置超时时间
1 | feign: |
超时处理方案
启动类中启用 Hystrix 熔断降级功能,自定义实现 fallback 接口:
1 |
|
心跳机制
实际的回答
我大概知道这是啥,就是你服务启动的时候,比如Nacos地址配错了、或者挂了,服务器就会一直刷日志,heartbeat之类的,就是一直找注册中心。SpringCloud 启动的时候会显示用什么配置文件,用地址是什么,给你展示出来。
应该就是心跳机制,检查服务是否在,免得真正调用的时候才发现出问题。
很显然没答到点上。
满意的回答
心跳机制的主要作用是:
确保服务可用性:定期向注册中心发送心跳信号,报告服务实例的健康状况。如果心跳信号发送失败,说明服务可能不可用,便于及时发现和处理问题。
预防调用失败:通过心跳机制,可以在服务实例不可用时及时将其从注册中心移除,避免实际调用时发现服务不可用的问题。
提高系统可靠性:定期监控服务健康状况,确保系统各个服务实例处于正常运行状态,提高系统的整体可靠性和稳定性。
Nacos 提供两种健康检查机制,分别对应不同类型的服务实例:
临时实例(非持久化实例):
- 机制:客户端主动上报心跳消息
- 特点:临时实例不会在 Nacos 服务端持久化存储,需要通过上报心跳的方式进行保活。如果一段时间内没有上报心跳,实例会被 Nacos 服务端摘除
永久实例(持久化实例):
- 机制:服务器端反向探测健康状况
- 特点:永久实例会在 Nacos 服务端持久化存储,即使注册实例的客户端进程不在,实例也不会从服务端删除,只会将健康状态设为不健康
配置示例:
1 | spring: |