博客记录-day169-力扣+面试

78 阅读13分钟

一、力扣

1、寻找重复数

287. 寻找重复数

image.png

image.png

考虑视为链表中找到环。

class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        int slow = 0, fast = 0;
        slow = nums[slow];
        fast = nums[nums[fast]];
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[nums[fast]];
        }
        int sec = 0;
        while (sec != slow) {
            sec = nums[sec];
            slow = nums[slow];
        }
        return sec;
    }
}

2、不同的二叉搜索树

96. 不同的二叉搜索树

image.png

dp[i]为长为i的序列构成二叉树的个数。

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[0] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
}

3、打家劫舍 II

213. 打家劫舍 II

image.png

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 0) return 0;
        // 情况1:偷第一个房子,因此不能偷最后一个。计算从第2个到倒数第2个的最大值,并加上第一个房子
        // 情况2:不偷第一个房子,计算从第1个到最后一个的最大值
        // 取两种情况的最大值
        return Math.max(nums[0] + count(nums, 2, n - 2), count(nums, 1, n - 1));
    }

    public int count(int[] nums, int left, int right) {
        int pre2 = 0;   // 上上最大金额
        int pre1 = 0;    // 上个最大金额
        for (int i = left; i <= right; i++) {
            // 当前房屋的最大金额:选择偷(上上个 + 当前金额)或不偷(上个)的较大值
            int ans = Math.max(pre1, pre2 + nums[i]);
            // 更新状态:前一个不偷的状态变为前一个偷的状态,当前状态变为新计算的ans
            pre2 = pre1;
            pre1 = ans;
        }
        return pre1; // 返回最终计算的最大金额
    }
}

二、面试

1、项目问题

1. 基于DDD的分层结构在项目中如何结合?

博客记录-day110-项目各种图+Redis事务,日志,缓存过期与淘汰面试题

2. 抽奖决策的时间复杂度如何从O(n)降到O(1)?具体做了什么优化?

博客记录-day137-面试,项目策略概率装配+力扣

3. 库存扣减方案能否防止超卖?

4. 分段消费是什么含义?如何防止超卖?

分段消费指将大流量请求拆分为多个小批次处理,避免瞬时高并发压垮系统。例如,将库存按批次(如每次扣减 100 件)分给不同消费者。防止超卖的关键在于:1) ​​分段原子性​​:通过数据库行锁或 Redis 分布式锁确保每段库存操作的互斥性;2) ​​分段计数​​:每批次扣减前校验剩余库存,避免超额;3) ​​异步补偿​​:若某批次失败,回滚并重新分配。例如,结合消息队列的限流和库存预扣机制,可平衡性能与准确性。

5. 使用 Redis 的 SETNX 命令实现分布式锁,能否保证强一致性?有没有可能最终结果不一致?

Redis 分布式锁通过 SETNX 实现的是​​最终一致性​​而非强一致性,因为Redis是最终一致性。

SETNX 仅能保证锁的互斥性,但无法实现强一致性。若客户端获取锁后崩溃未释放(死锁),或主从切换时锁信息未同步(如 Redis 主从异步复制),其他客户端可能同时持有锁,导致数据竞争。此外,SETNX 未设置超时时间可能引发永久阻塞。最终不一致的场景包括:1) 锁失效后多个客户端同时操作共享资源;2) 网络分区导致脑裂。因此,需结合 RedLock 算法(多节点部署+多数派确认)或超时续期机制降低风险,但仍无法彻底避免极端情况下的不一致。

6. 分布式锁的实现方案与其他方案(如 RedLock)有什么区别?优缺点对比?

单节点 Redis 锁​​:优点是简单高效,缺点是存在单点故障和锁失效风险。
​RedLock​​:通过多独立 Redis 实例实现,需多数节点成功才算加锁,优点是容错性高,缺点是性能开销大(需多次通信)、时钟漂移可能导致锁误判。
​ZooKeeper/etcd​​:基于共识算法(如 Paxos/Raft),提供强一致性,缺点是延迟较高,吞吐量低于 Redis。
​数据库锁​​:依赖事务隔离级别,实现复杂且性能较差。
综上,RedLock 在可用性与一致性间折衷,适合高并发场景;强一致性场景优选共识算法方案;简单场景可用单节点 Redis 配合超时和重试。

2、​​数据库与事务​

1. MySQL 的 Redo Log 有什么作用?数据恢复流程是怎样的?

  • ​Redo Log​​:物理日志,循环写,用于崩溃恢复。
  • ​Binlog​​:逻辑日志,追加写,用于主从复制和人工恢复。
    ​两阶段提交​​:涉及两者。协调者在 prepare 阶段写入 Redo Log(标记事务状态为 PREPARED),commit 阶段写入 Binlog 并同步到备库。若两阶段间崩溃,Redo Log 确保事务可恢复,Binlog 保证数据一致性。

2. Binlog 和 Redo Log 的区别是什么?两阶段提交是否涉及这两个日志?

✅binlog、redolog和undolog区别?

✅什么是事务的2阶段提交?

Binlog(二进制日志)是 MySQL 服务层的逻辑日志,记录事务的 SQL 操作(如 UPDATE 或 INSERT),主要用于主从复制和基于时间点的数据恢复;而 Redo Log(重做日志)是 InnoDB 存储引擎的物理日志,记录数据页的具体修改(如偏移量和新旧值),核心作用是通过 Write-Ahead Logging(WAL)机制保障崩溃恢复的持久性。

两阶段提交(2PC)会同时涉及这两个日志:在事务提交时,InnoDB 先通过 Redo Log 记录修改(Prepare 阶段),确保即使崩溃也能恢复未提交的事务;随后 MySQL 将 Binlog 写入磁盘并标记事务为已提交(Commit 阶段),保证主从复制的一致性。两阶段提交通过 Redo Log 的原子性与 Binlog 的持久性协同,解决了分布式事务的原子性问题——若提交阶段崩溃,Redo Log 的 Prepare 状态会触发回滚,而 Binlog 未写入则确保事务不会生效,从而维持数据一致性。

3. 在 MySQL 前加缓存(如查询接口),如何保证缓存与数据库数据一致性?

✅如何解决Redis和数据库的一致性问题?

image.png

3、场景题

1. 如何设计一个支持千万级文章的排行榜系统?数据存储方案是什么?如何避免单机瓶颈?

✅基于Redis的zset实现秒级排行榜

设计一个支持千万级文章的排行榜系统需采用分层存储与分布式架构:核心数据存储使用Redis Cluster作为缓存层,通过Sorted Set以文章ID为成员、动态指标(如点击量+时间衰减因子)为分数,支持实时Top N查询,结合Cassandra作为持久化层,采用宽表按文章ID哈希分片存储全量数据及原始指标,利用其分布式特性避免单机瓶颈;异步处理层面,通过Kafka缓冲用户行为数据,由Flink实时聚合后批量更新Redis和Cassandra,降低实时写入压力,同时结合本地缓存与定时任务预加载热点数据(如Top 1000)到Redis,减少直接查询压力;扩展性上,Redis与Cassandra均支持水平分片,Redis通过槽位迁移、Cassandra通过新增节点实现无缝扩容;容灾与监控方面,采用多可用区部署保障高可用,并通过Prometheus+Grafana实时监控集群状态,结合动态降级与限流策略应对高负载,最终实现高性能、低延迟的实时排行榜与高吞吐的持久化存储。

2. HTTP 请求超时,如何排查问题?(如:请求是否进入系统?系统内部如何定位?)

HTTP 请求超时可能涉及多个环节,需要从客户端到服务端逐层排查。以下是系统性排查步骤:


​一、客户端排查​
  1. ​检查网络连通性​

    ping <目标域名或IP>        # 检查是否能解析域名并收到响应
    telnet <目标IP> <端口>     # 测试端口是否可达(如 telnet example.com 80)
    curl -v http://example.com # 显示详细请求过程(重点关注 `Connected to` 和 `Timeout` 日志)
    
    • ping 不通:检查 DNS 解析、网络路由、防火墙规则。
    • telnet 失败:可能是目标服务器未监听端口,或中间网络(如防火墙、代理)拦截。
  2. ​检查客户端配置​

    • 代理设置:确认是否因代理服务器故障导致超时(如 http_proxy 环境变量)。
    • DNS 缓存:尝试更换 DNS 服务器(如 8.8.8.8)或直接使用 IP 地址测试。
    • 客户端超时时间:检查客户端代码或工具的超时配置是否过短。
  3. ​浏览器开发者工具​

    • 查看 Network 面板中的请求状态码、响应头、TCP 握手耗时。
    • 检查是否有重定向循环(状态码 301/302 多次跳转)。

​二、服务端排查​
​1. 确认请求是否到达服务端​
  • ​查看服务端访问日志​​:

    • Nginx/Apache:检查 access.log 是否有请求记录。
    • 应用日志:检查应用自身的请求入口日志(如 Spring Boot 的 Actuator 或 Node.js 的 morgan)。
  • ​抓包分析​​:

    tcpdump -i any port <服务端口> -w capture.pcap  # 抓取服务端网络包
    wireshark capture.pcap                          # 分析 TCP 握手和 TLS 协商
    
    • 如果服务端无 SYN 收到,说明请求未到达;可能是中间网络问题。
    • 如果服务端发送了 SYN-ACK 但无响应,可能是客户端问题。
​2. 服务端资源检查​
  • ​系统资源​​:

    top -H -p <进程PID>       # 查看 CPU/内存占用
    free -h                   # 检查内存和 Swap 使用
    df -h                     # 检查磁盘空间(日志写满可能导致异常)
    
  • ​线程/连接池​​:

    • Java:通过 jstack 检查线程阻塞(如死锁、数据库连接等待)。
    • Go:使用 pprof 分析 Goroutine 泄漏。
  • ​数据库/缓存​​:

    • 检查慢查询(如 MySQL 的 slow_query_log)。
    • Redis/Memcached 连接数是否耗尽。
​3. 依赖服务排查​
  • ​外部 API 调用​​:检查是否有第三方服务响应缓慢(如支付网关、短信服务)。
  • ​微服务链路​​:使用分布式追踪工具(如 SkyWalking、Zipkin)定位具体服务节点耗时。
​4. 代码逻辑问题​
  • ​死循环/阻塞操作​​:检查是否有未设置超时的同步调用(如 HTTP 请求、文件 IO)。
  • ​重试机制​​:过多的重试策略可能导致雪崩效应(需配合退避策略)。

​三、中间件与网络排查​
  1. ​负载均衡器(如 Nginx、ALB)​

    • 检查 upstream 健康状态和超时配置(如 proxy_read_timeout)。
    • 查看错误日志(如 error.log 中的 Connection timed out)。
  2. ​防火墙/安全组​

    • 检查是否限制了源 IP 或目标端口(如云服务商的安全组规则)。
  3. ​CDN/反向代理​

    • 确认缓存配置是否异常(如返回 504 Gateway Timeout)。
    • 检查缓存命中率(如 Varnish 的 varnishstat)。

​四、高级工具​
  1. ​分布式链路追踪​

    • 使用 Jaeger、Zipkin 跟踪请求在微服务中的完整路径。
    • 关注耗时最长的服务节点。
  2. ​性能分析工具​

    • ​火焰图​​:生成 CPU/协程火焰图定位阻塞点(如 go-torchasync-profiler)。
    • ​压测工具​​:模拟高并发场景复现问题(如 wrkjmeter)。
  3. ​监控系统​

    • Prometheus + Grafana:查看 QPS、错误率、响应时间指标。
    • ELK 日志:通过日志聚合分析异常模式。

​五、常见原因总结​
环节可能原因
​客户端​DNS 解析失败、代理配置错误、本地防火墙拦截
​网络层​防火墙规则、中间链路丢包、带宽拥塞
​服务端​线程池耗尽、数据库慢查询、死锁、资源泄漏
​依赖服务​第三方 API 响应慢、缓存击穿、消息队列积压

​六、建议步骤​
  1. 从客户端工具(如 curl -v)开始,确认请求是否离开客户端。
  2. 检查服务端访问日志和抓包,确认请求是否到达。
  3. 逐步检查系统资源、依赖服务、代码逻辑。
  4. 结合监控和链路追踪工具缩小范围。

通过分层排查,通常可以快速定位问题根源。

4、微服务

1. 是否接触过微服务开发?Spring Cloud 用过哪些组件?

在项目中主要使用过 Spring Cloud 的 Eureka(服务注册与发现)、Zuul 或 Gateway(API 网关)、Feign(声明式 HTTP 客户端)、Hystrix(熔断器)和 Config(统一配置中心)等组件。例如,通过 Eureka 实现服务注册,结合 Feign 完成服务间调用,利用 Hystrix 保障服务容错性,整体架构以微服务为核心进行开发。

2. 单体应用与微服务的区别?当前系统是单体还是微服务架构?

单体应用将所有功能模块集中在一个项目中,部署简单但扩展性差,模块间紧耦合,通信通过本地方法调用;微服务则拆分为独立服务,松耦合、独立部署,通过 API 通信,适合复杂业务且需高扩展性场景。当前系统采用微服务架构,核心模块如订单、用户服务独立开发部署,通过网关统一路由,数据库分库分表,提升了系统的可维护性和弹性。

5、linux,反射,docker,git

1. Linux 系统中如何查看进程的 CPU 占用率?常用的监控命令有哪些?

✅你掌握哪些Linux常用命令?

image.png

image.png

image.png

image.png

通过 top 命令实时查看进程 CPU 占用,按 P 按 CPU 排序;htop 提供更友好的交互式界面。ps aux --sort=-%cpu 可列出按 CPU 使用率降序排列的进程。mpstat -P ALL 监控各 CPU 核心状态,pidstat -u 细化到进程的 CPU 统计。此外,sar -u 可生成历史 CPU 报告,结合 vmstat 分析系统整体负载。

2. Java 反射机制是如何实现的?

Java 反射通过 Class 类动态获取类信息,调用 Class.forName() 或 对象.getClass() 获取类对象后,可解析构造方法、字段和方法。例如,getDeclaredMethods() 获取所有方法,newInstance() 创建实例,invoke() 动态调用方法。反射的核心在于 JVM 在运行时维护类的元数据,允许程序在不知道具体类的情况下操作对象,但会牺牲部分性能。

3. Docker 网络隔离如何实现?除了网络隔离,还需考虑哪些资源隔离?

✅Docker 的常用命令有哪些?

image.png

Docker 通过 bridge 网络模式默认隔离容器网络,每个容器分配独立 IP,利用 iptables 规则控制流量。自定义网络(如 docker network create)可进一步隔离,结合 --network-alias 管理域名解析。除网络隔离外,需考虑:

  1. ​CPU/内存​​:通过 --cpus--memory 限制资源;
  2. ​磁盘 I/O​​:使用 --device-read-bps 等参数限制读写速度;
  3. ​进程隔离​​:利用 Linux 的 cgroups 和 namespace 技术(如 PID、UTS 隔离),确保容器间进程互不可见。

4. git常用命令

✅Git的merge和rebase有什么区别?

image.png

✅Git如何回滚代码?reset和revert什么区别?

image.png