博客记录-day161-面试

136 阅读6分钟

一、面试

​1、数据库相关​

1. B+树的结构特点及为何适合磁盘IO?

✅为什么MySQL用B+树,MongoDB用B树?

image.png

2. B树与B+树的区别是什么?

✅什么是B+树,和B树有什么区别?

image.png

3. MySQL如何保证事务的原子性?

✅MySQL事务ACID是如何实现的?

image.png

4. 客户端异常断开时,未提交的事务如何回滚?

自动回滚机制​

  • ​事务的原子性保障​​:InnoDB存储引擎通过ACID特性保证事务的原子性。若客户端断开时事务未提交,MySQL会自动执行回滚,撤销所有未持久化的修改。
  • ​连接状态监测​​:服务器会定期检查连接活跃状态。若连接异常终止(如网络中断、客户端崩溃),未提交的事务会被识别为“悬空事务”并立即回滚。

2、分布式系统相关

1. Redis的ZSET(有序集合)如何实现?底层结构是跳表还是其他组合?

✅Redis中的Zset是怎么实现的?

image.png

2. 缓存击穿的定义及解决方案是什么?

✅什么是缓存击穿、缓存穿透、缓存雪崩?

image.png

image.png

​3、项目​

1. 责任链模式的核心思想是什么?在抽奖系统中如何划分过滤环节?

责任链模式的核心思想是将请求的发送者和接收者解耦,通过一系列处理对象组成一条链,请求沿着这条链传递,直到有对象处理它或者到达链尾。

在代码中,我们可以看到抽奖系统中的责任链模式实现:

@Slf4j
public abstract class AbstractLogicChain implements ILogicChain{

    private ILogicChain next;

    @Override
    public ILogicChain next() {
        return next;
    }

    @Override
    public ILogicChain appendNext(ILogicChain next) {
        this.next = next;
        return next;
    }

    protected abstract String ruleModel();

}

在抽奖系统中的过滤环节划分:

  1. 黑名单过滤:检查用户是否在黑名单中
  2. 规则权重过滤:根据用户积分等因素调整中奖概率
  3. 规则锁定过滤:检查用户是否满足抽奖次数等条件
  4. 库存过滤:检查奖品是否有库存

这些过滤环节通过责任链依次执行,每个环节可以决定是否继续传递请求或者直接返回结果。

2. 黑名单过滤失败后的兜底逻辑是什么?

从代码中可以看出,当用户命中黑名单时,系统会有兜底逻辑:

@Test
public void test_performRaffle_blacklist() {
    RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
            .userId("user003")  // 黑名单用户 user001,user002,user003
            .strategyId(100001L)
            .build();

    RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);

    log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity));
    log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity));
}

黑名单过滤失败后的兜底逻辑是:

  1. 系统会返回一个默认的奖品或者未中奖结果
  2. 记录用户的抽奖行为
  3. 不继续执行后续的抽奖逻辑

这样可以确保即使用户在黑名单中,系统也能正常响应,不会出现异常情况。

3. 如何实现即使命中黑名单仍可调整中奖概率的功能?

从代码中可以看出,系统通过规则权重链来实现对黑名单用户的中奖概率调整:

@Before
public void setUp() {
    // 策略装配 100001、100002、100003
    log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100001L));
    log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100006L));

    // 通过反射 mock 规则中的值
    ReflectionTestUtils.setField(ruleWeightLogicChain, "userScore", 4900L);
    ReflectionTestUtils.setField(ruleLockLogicTreeNode, "userRaffleCount", 10L);
}

实现方式:

  1. 使用规则权重链(RuleWeightLogicChain)来调整中奖概率
  2. 即使用户在黑名单中,系统仍然可以根据用户的积分等因素调整中奖概率
  3. 通过配置不同的权重值,可以实现对黑名单用户的差异化处理
  4. 系统可以根据业务需求,为黑名单用户设置极低的中奖概率,而不是完全禁止

4. 奖品库存为何使用Redis预扣键?如何保证最终一致性?

奖品库存使用Redis预扣键的原因:

  1. 高性能:Redis的内存操作速度快,适合高并发场景
  2. 原子性:Redis的操作具有原子性,避免并发问题
  3. 预扣机制:先在Redis中预扣库存,再异步更新数据库,减轻数据库压力

从代码中可以看出系统如何保证最终一致性:

@Scheduled(cron = "0/5 * * * * ?")
public void exec() {
    try {
        ActivitySkuStockKeyVO activitySkuStockKeyVO = skuStock.takeQueueValue();
        if (null == activitySkuStockKeyVO) return;
        log.info("定时任务,更新活动sku库存 sku:{} activityId:{}", activitySkuStockKeyVO.getSku(), activitySkuStockKeyVO.getActivityId());
        skuStock.updateActivitySkuStock(activitySkuStockKeyVO.getSku());
    } catch (Exception e) {
        log.error("定时任务,更新活动sku库存失败", e);
    }
}

保证最终一致性的机制:

  1. 延迟队列:使用延迟队列存储需要更新的库存信息
  2. 定时任务:定期从队列中获取数据并更新数据库
  3. 事务处理:使用事务确保数据库操作的原子性
  4. 异常处理:对异常情况进行处理,确保数据不会丢失
  5. 消息机制:当库存为零时,发送消息通知相关服务

5. 延迟队列的设计原理是什么?底层是否依赖Redis?为何选择延迟队列?

延迟队列的设计原理:

  1. 消息延迟投递:消息不会立即被消费,而是在指定的延迟时间后才可被消费
  2. 有序性:按照延迟时间排序,确保按时间顺序处理
  3. 可靠性:确保消息不会丢失,即使系统崩溃也能恢复

系统使用了Redisson的RDelayedQueue实现延迟队列功能,底层依赖Redis。

选择延迟队列的原因:

  1. 异步处理:减轻主流程压力,提高响应速度
  2. 削峰填谷:在高并发场景下,可以平滑处理请求
  3. 数据一致性:通过延迟队列和定时任务,保证Redis和数据库的数据最终一致性
  4. 故障恢复:即使系统崩溃,队列中的消息也不会丢失
  5. 减少数据库压力:避免频繁更新数据库,提高系统整体性能

通过EventPublisher类,系统实现了消息的发布功能:

public void publish(String topic, BaseEvent.EventMessage<?> eventMessage) {
    try {
        String messageJson = JSON.toJSONString(eventMessage);
        rabbitTemplate.convertAndSend(topic, messageJson);
        log.info("发送MQ消息 topic:{} message:{}", topic, messageJson);
    } catch (Exception e) {
        log.error("发送MQ消息失败 topic:{} message:{}", topic, JSON.toJSONString(eventMessage), e);
        throw e;
    }
}

这种设计使系统能够高效处理大量的抽奖请求,同时保证数据的一致性和可靠性。

4、算法

image.png

题目:二维坐标系中,小动物从原点出发,每次可向四个方向(上下左右)移动一步,走N步后可能到达的位置数量是多少?(N≤50)

public class CoordinatePositions {
    public static void main(String[] args) {
        // 测试不同步数的结果
        for (int n = 0; n <= 5; n++) {
            System.out.println("走 " + n + " 步后可能的位置数量: " + countPositions(n));
        }
    }
    
    /**
     * 计算走N步后可能的位置数量
     * @param n 步数
     * @return 可能的位置数量
     */
    public static int countPositions(int n) {
        // 通用公式:(n+1)²
        return (n + 1) * (n + 1);
    }
}