一、面试
1、项目问题
1. 怎么防止重复下单?
防止重复下单可通过多种机制实现:唯一索引约束通过在订单表的用户ID、商品ID等字段建立唯一索引避免重复数据;分布式锁利用Redis或Zookeeper在下单前锁定资源,防止并发冲突;幂等性设计通过生成唯一订单号、Redis的SETNX命令或数据库事务结合唯一索引确保操作仅执行一次;Token令牌机制则要求客户端先获取一次性有效token,服务端校验后处理请求。
2. 采用REDIS分段消费做防超卖的具体机制?
Redis分段消费通过将库存拆分为多个子库存(如10个片段),利用DECR/DECRBY命令原子减库存,结合Lua脚本保证操作的原子性。用户下单时随机选择子库存扣减,当某个子库存耗尽时尝试其他片段,从而分散热点数据竞争,提升并发性能。
3. 是否有限流机制?
常见的限流算法包括计数器、滑动窗口、漏桶和令牌桶算法。分布式限流可通过Redis结合Lua脚本实现,结合Sentinel或集群保障高可用;应用层限流则通过Servlet过滤器、Spring AOP或网关(如Gateway)实现,支持灵活应对不同场景的流量控制需求。
2、线程同步与并发编程
1. 还了解其他哪些线程同步方式?
除synchronized外,Java还提供ReentrantLock、ReadWriteLock、StampedLock、Semaphore、CountDownLatch、CyclicBarrier、Phaser、Exchanger等同步工具。原子类(如AtomicInteger)和ThreadLocal通过无锁化或线程本地变量减少竞争,volatile关键字则保证可见性但不确保原子性。
2. SYNCHRONIZE如何保证原子性?
✅synchronized是如何保证原子性、可见性、有序性的?
synchronized通过监视器锁(Monitor)实现互斥,同一时刻仅允许一个线程进入同步块。字节码层面使用monitorenter和monitorexit指令标记同步块,JVM插入内存屏障防止指令重排,确保操作的原子性和可见性。
3. SYNCHRONIZE能否保证有序性?
✅synchronized是如何保证原子性、可见性、有序性的?
synchronized通过内存屏障禁止指令重排,保证同步代码块内的操作有序。其happens-before规则确保同一锁的解锁操作先于后续加锁操作,但对不同锁或非同步代码无效,可能存在跨锁重排。
4. 了解无锁化编程吗?有什么问题?
无锁化编程通过CAS、原子类、写时复制等技术实现线程安全,但存在ABA问题(值变化后恢复导致误判)、自旋开销、单变量限制及活锁风险。例如,CAS失败时频繁重试可能浪费CPU资源,复合操作需额外设计保证原子性。
3、线程池
1. 线程池的原理是什么?
线程池通过核心线程、任务队列、最大线程数和拒绝策略管理任务。提交任务时,优先用核心线程处理;队列满后创建临时线程;资源耗尽则执行拒绝策略(如丢弃任务或抛出异常)。线程复用通过阻塞队列实现,减少线程创建销毁的开销。
2. 如何动态调整线程池参数?
可通过ThreadPoolExecutor的setCorePoolSize()【设置核心线程数】、setMaximumPoolSize()【设置最大线程数】等方法调整参数,或结合配置中心(如Nacos)实时更新。自适应方案监控队列长度和活跃线程数,动态优化参数以适应负载变化。
3. 如何监控线程池状态?
内置方法(如getActiveCount()【返回线程池中当前正在执行任务的线程的估计数量 】、getQueue().size()【返回线程池工作队列中当前积压的任务数量】)获取实时指标,JMX通过MBean暴露监控数据。自定义监控可定时采集指标并集成Prometheus,分析线程池使用率、任务等待时间及拒绝量,结合Arthas等工具诊断异常。
4. 拒绝策略有哪些适用场景?
AbortPolicy抛出异常,适用于需明确感知任务拒绝的场景;CallerRunsPolicy在调用者线程执行任务,适合降级处理;DiscardPolicy直接丢弃任务,用于非关键数据(如日志);DiscardOldestPolicy移除旧任务优先处理新任务,适用于实时性要求高的场景。
4、JVM内存与垃圾回收
1. 对JVM内存模型(JMM)的了解?
JMM定义多线程内存访问规范,核心为主内存(共享变量)和工作内存(线程私有副本)。通过happens-before规则保证可见性与有序性,如监视器锁规则(解锁先于加锁)、volatile变量规则(写先于读)及传递性等。
2. 垃圾回收算法中标记清除GC Roots可以用哪个?
标记阶段从GC Roots遍历可达对象并标记为存活,清除阶段回收未标记对象的内存。优点是实现简单,缺点是产生内存碎片且效率较低,需结合压缩算法(如标记-整理)优化。
3. 如何排查Full GC问题?
通过GC日志分析频率和耗时,使用jmap生成堆转储,借助MAT工具定位内存泄漏或大对象。调整新生代与老年代比例、优化对象生命周期或升级硬件(如增加堆内存)是常见解决方案。
4. Full GC和Young GC的区别?
Young GC回收新生代(Eden/Survivor区),触发于空间不足,停顿短(毫秒级);Full GC回收整个堆及方法区,触发条件包括老年代不足、System.gc()调用等,停顿长(秒级),影响更大。
5、数据库与索引
1. 数据库索引类型有哪些?
B-Tree索引支持等值和范围查询(如InnoDB主键);哈希索引仅等值查询(Memory引擎);全文索引用于文本搜索(InnoDB 5.6+);空间索引处理地理数据(R-Tree)。按物理结构分为聚集索引(数据与索引同序)和非聚集索引(索引与数据分离)。
2. 如何避免回表?
覆盖索引通过查询字段均包含在索引中,直接读取数据无需回表。优化联合索引设计(遵循最左前缀)、使用主键查询(InnoDB聚集索引)或索引下推(提前过滤数据)均可减少回表次数。
3. 主键一般用什么类型?分布式环境下自然ID的缺点?
整型(自增ID)或UUID是常用主键。自然ID(如自增ID)在分布式系统中存在唯一性冲突、性能瓶颈(集中生成)、迁移困难及安全风险(易被猜测)等问题。
4. 分布式ID生成方式有哪些?
Snowflake算法生成趋势递增的64位ID(时间戳+机器ID+序列号);数据库号段模式预分配ID范围;Redis自增或Leaf、UidGenerator等系统提供高可用方案。UUID无序但全局唯一,适合非索引场景。
5. B+树为什么适合做索引?
B+树高度平衡,支持高效范围查询(叶子节点链表连接);非叶子节点仅存键值,容纳更多索引项,降低树高;数据存储在叶子节点,磁盘I/O更少。相比B树,B+树分支因子更大,查询更稳定。
6. B+树如何保持有序?
插入时定位叶子节点并保持有序,节点满则分裂并更新父节点;删除时合并或重分布键值,通过旋转维护平衡。非叶子节点分隔键确保左右子树有序,叶子节点链表支持顺序遍历。
7. B+树相比B树为什么更扁平?
B+树非叶子节点不存数据,可存更多键值,分支因子更高,树高更低。例如,相同节点大小下,B+树存储量是B树的两倍,减少磁盘访问次数,提升查询效率。
6、消息队列
1. 消息队列的使用场景?
消息队列用于日志异步处理、数据同步、分布式事务(如事务消息)、流量削峰、延迟任务调度、系统解耦及事件通知等场景,提升系统扩展性和可靠性。
2. 除了RocketMQ还了解哪些消息队列?
✅Kafka、ActiveMQ、RabbitMQ和RocketMQ都有哪些区别?
Kafka高吞吐适合日志和流处理;RabbitMQ基于AMQP,支持复杂路由;Pulsar支持存储计算分离和流批一体;ActiveMQ、NSQ、Redis Streams等各有适用场景,如ZeroMQ轻量级,适合内部通信。
3. Kafka如何保证消息不丢失?
生产者通过acks=all(全副本确认)、重试和幂等性避免丢失;Broker多副本(ISR机制)和持久化存储保障数据冗余;消费者手动提交offset并配合事务确保消费处理原子性。
4. Kafka的Offset有什么作用?
Offset标记消费进度,支持手动/自动提交,存储于__consumer_offsets主题。它实现断点续传、消费重放及重平衡时的分区分配,监控Offset可分析消费延迟。
7、网络
1. 浏览器输入域名后的解析过程?
浏览器解析URL后递归查询DNS:缓存→操作系统→路由器→ISP DNS→根域名服务器→顶级域名服务器→权威服务器,最终获取IP。随后三次握手建立TCP连接,发送HTTP请求,服务器响应后渲染页面,最后关闭连接。
2. 三次握手能否携带数据?
前两次握手(SYN和SYN+ACK)不携带数据,仅建立连接;第三次握手(ACK)确认连接建立,可携带数据,此时客户端进入ESTABLISHED状态,可发送请求。
3. 如何防御SYN攻击?
博客记录-day063-反射机制详解、SPI机制详解+TCP面试题 - 掘金
服务器端启用SYN Cookie、调整TCP参数(增大半连接队列、缩短超时)及限制单IP速率;网络层通过防火墙过滤异常流量;应用层使用CDN或代理隐藏真实服务器,分散攻击压力。客户端防护有限,主要依赖可信网络和代理。
4. 客户端能否做SYN攻击防御?
客户端无法直接防御针对服务器的SYN攻击,但可通过代理访问、检测异常连接或启用备用连接减少风险。企业网络可监控出站流量,防止内部主机发起攻击。