每日一问

记录微信交流群中每天的提问。

2026.08.25#

raft中多数派为什么是n / 2 + 1#

为了保证系统的强一致性,必须确保读操作能读到最新的写操作,这就要求读集合(R)和写集合(W)必须有交集,即满足 |W| + |R| > N(N 为总节点数)。 在满足该公式的前提下,系统的设计需要在可用性和可靠性之间做权衡: 如果要求全部节点写入成功(W=N, R=1),读取极快,但任何一个节点宕机都会导致系统不可写,可用性极差。

如果只需一个节点写入(W=1, R=N),写入极快,但每次读取必须全量查询所有节点,容错性同样极差。

为了在这两者之间取一个最佳的“中间值”,Raft 和 Paxos 等算法通常将 N 设为奇数,并要求写入和读取都达到多数派(W = R = N/2 + 1)。这样不仅完美满足了 |W| + |R| > N,保证了读写集合的交集,同时也能容忍少数派((N-1)/2 个)节点的宕机,实现了可用性和可靠性的最优解。

额外练习: 以下代码有死锁的风险吗

package main

import (
    "sync"
    "time"
)

// Service 抽象的服务,管理共享数据和事件分发
type Service struct {
    mu      sync.Mutex
    data    int
    eventCh chan int //带缓冲的channel
}

// PublishEvent 模拟服务端内部逻辑:更新数据并广播事件
func (s *Service) PublishEvent() {
    s.mu.Lock()
    defer s.mu.Unlock()

    s.data++

    s.eventCh <- s.data
}

// RPC_UpdateData 模拟暴露给客户端的同步调用接口
func (s *Service) RPC_UpdateData(val int) {
    s.mu.Lock()
    defer s.mu.Unlock()

    s.data = val
}

/*
// 客户端按照以下方式使用Service
for {
        select {
        case val, ok := <-cmdCh:
            if !ok {
                return
            }
            svc.RPC_UpdateData(val)
        case _, ok := <-svc.eventCh:
            if !ok {
                return
            }
            // 处理服务端推送的事件
        }
    }
*/
}

答案:有,PublishEvent在向channel发送数据时加锁可能会导致死锁

2026.08.09#

线上某个 Redis Key 每分钟被访问数百万次,Redis CPU 持续升高,但内存和网络带宽尚未打满。你会如何判断它是不是热点 Key?为什么热点 Key 会打高 CPU?如何分别治理读热点和写热点?有哪些一致性与可用性代价?#

先对比 Redis 各节点的 CPU、QPS、延迟和网络流量,确认是否存在单节点负载倾斜;再通过客户端或代理层访问统计定位具体 Key。使用 LFU 淘汰策略时,也可以通过 redis-cli –hotkeys 采样热点 Key。 Redis Cluster 使用 CRC16(key) mod 16384 计算槽位。同一个 Key 固定落到同一个槽和主节点,因此大量请求会集中消耗该节点的命令执行、协议解析、字典查找、网络读写和响应生成能力,最终导致单节点 CPU 成为瓶颈。 读热点可以采用: 业务进程本地缓存,并设置较短 TTL 和随机抖动; 使用 singleflight 合并并发回源请求; 将数据复制为多个不同 Key,分散到不同槽位; 在允许短暂不一致时读取副本。 代价是缓存失效复杂、存在短暂不一致,并可能发生击穿。 写热点不能直接无条件拆分: 对计数、点赞等可交换、可合并操作,可以拆成多个桶,之后异步聚合; 对普通状态更新,可以通过消息队列串行化、削峰并合并连续写; 需要保证消息顺序、幂等和失败补偿。 写热点治理通常会引入最终一致性、写入延迟、聚合复杂度以及故障恢复成本。

2026.08.07#

pprof日常怎么排查线上问题?CPU高、内存泄漏、goroutine泄漏分别用什么指标定位?#

Go的pprof分两种,一种是runtime/pprof,一种是net/http/pprof,线上一般用后者,导入_ “net/http/pprof"之后,访问/debug/pprof/这个路由就能看到各类采样数据。采样类型主要有五个:cpu、heap、goroutine、mutex、block,分别对应CPU占用、内存分配、goroutine数量、锁竞争、阻塞操作。

日常排查线上问题,一般分三步走:首先通过监控系统比如Prometheus+Grafana看概览指标,发现异常,然后通过pprof拉取对应的采样数据,最后用go tool pprof分析定位到具体代码行。

接下来分三种典型场景来说。

第一个是CPU高。当发现CPU使用率持续飙高,可以采样30秒的CPU profile,curl /debug/pprof/cpu?seconds=30 > cpu.pprof,然后用go tool pprof -top cpu.pprof看最耗CPU的函数,或者用list 函数名精确定位到哪一行代码。CPU高通常有两种情况,一种是业务逻辑本身的计算量大,需要优化算法或加缓存;另一种是频繁的GC导致大量CPU花在垃圾回收上,这时候看/debug/pprof/heap?gc=1触发一次GC看内存分配情况,同时用go tool pprof -alloc_space看分配最多的对象是哪些,针对性做对象复用或减少分配。

第二个是内存泄漏。内存泄漏的特征是内存使用只涨不跌,或者周期性涨跌但整体趋势向上。Heap profile用/debug/pprof/heap采样,分析的时候用go tool pprof -inuse_space看当前占用的内存,用-alloc_space看累计分配量。inuse_space能看出当前内存被谁占着,alloc_space能看出谁分配得最频繁。内存泄漏常见的原因有三种:第一种是全局的map或slice只增不减,比如缓存没有淘汰策略;第二种是goroutine泄漏导致goroutine的栈内存无法释放,需要结合goroutine profile一起看;第三种是CGO或者第三方库申请的内存没释放,这种情况用go tool pprof -inuse_space是看不出来的,需要结合系统内存监控和cgo内存分析的额外手段。

第三个是goroutine泄漏。特征是goroutine数量持续上涨不回落。 用/debug/pprof/goroutine拉取,分析时用go tool pprof -traces查看所有goroutine的调用栈,或者访问/debug/pprof/goroutine?debug=2看文本格式的详细栈信息。重点看goroutine的状态,如果大量卡在chan send、chan receive、select,说明是channel导致的阻塞;如果卡在sync.WaitGroup.Wait,说明是WaitGroup的Done没调用;如果卡在sync.RWMutex.Lock,说明是锁持有时间过长或死锁。定位到具体阻塞位置后,检查为什么条件永远不满足。

2026.08.05#

Golang 中数组和切片的区别?#

数组: 数组固定长度。数组长度是数组类型的一部分,所以 [3]int 和 [4]int 是两种不同的数组类型。数组需要指定大小,不指定也会根据初始化,自动推算出大小,大小不可改变。数组是通过值传递的。 切片: 切片可以改变长度。切片是轻量级的数据结构,三个属性:指针、长度、容量;不需要指定大小。切片在传参时按值传递的是 slice header(包含底层数组指针、长度、容量)的副本,但因为副本中的指针指向同一底层数组,所以函数内对底层数组元素的修改对调用方可见。切片可以通过数组来初始化,也可以通过内置函数 make() 来初始化,初始化的时候 len=cap,然后进行扩容。

分析: slice 的底层数据其实也是数组,slice 是对数组的封装,它描述一个数组的片段。slice 实际上是一个结构体,包含三个字段: 长度、容量、底层数组 。

// runtime/slice.go type slice struct { array unsafe.Pointer // 元素指针 len int // 长度 cap int // 容量 }

2026.08.04#

索引失效的场景#

对索引使用左或者左右模糊匹配 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。 因为索引 B+ 树是按照「索引值」有序排列存储的,只能根据前缀进行比较。

对索引字段使用函数 如果查询条件中对索引字段使用函数,就会导致索引失效。 因为索引保存的是索引字段的原始值,而不是经过函数计算后的值,自然就没办法走索引了。

对索引字段进行表达式计算 在查询条件中对索引进行表达式计算,也是无法走索引的。 原因跟对索引使用函数差不多,因为索引保存的是索引字段的原始值,所以无法走索引。只能通过把索引字段的取值都取出来,然后依次进行表达式的计算来进行条件判断,因此采用的就是全表扫描的方式。

对索引字段进行隐式类型转换 如果索引字段是字符串类型,但是在条件查询中,输入的参数是整型的话,你会在执行计划的结果发现这条语句会走全表扫描。 但是如果索引字段是整型类型,查询条件中的输入参数即使字符串,是不会导致索引失效,还是可以走索引扫描。 这是因为,MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。而这个转换操作,底层是调用了CAST函数,而前面我们也说了,对索引使用函数是会导致索引失效的。

联合索引非最左匹配 多个普通字段组合在一起创建的索引就叫做联合索引,也叫组合索引。联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配。 比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引: where a=1; where a=1 and b=2 and c=3; where a=1 and b=2; 但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效: where b=2; where c=3; where b=2 and c=3;

原因是,在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序

2026.08.03#

如何评测一个 Agent 做得好不好?#

评测维度分结果和过程两类,自动化靠程序校验、模型裁判和回归测试集。

评什么——两个维度:

  1. 结果:任务成功率(有没有做成,要能客观判定)。
  2. 过程:工具调用对不对、用了多少步/多少 token、有没有绕路死循环。

怎么自动化——三招:

  1. 能用代码验证的用代码验证(比如跑测试、查数据库最终状态)。
  2. 不能的用强模型当裁判打分(LLM-as-Judge,配上明确评分标准)。
  3. 线上 bad case 沉淀成固定测试集,每次改动都回归跑一遍。

加分点:Agent 输出不稳定,同一任务要跑多次看全过率(pass^k),不能跑一次过了就算数。

2026.08.02#

Kafka中Topic分区数设置过多会导致哪些生产问题?#

分区过多会放大Broker端文件句柄和内存消耗,引发频繁Full GC甚至OOM;同时Controller在选举或扩分区时需同步大量元数据,集群抖动加剧。客户端也会因线程和缓存膨胀出现性能下降,最终拖慢整体吞吐。

2026.07.08#

美股实时行情跨中美传输遇到丢包,怎么设计?#

评级:场景题(量化公司原题)

美股实时行情跨太平洋传输到国内服务器,核心挑战是高 RTT、随机丢包、网络抖动、路由绕行、带宽波动、乱序、跨境链路拥塞和运营商 QoS 差异。行情数据又有强时效性,业务通常更关注“尽快拿到最新状态”,同时关键成交、盘口变更也需要完整性保障。

  1. 原生 TCP 的主要问题是重传和拥塞控制会放大延迟。中美跨洋 RTT 常见在 120ms 以上,丢包后触发超时重传、快速重传、拥塞窗口收缩,容易产生明显尾延迟;高频小包行情还会受到滑动窗口、Nagle、队头阻塞和拥塞避让影响,吞吐与延迟都会抖动。

  2. 原生 UDP 的优势是低延迟和低协议开销,问题是缺少确认、重传、有序交付和拥塞控制。跨境随机丢包会直接丢失成交、盘口更新等关键数据,业务层需要自行识别缺口并修复。

  3. 更推荐 UDP + 应用层可靠机制,或使用 QUIC / 自研可靠传输协议。常见做法是每条行情带递增序号、时间戳和频道 ID,接收端检测乱序与缺口;对关键增量数据做 NACK 重传,对可容忍轻微损失的高频价格流使用 FEC 前向纠错、冗余发送和最新状态覆盖。

  4. 分层解决思路:

    • 网络层:选择专线、金融低延迟线路、SD-WAN、多运营商链路、海外中转 POP,做链路质量探测和动态路由切换。
    • 传输层:TCP 场景开启 BBR、SACK、窗口调优和连接复用;UDP 场景增加序号、ACK/NACK、FEC、乱序缓冲、重传队列和速率控制。
    • 应用层:采用“快照 + 增量”模型,增量丢失时快速请求最新快照;按 symbol 或 channel 分片传输,避免单通道阻塞;消费端按序号做幂等、去重、缺口检测和延迟监控。
    • 运维层:持续监控 RTT、丢包率、乱序率、重传率、端到端延迟和各运营商路径质量,按行情时段动态调整链路和冗余策略。
  5. 面试回答可以落在取舍上:原生 TCP 可靠性强但尾延迟高;原生 UDP 低延迟但完整性由业务承担。实时行情更适合基于 UDP 构建轻量可靠层,把关键数据可靠性、最新状态优先级和延迟预算放到业务协议中控制。

2026.07.07#

多进程机器频繁缺页中断,CPU 占用不高但请求延迟极高,怎么分析?#

评级:后端面试场景题

根因通常是物理内存无法容纳所有进程的工作集,系统频繁发生换页和 major page fault。缺页需要等待磁盘 IO 把页面换入内存,进程进入阻塞等待,CPU 看起来空闲,业务请求却被 IO 等待拖慢,最终表现为延迟飙升。

常见诱因:

  1. 整机物理内存不足,多进程内存占用叠加超过可用内存,产生内存颠簸。
  2. 程序存在内存泄漏、无限制大缓存、一次性加载大批量数据等行为,占用过量内存。
  3. 并发进程数量过多,多个进程同时争抢内存。
  4. swappiness 参数偏高,内核更积极地把匿名页换出到 swap。
  5. swap 位于机械硬盘或低速磁盘,缺页等待时间被进一步放大。

排查时可以看 vmstatsi/sosar -B 的缺页指标、topwafree -h 的 swap 使用量、pidstat -r 的进程内存变化,以及业务进程的 RSS、VSZ、major fault 数量。

优化方案:

  1. 增加物理内存,或降低单机承载的进程数量,把工作集控制在物理内存范围内。
  2. 修复内存泄漏,限制本地缓存大小,避免一次性加载超大数据,改成分页、流式或分批处理。
  3. 给进程设置内存上限和并发上限,通过 cgroup、容器 limits 或进程池控制资源争抢。
  4. 调低 vm.swappiness,减少主动换页;对低延迟核心服务可以关闭 swap 或只保留应急 swap。
  5. 把 swap 放到 SSD,并监控 major fault、swap in/out 和 IO wait,及时扩容或迁移实例。

2026.07.06#

进程、线程、协程三者区别#

评级:基础知识

  1. 进程是操作系统资源分配的基本单位。每个进程拥有独立虚拟地址空间、文件句柄、堆、信号、内核对象等资源,进程之间通过管道、Socket、共享内存、消息队列等 IPC 机制通信。

  2. 线程是 CPU 调度执行的基本单位,依附于进程存在。同一进程内的线程共享堆、全局变量、文件描述符和地址空间;每个线程拥有自己的栈、寄存器上下文和线程局部数据。线程切换需要内核参与,成本低于进程切换。

  3. 协程是用户态轻量级执行单元,由语言运行时或协程库调度。协程切换通常只需要保存和恢复少量上下文,成本低;多个协程可以运行在一个或多个线程上,遇到 IO、定时器或显式让出时由调度器切换执行。

  4. Go 的 goroutine 可以看作运行时管理的轻量级协程。goroutine 初始栈很小并可按需增长,Go runtime 通过 GMP 模型把大量 goroutine 调度到少量系统线程上运行。

  5. 面试回答可以按“资源归属、调度者、切换成本、通信方式”四个维度讲:进程资源隔离最强、切换成本最高;线程共享进程资源、由内核调度;协程由用户态调度,适合高并发 IO 密集型任务。

2026.07.04#

虚拟地址、虚拟地址空间、页表三者有什么联系?#

评级:OS 基础

  1. 虚拟地址空间是进程逻辑上可使用的全部地址范围。每个进程通常拥有独立的虚拟地址空间,看起来像一片连续、私有的逻辑内存。

  2. 虚拟地址是程序访问内存时使用的逻辑地址,属于虚拟地址空间中的某一个位置。分页机制下,虚拟地址通常可以拆成“虚拟页号 + 页内偏移”。

  3. 页表是虚拟地址到物理地址的映射表,核心内容是“虚拟页号 -> 物理页框”的对应关系,同时还会记录权限位、有效位、脏位等控制信息。

  4. CPU 访问内存时,会先拿到程序给出的虚拟地址,再通过页表找到对应物理页框,最后加上页内偏移得到真实物理地址。

三者关系可以概括为:虚拟地址空间提供进程视角下的逻辑内存范围,虚拟地址是这个空间里的具体位置,页表负责把这个位置映射到物理内存。

2026.07.03#

RabbitMQ 中为什么需要 Exchange?#

评级:RabbitMQ 常见

Exchange 的核心作用是根据交换机类型、Routing KeyBinding 规则,把 Producer 发送的消息路由到一个或多个 Queue。它让生产者只关心“把消息发给哪个交换机”,让队列绑定关系由 RabbitMQ 配置管理。

如果 Producer 直接发送到 Queue,会有几个问题:

  1. Producer 必须知道每个 Queue 的名字,生产者和具体队列强绑定。
  2. 新增、删除或调整 Queue 时,Producer 代码和配置都可能跟着改。
  3. 一个消息要投递给多个 Queue 时,Producer 需要自己发送多次,业务代码会混入路由逻辑。

有了 Exchange 之后:

  1. Producer 只需要把消息发送给 Exchange,并携带合适的 Routing Key
  2. Exchange 根据绑定关系自动把消息路由到一个或多个 Queue。
  3. 新增消费者、新增 Queue 或调整消息分发规则时,通常只需要修改绑定关系,Producer 逻辑保持稳定。
  4. Direct、Topic、Fanout、Headers 等交换机类型可以覆盖精确路由、通配符路由、广播和按消息头路由等场景。

面试回答可以收口到一句:Exchange 把“消息生产”和“消息路由”解耦,Producer 负责发送业务事件,Exchange 负责按规则分发到队列。

2026.07.02#

TCP 主动关闭方为什么要等待 TIME_WAIT 2MSL?#

评级:TCP 高频

先确认角色:TIME_WAIT 是主动关闭方的状态。四次挥手过程通常是主动关闭方发送 FIN,被动关闭方返回 ACK;被动关闭方处理完剩余数据后发送 FIN;主动关闭方返回最后一个 ACK,进入 TIME_WAIT,等待 2MSL 后释放连接。

  1. 处理最后一个 ACK 丢失的场景。主动关闭方发送最后一个 ACK 后,如果这个 ACK 在网络中丢失,被动关闭方会重传 FIN。主动关闭方处于 TIME_WAIT 时仍能收到这个重传 FIN,并再次发送 ACK,帮助对端正常进入关闭状态。

  2. 等待旧连接的滞留报文自然过期。网络中可能存在延迟到达、乱序到达的旧数据包。等待 2MSL 可以让本次连接相关报文在网络中消失,降低旧报文影响后续相同四元组连接的风险。

  3. MSL 是 Maximum Segment Lifetime,表示 TCP 报文在网络中的最大存活时间。2MSL 覆盖两个方向上的最大残留时间,给最后一次 ACK 和可能重传的 FIN 留出完整处理窗口。

面试回答可以这样收口:TIME_WAIT 的核心价值是保证最后一个 ACK 可补发,并让旧连接报文充分过期;它属于主动关闭方。

2026.06.30#

Kafka 如何保证消息不丢失?#

评级:Kafka 高频

Kafka 保证消息不丢失需要 Producer、Broker、Consumer 三端协同配置。面试里可以按“生产确认、服务端副本、消费提交”三段回答。

  1. Producer 端设置 acks=all,让消息写入 Leader 并同步到足够多的 ISR 副本后再返回成功;同时开启重试,合理配置 retriesdelivery.timeout.msrequest.timeout.msenable.idempotence=true,避免网络抖动、请求超时和重试带来的重复写入问题。

  2. Broker 端配置足够的副本数,比如 replication.factor >= 3,并设置合理的 min.insync.replicas,保证至少有多个同步副本写入成功。生产环境通常配合 unclean.leader.election.enable=false,避免落后副本成为 Leader 后造成已确认消息被截断。

  3. Consumer 端关闭自动提交 offset,使用手动提交。业务逻辑处理成功后再提交 offset,可以避免消费者拉到消息后进程崩溃导致“假消费”。批量消费时要在批处理成功后提交本批次最大 offset,并做好幂等处理。

  4. 可靠性还要配合监控和运维。重点关注 ISR 收缩、Under Replicated Partitions、生产失败率、重试次数、Consumer lag、磁盘故障和 Broker 重启。发现 ISR 频繁波动时,要检查磁盘、网络、Broker 负载和副本同步延迟。

  5. 工程取舍上,acks=all、多副本、手动提交 offset 会提升可靠性,也会增加延迟和资源成本。核心交易、订单、支付、审计日志更适合可靠性优先;日志采集、埋点等场景可以按业务价值降低配置强度。

可能的追问点#

  1. acks=allacks=1 在生产环境中的取舍依据是什么?
  2. ISR 列表的变更对消息写入和数据完整性有什么影响?
  3. Consumer 手动提交 offset 时如何平衡性能和数据不丢?

2026.06.29#

Agent 多轮调用过程怎么排查?#

评级:拉完了

Agent 的一次请求通常包含多轮 LLM 调用、Tool 调用、循环决策、上下文裁剪和中间状态更新。排查重点是把对话过程还原成一棵可观察的 trace 树,看到每一步输入、输出、耗时、错误和父子关系。

  1. hook 采集是入口。可以做一个 plugin,在每轮 LLM 调用、Tool 调用、循环决策前后都记录事件,带上 run_idtrace_idspan_idparent_span_id、用户请求、模型参数、工具名、耗时、状态码、错误信息和 token 成本。

  2. 结构化 Tracing 是核心。把一次 agent 执行视为一次分布式 trace,把每次 LLM call、Tool call、memory read/write、planner decision 都建成 span。这样可以从树形结构中看出调用顺序、分支、重试、并发子任务和异常节点。

  3. 关键 hook 点包括:LLM 调用前、LLM 调用后、Tool 调用前、Tool 调用后、上下文压缩前后、检索前后、循环/决策点、任务结束点。每个点记录输入摘要、输出摘要、上下文长度、token 数、耗时、错误、重试次数和 stop reason。

  4. 排查流程可以按四步走:先通过 run_id 找到问题请求;再看 trace timeline 定位慢节点或失败节点;接着展开对应 span 检查 prompt、tool args、tool result、上下文快照;最后和成功样本 trace 对比,确认问题来自提示词、工具返回、上下文丢失、权限、模型输出偏差或外部依赖。

  5. 落地上可以直接接 Langfuse。它是面向 LLM agent 的开源 tracing 平台,支持 trace/span UI、prompt 记录、token 成本、延迟统计、错误追踪和会话级分析,适合快速把日志流升级成可视化调用链。

可能的追问点#

  1. trace、span、log、metric 的区别是什么?
  2. Agent 的 trace 里哪些字段必须脱敏?
  3. 如何用 trace 复现一次失败的 agent 调用?

2026.06.26#

Go 使用协程要注意什么?#

评级:Go 并发常见

  1. 防泄漏:每个 goroutine 都要有明确退出路径,常用 context、关闭 channel、done channel 或任务队列收口。main 函数退出时进程结束,其他 goroutine 会随进程结束;WaitGroup.Add 要在启动 goroutine 前调用;高并发任务要用 worker pool、信号量或限流器控制 goroutine 数量。

  2. 并发安全:多个 goroutine 共享读写同一份数据时,要用 sync.Mutexsync.RWMutexatomic、channel 或并发安全容器保护状态。开发和测试阶段配合 go test -race 检测数据竞争;全局单例初始化用 sync.Once;复杂共享状态优先通过 channel 明确数据归属。

  3. 闭包与 panic:循环里启动 goroutine 时显式拷贝循环变量,避免闭包引用同一个变量导致结果异常。子 goroutine 的 panic 要在自身内部用 defer + recover 兜底,服务端常在 goroutine 入口、HTTP/RPC middleware、worker 边界统一处理 panic。

  4. Channel 规范:nil channel 的读写会永久阻塞;读取已关闭 channel 会立即返回零值和 ok=false;向已关闭 channel 写入会 panic;关闭已关闭 channel 会 panic。for range 遍历 channel 依赖发送端关闭 channel 才能退出,工程上通常由发送方负责关闭。

  5. 调度性能:纯 CPU 死循环要提供退出条件,必要时用 runtime.Gosched() 或拆分任务让出调度机会;IO、锁等待、系统调用等阻塞点通常会触发调度器切换。循环里的 select 定时逻辑优先复用 time.Timertime.Ticker,减少反复 time.After 带来的定时器对象积累。

  6. 工程规范:跨 goroutine 传递超时、取消和请求级信息统一使用 context;单个 goroutine 职责保持清晰;goroutine 之间通过明确的输入输出协作;日志、指标和 trace 要带上请求 ID 或任务 ID,方便排查泄漏、阻塞和异常退出。

可能的追问点#

  1. 循环里启动 goroutine,闭包捕获循环变量会出什么问题?为什么?怎么修复?
  2. 为什么父协程的 recover 捕获不到子协程的 panic?会造成什么后果?
  3. 对 nil channel、已关闭的 channel 分别执行读、写、close 操作,各是什么结果?

2026.06.25#

MySQL 中如何排查慢 SQL?#

评级:MySQL 常见

排查慢 SQL 一般按“定位 SQL、分析执行计划、确认瓶颈、优化验证”四步走。

  1. 先开启慢查询日志 slow_query_log,设置合理的 long_query_time,定位执行耗时高、扫描行数多、出现频率高的 SQL。线上也可以结合数据库监控、APM、performance_schema 和业务链路 trace 找到具体接口与 SQL。

  2. EXPLAINEXPLAIN ANALYZE 查看执行计划,重点看 typekeyrowsfilteredExtra。比如 type 越接近 const/ref/range 通常越好;key 表示实际选择的索引;rows 表示预估扫描行数;Extra 中重点关注 Using filesortUsing temporaryUsing index conditionUsing where 等信息。

  3. 结合 SHOW PROFILEoptimizer_traceperformance_schema、InnoDB 状态和系统监控判断瓶颈类型。常见方向包括 CPU 计算、磁盘 IO、锁等待、回表过多、排序临时表、扫描行数过大、统计信息偏差、连接查询驱动表选择异常。

  4. 优化时优先从索引和 SQL 写法入手。常见手段包括新建或调整联合索引,按 WHEREJOINORDER BYGROUP BY 设计索引顺序;减少 SELECT * 和返回数据量;优化深分页;拆分复杂关联查询;让条件表达式保持索引列原样;更新统计信息。

  5. 优化后在测试环境和灰度环境验证执行计划、扫描行数、响应耗时和业务结果。慢 SQL 优化要同时关注读性能、写入成本、索引维护成本和线上回滚方案。

可能的追问点#

  1. 如果让你创建联合索引,你会怎么做?
  2. typekeyrowsExtra 等字段具体怎么看?
  3. 索引失效的场景有哪些?参考 2026.06.24 每日一问。

2026.06.24#

索引失效的情况有哪些?#

评级:MySQL 常见

  1. 对索引列使用函数,索引列参与函数计算后,优化器通常难以直接利用原始索引有序性。
SELECT * FROM orders WHERE DATE(create_time) = '2026-06-24';
  1. 对索引列使用表达式计算,查询条件中的索引列被运算包裹后,B+ 树索引上的原始值难以直接匹配。
SELECT * FROM orders WHERE score + 10 = 100;
  1. 存在索引隐式类型转换,字段类型和查询值类型差异会触发转换,转换发生在索引列一侧时会影响索引使用。
SELECT * FROM orders WHERE user_id = 123456;
  1. 联合索引违反最左前缀原则。假设联合索引是 (user_id, create_time, status),查询跳过最左列或范围条件后继续使用后续列,都可能导致部分索引利用受限。
-- 缺少最左列 user_id
SELECT * FROM orders WHERE status = 1;

-- create_time 是范围条件,status 的索引利用可能受限
SELECT * FROM orders
WHERE user_id = 1
  AND create_time > '2026-06-01'
  AND status = 1;

可能的追问点#

  1. 联合索引的最左前缀原则,ORDER BYGROUP BY 也遵守吗?
  2. INOR 会导致索引失效吗?

2026.06.23#

Claude Code 与 Codex 的技术区别#

评级:群友八股(网易一面、二面)

  1. 子智能体架构

Claude Code 的子智能体设计遵循上下文隔离原则。每个子智能体运行在独立上下文窗口中,支持工具约束、配置复用和成本控制。其核心价值在于隔离性、可控性和任务边界清晰。

Codex 采用并行专用智能体模式,通过 specialized agents 执行子任务工作流,最终汇总结果。二者的工程侧重点可以这样概括:Claude Code 重视隔离性,Codex 强调并行吞吐。

  1. 上下文管理机制

Claude Code 通过自动压缩历史对话,将有效信息密度提升到更高水平,在有限 token 窗口内维持任务连贯性。

Codex 结合云端异步智能体的优势,利用服务端算力进行更激进的上下文重组。

从实测体验看:

  • Claude Code:压缩策略保守,优先保证信息完整性;适合代码重构、大型 PR 审查等精确性要求高的场景。
  • Codex:压缩策略激进,优先保证窗口利用效率;适合多文件并行修改、批量重构等吞吐量优先的场景。
  1. MCP 协议与工具生态

模型上下文协议(MCP)是 Claude Code 先发的重要基础设施。它定义了一套标准化的工具调用接口,允许第三方服务以插件形式接入。

Codex 后续跟进 MCP 能力,同时工具生态更依赖 OpenAI 自有体系。

  1. 技术栈差异
  • Claude Code:命令行作为核心入口,通过 hooks、skills、MCP 插件向外扩展,开发者工作流深度优先。
  • Codex:命令行、IDE、桌面 App、移动端和云端任务共同覆盖,访问覆盖面优先。

可能的追问点#

  1. Claude Code 的“自动压缩”是语义压缩还是统计压缩?
  2. 子智能体的故障隔离边界是什么?

2026.06.22#

从用户输入一个地址到浏览器显示画面,中间经历了什么?#

评级:常见八股(字节一面、腾讯一面、TME 一面、快手一面真题)

当用户在浏览器输入地址(URL)并回车后,浏览器会解析 URL 得到协议、域名等信息,先查本地 DNS 缓存和 Hosts 文件,未命中则向本地 DNS 服务器发起递归查询,最终从权威 DNS 拿到目标服务器 IP。

浏览器会根据协议判断连接方式。HTTP 通常在 TCP 三次握手后发送请求;HTTPS 会在 TCP 三次握手后继续进行 TLS/SSL 握手,完成身份认证、加密算法协商和会话密钥生成。

连接建立后,浏览器按 HTTP/HTTPS 协议组装请求报文发送给服务器。服务器处理请求,可能会查询数据库、访问缓存、调用内部接口,然后返回 HTML、状态码、响应头和响应体。

浏览器收到 HTML 后开始解析文档,构建 DOM 树;解析 CSS 构建 CSSOM 树;二者合成渲染树。遇到 JavaScript 脚本时,根据脚本加载方式执行对应逻辑,脚本可能修改 DOM、CSSOM 或继续发起网络请求。

渲染阶段会经历样式计算、布局(Reflow)、绘制(Paint)和合成(Composite),最终把页面像素显示到屏幕上。页面中的图片、CSS、JS、字体等资源会继续按依赖关系拉取,异步请求也可能继续更新页面内容。

可能的追问点#

  1. DNS 协议属于哪个层,具体流程是?
  2. HTTPS 和 HTTP 的区别?
  3. TLS/SSL 的过程?

2026.06.18#

在 RAG 中,常见的分块策略有哪些?区别是什么?#

评级:AI 基础知识

常见的分块策略有六种:自然结构分块、固定大小分块、滑动窗口分块、递归分块、语义分块和混合分块。

  1. 自然结构分块:按文档原有格式拆分,遇到标题、空行、章节编号、句号这些天然分隔符就切分。Markdown 文档可以按 ### 切,普通文档可以按段落空行切。
  2. 固定大小分块:按固定字符数、词数或 token 数均匀切分。比如每 500 tokens 切一块,实现简单,适合快速处理大量非结构化文本。
  3. 滑动窗口分块:在固定大小基础上加入重叠机制。相邻两个块之间保留 10% 到 20% 的重叠内容,避免关键信息刚好卡在分界线上被截断。
  4. 递归分块:分层拆分,先按大结构粗分,超长的部分再递归细化。比如先按章节切,章节太长就按段落切,段落还长就按句子切,直到块大小达标。
  5. 语义分块:用 NLP 模型判断语义边界,确保每个块是完整的语义单元。它会识别一段话是否讲完、下一段是否进入新话题。
  6. 混合分块:组合使用多种策略。常见做法是先按结构或固定大小快速处理,再对关键部分做语义优化,形成粗筛加精修的流程。

2026.06.17#

Go map 新的底层实现(Swiss Table)#

评级:Go 新版本知识

完整内容参考:飞书文档

  1. 面试中可以把旧版 hmap 和新版 Swiss Table 放在一起讲。旧版 hmap 的核心是 hmap + bucket + overflow bucket;Go 1.24 的新实现基于 Swiss Tables,核心是开放寻址、8 槽 group、control word 和探测序列。
  2. 旧版 hmap 的一个 bucket 通常保存 8 个 key/value,并通过 tophash 快速筛候选;冲突较多时挂 overflow bucket。查找可能沿着 overflow 链逐个扫描,链式访问容易带来更多 cache miss。
  3. Swiss Table 使用开放寻址,key/value 存在 table 的槽位里。发生冲突后,根据 probe sequence 寻找后续 group 和空槽。它把哈希分成 H1/H2:H1 用于定位 table 和 group,H2 存在 control byte 里做快速候选匹配。
  4. 新实现的关键优化是 group + control word。一个 group 有 8 个槽和 8 字节控制字,控制字记录每个槽的状态和 H2。查找时先用位运算在控制字里批量筛出候选槽位,再对少量 key 做精确比较,把逐槽扫描变成批量候选筛选。
  5. 旧版 hmap 在冲突严重时依赖 overflow bucket,内存局部性和扫描路径较差;Swiss Table 把数据组织在连续 group 中,减少指针追踪,提升缓存友好性。
  6. 内存和扩容方面,旧版 hmap 在扩容阶段维护 bucketsoldbuckets 和迁移状态,冲突多时还会产生额外溢出桶成本;Swiss Table 去掉 overflow bucket 链,通过更高装载率和紧凑元数据提升空间利用率。经典 Swiss Table 的目标负载率接近 7/8。
  7. Go 的新 map 为了保持渐进扩容,把一个 map 拆成多个独立 table,每个 table 覆盖一段 key 空间,单个 table 最多 1024 个 entry;上层用 hash 的高位选择 table。这样既能引入 Swiss Table 的查找优势,又能控制单次插入的扩容工作量。
  8. 面试回答时,把差异落在四点:冲突处理从 overflow 链转向开放寻址和探测序列;候选匹配从逐项扫描转向 control word 批量筛选;内存布局从链式溢出桶转向连续 group;扩容组织从单组 bucket 迁移转向多 table 下的渐进增长。

2026.06.16#

协程、线程、进程的区别#

评级:基础知识

  1. 进程是程序运行时的资源容器,拥有独立地址空间、文件句柄、虚拟内存和权限上下文。进程之间通过管道、Socket、共享内存、消息队列等 IPC 方式通信。
  2. 进程隔离性强,稳定性和安全性更高。一个进程崩溃通常影响自身范围;代价是创建、销毁和上下文切换成本更高,切换时涉及页表、寄存器、内核资源等状态。
  3. 线程是进程内的执行单元,也是 CPU 调度的基本单位。多个线程共享所属进程的地址空间、堆、全局变量和文件句柄,每个线程保留自己的栈、寄存器和线程局部数据。
  4. 线程通信成本低,共享数据访问方便;共享内存也带来数据竞争风险,工程上需要互斥锁、读写锁、条件变量、原子操作等机制保证并发安全。
  5. 协程是用户态轻量级执行单元,由语言运行时或协程库调度。协程通常拥有更小的初始栈,创建成本低,切换时主要保存和恢复少量上下文,调度路径更短。
  6. Go 的 goroutine 可以理解为 Go runtime 管理的轻量级协程。goroutine 初始栈很小,并按需增长;运行时通过 GMP 模型把大量 goroutine 调度到少量系统线程上执行。
  7. 协程之间共享变量依然需要并发控制。Go 中常见做法是用 channel 传递数据,用 sync.Mutexsync.RWMutexatomic 等工具保护共享状态。
  8. 面试回答时按资源归属和调度层级讲清楚:进程是资源隔离单位,线程是内核调度单位,协程是用户态调度单位;从进程到线程再到协程,隔离性逐步降低,创建和切换成本也逐步降低。

2026.06.15#

RocketMQ 如何实现延迟消息,不同版本之间有什么区别?#

评级:中级高频

  1. 延迟消息指生产者把消息发送到 Broker 后,由 Broker 持有一段时间,在指定延迟或时间点到达后再投递给消费者。常见场景有订单超时关闭、支付状态检查、定时提醒、失败任务退避重试。
  2. RocketMQ 4.x 通过固定延迟等级实现延迟消息。生产者发送消息时设置 delayTimeLevel,比如某个等级代表 30 分钟;Broker 收到延迟消息后,会保存原始 Topic、QueueId 等属性,并把消息改写到内部 Topic SCHEDULE_TOPIC_XXXX
  3. SCHEDULE_TOPIC_XXXX 是 4.x 暂存延迟消息的内部 Topic。每个延迟等级对应这个 Topic 下的一个队列,比如 1s、5s、10s、30s、1m、5m、10m、30m、1h、2h 等等级会落到对应队列中。
  4. ScheduleMessageService 是 Broker 内部负责延迟消息调度的服务。它会周期性扫描 SCHEDULE_TOPIC_XXXX 的各个队列,根据消息写入时间和延迟等级计算投递时间,发现到期消息后恢复原始 Topic 和 QueueId。
  5. 到期消息需要重新写入 CommitLog。RocketMQ 的消费依赖业务 Topic 下的 ConsumeQueue 索引;延迟阶段的消息属于 SCHEDULE_TOPIC_XXXX,到期后恢复成真实 Topic 并重新写入 CommitLog,才能生成业务 Topic 的 ConsumeQueue 索引,让消费者拉取到消息。
  6. 4.x 的特点是实现简单、稳定,和原有存储模型结合紧密;能力边界是固定延迟等级。业务需要 37 分钟后执行或明天上午 10 点投递时,通常要调整等级配置或在业务层增加调度能力。
  7. RocketMQ 5.x 增强为更完整的定时/延迟消息能力,支持更灵活的投递时间。5.x 引入 Timer Message 相关机制,用 TimerLog、TimerWheel 等思路管理定时消息,更适合大规模、细粒度、指定时间点投递的场景。
  8. 延迟消息适合理解为到期后尽快投递。实际投递时间会受到 Broker 扫描调度、系统负载、存储性能、消息堆积和消费者处理能力影响,适合秒级或最终执行类场景。
  9. 电商订单超时关闭是典型场景。订单创建成功后发送一条 30 分钟延迟消息;消费者收到后查询订单状态,状态仍为待支付时关闭订单并释放库存,状态已支付时结束处理。消费者要按订单状态做幂等判断,保证重复投递时结果稳定。

2026.06.12#

分布式系统中的 CAP 理论和 BASE 理论#

评级:基础知识

  1. CAP 分别是 Consistency(一致性)、Availability(可用性)和 Partition Tolerance(分区容错性)。一致性强调所有节点读到同一份最新数据;可用性强调每个请求都能收到响应;分区容错性强调网络分区发生时系统仍能继续运行。
  2. CAP 描述的是分布式系统在网络分区场景下的取舍。网络分区发生后,节点之间通信异常;系统要么等待数据同步后再响应,偏向一致性;要么先响应请求并接受短暂状态差异,偏向可用性。
  3. 真实分布式系统里网络故障、机房抖动、跨地域链路异常都属于常态风险,所以 P 一般必须保留。发生分区时,核心选择集中在 CP 和 AP。
  4. CP 系统优先保证一致性,适合银行转账、支付扣款、库存扣减、分布式锁、配置中心等场景。发生分区时,部分请求可能等待、失败或降级,以保证关键数据准确。
  5. AP 系统优先保证可用性,适合点赞数、浏览量、商品详情缓存、推荐特征、评论计数等场景。系统可以接受短暂状态差异,并通过异步同步、补偿任务和对账机制达到最终一致。
  6. BASE 是 Basically Available、Soft State、Eventually Consistent,分别对应基本可用、软状态和最终一致性。它是 AP 思路在工程上的落地方式,强调高并发下先保证核心服务可用,再通过异步链路收敛数据。
  7. ACID 追求事务内的原子性、一致性、隔离性和持久性,适合强一致事务;BASE 接受短暂软状态,适合高并发、跨服务、跨存储的分布式业务。
  8. 电商系统中,商品详情页库存展示可以偏 AP,通过缓存刷新、消息队列同步和定时校准保持最终一致;下单扣库存和支付扣款偏 CP,要保证扣减唯一、余额准确和订单状态一致。

2026.06.11#

OSI 七层模型和 TCP/IP 四层模型#

评级:基础知识

  1. OSI 是国际标准化组织提出的七层理论参考模型,自下而上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
  2. TCP/IP 是互联网实际使用的四层模型,自下而上分别是网络接口层、网络层、传输层和应用层。
  3. 物理层负责比特流在网线、光纤、无线电等介质上传输;数据链路层负责同一链路内的帧传输、MAC 地址、差错检测和交换机转发。
  4. 网络层负责跨网络寻址和路由,典型协议是 IP、ICMP;传输层负责端到端通信,典型协议是 TCP 和 UDP。
  5. 会话层负责会话建立、维护和释放;表示层负责数据格式、编码、压缩和加密;应用层面向具体应用协议,比如 HTTP、DNS、SMTP、FTP。
  6. 映射关系上,TCP/IP 的网络接口层对应 OSI 的物理层和数据链路层;TCP/IP 的网络层、传输层分别对应 OSI 的网络层、传输层;TCP/IP 的应用层对应 OSI 的会话层、表示层和应用层。
  7. 面试回答时可以先背层级,再讲职责和协议例子:OSI 更适合理论分层记忆,TCP/IP 更贴近真实网络协议栈。

2026.06.09#

LRU 和 LFU 是什么?适用于什么场景,底层的实现原理是什么?#

评级:LRU 手撕高频,LFU 可能会被提及

  1. LRU 是 Least Recently Used,按最近访问时间淘汰数据。核心思想是时间局部性:最近访问过的数据,短期内再次访问概率更高。缓存容量满时,优先淘汰最久没有被访问的数据。
  2. LFU 是 Least Frequently Used,按访问频次淘汰数据。核心思想是稳定热点:访问次数越高的数据,未来继续被访问的概率更高。缓存容量满时,优先淘汰访问次数最低的数据。
  3. LRU 适合热点变化快、最近访问更能代表未来访问的场景,比如页面缓存、本地查询结果缓存、操作系统页缓存、InnoDB Buffer Pool 类缓存。
  4. LFU 适合长期热点明显、访问频次稳定、希望降低偶发访问干扰的场景,比如 Redis 热点 Key、CDN 静态资源缓存、商品详情缓存、推荐特征缓存。
  5. LRU 的底层实现通常是哈希表 + 双向链表。哈希表负责通过 key 在 O(1) 时间定位节点;双向链表负责维护访问顺序,链表头部放最近访问的数据,链表尾部放最久访问的数据。Get 命中后把节点移动到头部,Put 命中后更新值并移动到头部,容量满时删除尾部节点。
  6. LFU 的底层实现通常是 key -> node 哈希表 + freq -> 双向链表 哈希表 + minFreq。每个节点记录 key、value、freq;同一访问频次的节点放在同一条双向链表里,链表内部再按 LRU 顺序排列。Get 命中后频次加一,把节点从旧频次链表移动到新频次链表;Put 容量满时,从 minFreq 对应链表的尾部淘汰一个节点。
  7. 工程取舍:LRU 实现简单、维护成本低,对最近访问敏感;LFU 更能保留长期高频热点,但频次统计有额外维护成本,热点迁移时会受历史频次影响,生产实现常加入频次衰减或近似统计。
  8. 面试收口:LRU 看最近一次访问时间,核心结构是 map + 双向链表;LFU 看访问次数,核心结构是 map + freqMap + minFreq + 双向链表。手写重点是保证 GetPut、移动节点和淘汰节点都是 O(1)。

LRU 示例代码#

package main

import "fmt"

type lruNode struct {
	key, value int
	prev, next *lruNode
}

type LRUCache struct {
	capacity int
	items    map[int]*lruNode
	head     *lruNode
	tail     *lruNode
}

func NewLRUCache(capacity int) *LRUCache {
	head, tail := &lruNode{}, &lruNode{}
	head.next = tail
	tail.prev = head

	return &LRUCache{
		capacity: capacity,
		items:    make(map[int]*lruNode),
		head:     head,
		tail:     tail,
	}
}

func (c *LRUCache) Get(key int) int {
	node, ok := c.items[key]
	if !ok {
		return -1
	}
	c.moveToHead(node)
	return node.value
}

func (c *LRUCache) Put(key, value int) {
	if c.capacity <= 0 {
		return
	}

	if node, ok := c.items[key]; ok {
		node.value = value
		c.moveToHead(node)
		return
	}

	node := &lruNode{key: key, value: value}
	c.items[key] = node
	c.addToHead(node)

	if len(c.items) > c.capacity {
		removed := c.removeTail()
		delete(c.items, removed.key)
	}
}

func (c *LRUCache) moveToHead(node *lruNode) {
	c.remove(node)
	c.addToHead(node)
}

func (c *LRUCache) addToHead(node *lruNode) {
	node.prev = c.head
	node.next = c.head.next
	c.head.next.prev = node
	c.head.next = node
}

func (c *LRUCache) remove(node *lruNode) {
	node.prev.next = node.next
	node.next.prev = node.prev
}

func (c *LRUCache) removeTail() *lruNode {
	node := c.tail.prev
	c.remove(node)
	return node
}

func main() {
	cache := NewLRUCache(2)
	cache.Put(1, 1)
	cache.Put(2, 2)
	fmt.Println(cache.Get(1)) // 1
	cache.Put(3, 3)
	fmt.Println(cache.Get(2)) // -1,key 2 被淘汰
}

LFU 示例代码#

package main

import (
	"container/list"
	"fmt"
)

type lfuEntry struct {
	key, value int
	freq       int
}

type LFUCache struct {
	capacity int
	size     int
	minFreq  int
	items    map[int]*list.Element
	freqs    map[int]*list.List
}

func NewLFUCache(capacity int) *LFUCache {
	return &LFUCache{
		capacity: capacity,
		items:    make(map[int]*list.Element),
		freqs:    make(map[int]*list.List),
	}
}

func (c *LFUCache) Get(key int) int {
	element, ok := c.items[key]
	if !ok {
		return -1
	}

	entry := element.Value.(*lfuEntry)
	c.increaseFreq(element)
	return entry.value
}

func (c *LFUCache) Put(key, value int) {
	if c.capacity <= 0 {
		return
	}

	if element, ok := c.items[key]; ok {
		entry := element.Value.(*lfuEntry)
		entry.value = value
		c.increaseFreq(element)
		return
	}

	if c.size == c.capacity {
		listByFreq := c.freqs[c.minFreq]
		back := listByFreq.Back()
		entry := back.Value.(*lfuEntry)
		delete(c.items, entry.key)
		listByFreq.Remove(back)
		c.size--
	}

	entry := &lfuEntry{key: key, value: value, freq: 1}
	listByFreq := c.getList(1)
	c.items[key] = listByFreq.PushFront(entry)
	c.minFreq = 1
	c.size++
}

func (c *LFUCache) increaseFreq(element *list.Element) {
	entry := element.Value.(*lfuEntry)
	oldFreq := entry.freq
	oldList := c.freqs[oldFreq]
	oldList.Remove(element)

	if oldFreq == c.minFreq && oldList.Len() == 0 {
		c.minFreq++
	}

	entry.freq++
	newList := c.getList(entry.freq)
	c.items[entry.key] = newList.PushFront(entry)
}

func (c *LFUCache) getList(freq int) *list.List {
	if c.freqs[freq] == nil {
		c.freqs[freq] = list.New()
	}
	return c.freqs[freq]
}

func main() {
	cache := NewLFUCache(2)
	cache.Put(1, 1)
	cache.Put(2, 2)
	fmt.Println(cache.Get(1)) // 1,key 1 的频次变成 2
	cache.Put(3, 3)
	fmt.Println(cache.Get(2)) // -1,key 2 频次最低,被淘汰
	fmt.Println(cache.Get(3)) // 3
}

2026.06.08#

短视频平台点赞系统高并发设计#

评级:goclub 原创题、场景题

场景:某条视频突然爆火,5 分钟内收到 1000 万次点赞请求,峰值 20 万 QPS。用户点完赞后,需要立刻看到“已点赞”状态,视频详情页的点赞数也要尽快变化,同时系统还要更新作者通知、排行榜、推荐特征。

  1. 核心问题有五类。第一类是写入压力,数据库直接承接 20 万 QPS 的点赞记录和计数更新会被打满。第二类是一致性压力,用户点赞状态、视频点赞数、数据库记录、缓存计数在短时间内会出现状态差异。第三类是热点压力,单条爆款视频会形成热点 key、热点行和热点消息分区。第四类是下游放大,通知、排行榜、推荐特征等任务会被点赞流量同步放大。第五类是重复请求,用户连点、网络重试、客户端补偿都会产生重复点赞。
  2. 请求链路先保证强体验数据。点赞接口同步完成鉴权、限流、幂等判断和用户点赞状态写入,让用户立刻看到“已点赞”。用户维度可以用 user_id + video_id 做幂等键,落到 Redis Set、Hash 或独立状态 key,也可以用 MySQL 唯一索引做最终兜底。
  3. 视频点赞数走缓存计数和异步落库。接口阶段优先更新 Redis 计数,比如对 video_like_count:{video_id} 做原子增量,详情页读取 Redis 展示最新计数;数据库点赞记录和视频计数字段通过 MQ 异步批量写入,减少单次请求的数据库写放大。
  4. 下游任务用消息队列拆分。点赞主事件进入 MQ 后,消费者分别处理点赞记录落库、计数汇总、作者通知、排行榜、推荐特征。核心链路优先处理“状态和计数”,通知、推荐特征、排行榜可以按优先级消费,积压时允许延迟、采样、合并或临时降级。
  5. 幂等和顺序要单独设计。幂等键使用 user_id + video_id + action,重复点赞直接返回当前状态。点赞和取消点赞可以带版本号、时间戳或操作序列,消费者按最新操作生效,避免乱序消息把用户状态回滚。
  6. 热点数据要削峰。入口层做用户维度和视频维度限流,Redis 侧对热点 key 做本地缓存、分片计数或批量聚合,消费者侧把多次点赞合并成批量 SQL 或定时 flush。排行榜可以按时间窗口聚合,例如分钟级 ZSet,再周期性合并到更长窗口。
  7. 积压要可观测、可降级、可补偿。监控 MQ 堆积量、消费延迟、Redis 失败率、数据库批量写耗时和点赞状态差异。消费速度低于生产速度时扩容消费者、扩大批量、关闭低优先级任务、对通知做聚合提醒。后续通过对账任务扫描 Redis、MQ、DB,补齐漏写和修正计数。
  8. 面试收口:这个场景的核心设计是“同步保证用户体验,异步承接高吞吐,下游解耦削峰,幂等保证重复请求安全,监控补偿保证最终一致”。用户自己的点赞状态优先强保证,视频点赞数秒级变化,通知、排行榜、推荐特征按优先级最终完成。

2026.06.05#

Redis 大 Key 会造成什么影响,如何解决大 Key 问题?#

评级:中高级八股、中频

定义:大 Key 指单个 Key 对应的 value 很大,或集合类 Key 的成员数量很多、成员内容很大。

例子:5MB 的 String、10 万成员的 List/ZSet、总大小几十 MB 的 Hash。

  1. 产生原因:业务数据集中堆到一个 Key,Hash、List、ZSet 持续膨胀,热点业务数据天然集中。
  2. 主要影响:大 value 读写占用 Redis 主线程,序列化、网络传输、后续请求排队时间变长。
  3. 系统风险:内存压力、网络带宽压力、复制延迟、主从切换风险、同步删除卡顿都会上升。
  4. fork 与 AOF 风险:BGSAVE、AOF rewrite、全量同步会触发 fork;大 Key 修改会带来 COW 内存上涨;AOF 写入和 fsync 延迟容易抖动。
  5. 发现方式:低峰期用 redis-cli --bigkeys;用 SCAN + MEMORY USAGE 定时巡检;集合类用 HLENLLENSCARDZCARD 统计规模。
  6. 治理思路:把一个大 Key 拆成多个小 Key,按用户 ID、业务 ID、时间片或哈希分片存储;集合类设置最大成员数、分页存储、定期归档。
  7. 线上迁移:新 Key 分片、双写、分批迁移、灰度切读、异步清理、监控回滚。

例子:user:info:all 是大 Hash,可以改成 user:info:{shardId},按 hash(uid) % 100 分片;应用先双写,后台用 HSCAN 分批迁移,配合 pipeline 和 sleep 控制压力;切读后用 UNLINKHSCAN + HDEL 清理老 Key。

面试收口:大 Key 治理的核心是把单次大操作拆成多次小操作,把集中风险拆成可灰度、可监控、可回滚的迁移流程,同时关注主线程阻塞、内存、网络、复制、fork、COW、AOF 和 fsync

2026.06.04#

RAG 完整链路#

评级:中难度、中频

链路:文档切块 -> embedding -> 向量库 -> topK 检索 -> prompt 拼接 -> LLM 回答。

  1. 文档入库:PDF、网页、Markdown、数据库内容先清洗,去掉无效格式、噪声文本和重复内容,再按段落、标题层级或语义边界切成 chunk。
  2. 向量化:每个 chunk 通过 Embedding 模型转成向量,连同原文、标题、来源、权限、时间等元数据一起写入 Milvus、FAISS、Qdrant、pgvector 等向量库。
  3. 用户提问:用户问题也通过同一个或兼容的 Embedding 模型转成向量,用余弦相似度、内积或 L2 距离检索相关 chunk。
  4. 召回与重排:先从向量库粗召回 TopK,再用 Reranker 对问题和候选片段做相关性打分,选出最相关、信息密度最高的片段进入上下文。
  5. 生成答案:把用户问题、检索片段、回答规则、引用要求和安全边界拼成 Prompt,交给 LLM 生成最终回答。
  6. 工程优化:常见优化包括 chunk 大小和 overlap 调整、混合检索、元数据过滤、权限过滤、rerank、上下文压缩、引用溯源、缓存和效果评估。
  7. 面试收口:RAG 的核心是“先检索,再生成”。链路重点看文档质量、切块策略、向量召回、重排质量、Prompt 组织和答案可追溯性。

2026.06.03#

I/O 多路复用解决什么问题,常用 I/O 多路复用技术对比#

评级:中高难度、中频

  1. I/O 多路复用主要解决用少量线程同时管理大量 I/O 对象的事件等待问题,常用于网络连接、终端输入、磁盘读写和高并发 I/O 服务。
  2. select 的优点是跨平台、接口简单、历史最久;特点是使用 fd 位图表示监听集合。它受 FD_SETSIZE 限制,每次调用都需要传入 fd 集合,内核和用户态之间存在拷贝成本,返回后还要线性扫描找就绪 fd。
  3. poll 用结构体数组替代位图,突破了 select 固定 fd 位图的限制。它仍然需要每次传入全量 fd 数组,返回后也要线性扫描,所以连接数很大时效率会下降。
  4. epoll 的基本流程是通过 epoll_create 创建实例,通过 epoll_ctl 注册或修改 fd,通过 epoll_wait 等待就绪事件。它把 fd 集合长期维护在内核里,返回的是就绪事件列表,适合连接很多、活跃连接较少的高并发网络场景。
  5. epoll 支持 LT 和 ET 两种触发模式。LT 是水平触发,只要 fd 仍可读或可写,就会持续通知;ET 是边缘触发,只在状态变化时通知一次,通常需要配合非阻塞 I/O 循环读写直到 EAGAIN
  6. 面试收口:select 胜在通用简单,poll 解决固定 fd 位图限制,epoll 通过内核维护监听集合和返回就绪列表提升高并发场景效率,Linux 下 Nginx、Redis 这类服务通常依赖这种模型。

2026.06.02#

HTTP 状态码表示的意思#

评级:基础知识、中低频

  1. HTTP 状态码是服务端返回给客户端的三位数字,用来表示本次请求的处理结果。第一位数字表示状态大类,后两位表示具体含义。
  2. 1xx 表示提示信息,代表请求已经收到,客户端可以继续后续操作,常见如 100 Continue
  3. 2xx 表示请求已被成功处理,常见如 200 OK 表示请求成功,201 Created 表示资源创建成功,204 No Content 表示请求成功且响应体为空。
  4. 3xx 表示重定向或缓存相关结果,常见如 301 Moved Permanently 表示永久重定向,302 Found 表示临时重定向,304 Not Modified 表示客户端可以继续使用本地缓存。
  5. 4xx 表示客户端侧请求条件存在问题,常见如 400 Bad Request 表示请求格式或参数有问题,401 Unauthorized 表示需要身份认证,403 Forbidden 表示权限受限,404 Not Found 表示资源路径缺失或地址错误。
  6. 5xx 表示服务端处理链路出现故障,常见如 500 Internal Server Error 表示服务端内部处理异常,502 Bad Gateway 表示网关收到上游异常响应,503 Service Unavailable 表示服务暂时过载或维护,504 Gateway Timeout 表示网关等待上游响应超时。
  7. 面试收口:HTTP 状态码可以按首位数字快速判断请求结果,2xx 看成功处理,3xx 看跳转和缓存,4xx 看客户端请求条件,5xx 看服务端处理链路。

2026.06.01#

TCP 和 UDP 的对比#

评级:基础知识八股、中频

  1. TCP 是面向连接的可靠字节流协议,通信前需要建立连接,通信过程中通过确认应答、超时重传、序列号和校验机制保证数据可靠到达。
  2. UDP 是无连接的数据报协议,发送前无需建立连接,头部更小,交付路径更短,适合对时延敏感或由应用层自定义可靠性的场景。
  3. TCP 提供有序交付、流量控制和拥塞控制,可以把数据按字节流稳定交给上层应用;UDP 以数据报为单位交付,每个报文保留边界,应用层需要自行处理丢包、乱序和重传策略。
  4. TCP 常用于文件传输、网页访问、数据库连接、RPC 等可靠性优先的场景;UDP 常用于实时音视频、在线游戏、DNS、QUIC 等更关注时延和灵活控制的场景。
  5. 面试收口:按连接、可靠性、顺序、控制和应用场景五个维度对比即可,核心是 TCP 追求可靠有序传输,UDP 追求低开销和低时延。

2026.05.29#

Skill 的工作流程,如何写自己的 Skill?#

评级:AI Agent 工程、中低频

  1. Skill 是给 AI Agent 使用的专项能力包,核心作用是把某类任务的触发条件、执行流程、领域知识、规则约束、工具脚本和素材模板沉淀下来,让 Agent 遇到同类任务时可以按稳定流程处理。
  2. 一个 Skill 的工作流程通常分为四层:触发条件决定什么时候启用;工作流程规定先做什么、后做什么;知识与规则提供领域判断标准和输出约束;工具与素材提供脚本、模板、参考资料、图片、字体等可复用资源。
  3. 写自己的 Skill 前,先想清楚它解决什么问题、面向哪些请求、输入是什么、输出是什么、成功标准是什么。适合固化的内容包括重复出现的步骤、固定格式的交付物、容易遗漏的检查项、领域术语、接口规范、脚本命令和模板文件。
  4. 一个常见 Skill 目录包含 SKILL.md,以及可选的 scripts/references/assets/SKILL.md 写触发描述和核心流程;scripts/ 放稳定可执行的脚本;references/ 放按需读取的长文档;assets/ 放最终产物要复用的素材。
  5. SKILL.md 的重点是简洁、可执行、可触发。description 要覆盖用户可能的说法,因为 Agent 会先通过名称和描述判断是否加载 Skill;正文保留关键步骤和判断规则,详细资料放到引用文件里按需读取。
  6. 可以按下面的模板起草:
# Skill 名称

## 1. 适用场景

这个 Skill 用于:[描述它解决的任务类型]

典型触发请求:

- [用户会怎么说 1]
- [用户会怎么说 2]
- [用户会怎么说 3]

## 2. 目标

使用这个 Skill 后,应该产出:[最终交付物]

成功标准:

- [标准 1]
- [标准 2]
- [标准 3]

## 3. 输入

用户可能提供:

- [输入类型 1:文本 / 文件 / 链接 / 数据 / 图片]
- [输入类型 2]
- [输入类型 3]

需要主动确认的信息:

- [关键信息 1]
- [关键信息 2]

## 4. 输出

默认输出格式:

- 类型:[Markdown / JSON / 表格 / 代码 / 报告 / 设计稿]
- 结构:[章节、字段或模块]
- 粒度:[简版 / 标准版 / 详细版]

输出模板:

[在这里放固定输出结构]
  1. 面试收口:Skill 的本质是把可复用的任务经验产品化,先定义触发条件和输入输出,再沉淀流程、规则、工具和素材,让 Agent 在同类任务上稳定地产出高质量结果。

2026.05.28#

什么是 Function Calling?原理是什么?#

评级:中级八股、中频

  1. Function Calling 是大模型根据用户输入生成结构化函数调用请求的能力。它让模型可以把自然语言意图转成可执行的工具调用,比如查数据库、调接口、搜索文档、执行计算或操作业务系统。
  2. 核心输入包括用户问题和工具定义。工具定义通常用 JSON Schema 描述函数名、函数说明、参数字段、参数类型、必填项和约束条件,模型会根据上下文判断需要调用哪个工具以及传入什么参数。
  3. 执行流程是:应用把用户问题和工具列表传给模型;模型输出 tool_calls,其中包含函数名和结构化参数;应用侧解析 tool_calls 并真正执行函数;函数执行结果再返回给模型;模型基于工具结果生成最终回复。
  4. Function Calling 的关键点是模型负责“选择工具和生成参数”,应用负责“执行工具和控制权限”。生产环境需要做好参数校验、权限控制、超时控制、重试、审计日志和异常兜底。
  5. 面试收口:Function Calling 本质是让大模型从自然语言生成结构化工具调用请求,通过外部函数补齐模型自身缺少的实时数据、业务能力和执行能力。

2026.05.27#

谈谈你对 RAG 的理解?#

评级:中级八股、中频

  1. RAG 是 Retrieval-Augmented Generation,中文常叫检索增强生成。核心目标是让大模型在回答前先检索外部知识,再结合检索结果生成答案,从而提升知识时效性和答案准确性。
  2. 典型流程是:先把文档清洗、切分成 chunk,再通过 embedding 模型转成向量并写入向量数据库;用户提问时也会被 embedding 成向量;系统根据向量相似度召回相关文档片段;最后把问题和召回内容一起放进 prompt 交给模型回答。
  3. RAG 常用于企业知识库、客服问答、内部文档助手、法律合同检索、技术文档问答等场景,尤其适合知识经常更新、答案需要引用依据、私有数据保留在外部知识库中统一管理的业务。
  4. RAG 的效果重点看文档质量、切分策略、embedding 模型、召回策略、重排效果和 prompt 组织。工程上常见优化包括混合检索、rerank、元数据过滤、引用溯源和权限过滤。
  5. 面试收口:RAG 的本质是“先检索,再生成”,用外部知识库给大模型补充上下文,让回答更贴近业务数据和最新资料。

2026.05.26#

如何查看网络性能指标?#

评级:高级八股、低频

  1. 先看基础连通性和时延。常用 ping 查看延迟、丢包和抖动;traceroutemtr 查看链路路径和每一跳耗时;curl -w 可以拆分 DNS、TCP 建连、TLS 握手、首字节和总耗时。
  2. 看网卡层指标。ip -s linkifconfigethtool -S eth0 可以查看收发包量、错误包、丢包、重传、队列丢弃、网卡速率和双工模式;sar -n DEV 1 可以持续观察各网卡吞吐。
  3. 看 TCP 层指标。ss -s 查看连接整体状态;ss -antp 查看具体连接、状态和进程;netstat -snstat 查看 TCP 重传、连接失败、超时、reset 等统计;sar -n TCP,ETCP 1 可以看主动连接、被动连接、重传和错误。
  4. 看带宽、流量和进程维度。iftopnloadvnstat 看实时流量;nethogs 看进程流量;iperf3 做端到端带宽压测;tcpdump 抓包分析三次握手、重传、reset、DNS 和应用协议问题。
  5. 线上排查建议按层次推进:应用耗时拆分、主机网卡指标、TCP 状态与重传、链路丢包与抖动、抓包验证。核心关注指标是延迟、丢包、吞吐、连接数、重传率、错误包、队列丢弃和带宽利用率。

2026.05.25#

map 扩容机制?#

评级:中级八股、中频

  1. Go 的 map 底层是哈希表结构,核心对象是 hmap。数据存放在一组 bucket 中,每个 bucket 通常保存 8 个 key/value,并配有 tophash 用于快速判断候选项;哈希冲突较多时会挂接 overflow bucket。
  2. map 查找时会先对 key 计算 hash,再根据低位定位 bucket,在 bucket 内结合 tophash 和 key 比较找到目标元素。插入时如果目标 bucket 放满,会使用 overflow bucket 承接冲突数据。
  3. 扩容触发主要有两类:元素数量增长导致负载因子过高;overflow bucket 过多导致哈希表局部冲突严重。前者通常触发翻倍扩容,后者可能触发等量扩容,用于整理 bucket 和 overflow bucket。
  4. Go 的 map 扩容采用渐进式搬迁。扩容开始后会分配新的 bucket 数组,旧 bucket 保存在 oldbuckets 中;后续每次读写 map 时顺带搬迁一部分旧 bucket,逐步把数据迁到新 bucket,降低一次性搬迁带来的停顿。
  5. 扩容期间访问 map 时,需要同时考虑新旧 bucket。查找、插入和删除会根据 bucket 的迁移状态决定访问旧表还是新表,并在操作过程中推进迁移。
  6. 面试收口:Go map 通过 bucket + overflow bucket 解决哈希冲突,通过负载因子和 overflow 数量触发扩容,通过渐进式搬迁平滑扩容成本。

2026.05.22#

defer 什么时候执行?多个 defer 的执行顺序,defer 的应用场景?#

评级:中级八股、中低频

  1. defer 会在当前函数返回前执行。函数执行到 return、正常走到函数末尾,或者因为 panic 退出当前函数时,已经注册的 defer 都会按规则执行。
  2. 多个 defer 的执行顺序是后进先出,也就是最后注册的 defer 最先执行。
  3. defer 的参数会在注册时立即求值。比如 defer fmt.Println(i) 会保存当时的 i 值;闭包形式 defer func(){ fmt.Println(i) }() 会在真正执行时读取外层变量。
  4. defer 常用于资源收尾,比如关闭文件、关闭连接、释放锁、提交或回滚事务、捕获 panic、统计耗时和统一记录日志。
  5. 面试收口:defer 是函数级收尾机制,执行时机在函数退出阶段,顺序是 LIFO,参数求值发生在注册阶段。

2026.05.21#

MySQL 如何优雅分页?#

评级:中级八股、中频

  1. 普通分页通常写成 limit size offset n。MySQL 需要先扫描并取出 offset + size 条记录,再丢弃前面的 offset 条,页码越深扫描成本越高,深分页性能会明显变差。
  2. 游标分页基于主键或索引字段向后翻页,例如 where id > last_id order by id limit size。这种方式可以利用索引直接定位,性能稳定,适合信息流、列表滚动、时间线等连续翻页场景。
  3. 延迟关联适合需要返回完整行的深分页。先通过覆盖索引查出目标主键集合,再用主键回表查询完整数据,减少大量无效回表。
  4. 设计分页时要保证排序稳定。常见做法是使用唯一字段兜底,比如 order by create_time desc, id desc,游标也保存 create_timeid,避免相同时间的数据顺序抖动。
  5. 面试收口:普通分页实现简单,深分页优先考虑基于索引的游标分页;需要跳页或兼容后台列表时,可以用延迟关联和覆盖索引降低扫描与回表成本。

2026.05.20#

RabbitMQ 的交换机类型以及适用场景#

评级:中级八股、中低频

  1. RabbitMQ 常见 Exchange 类型有 directfanouttopicheaders
  2. direct 按 routing key 精确匹配,把消息路由到绑定 key 完全一致的队列,适合点对点分发、明确业务类型路由和任务队列。
  3. fanout 会把消息广播到所有绑定到该交换机的队列,routing key 在路由时被忽略,适合系统通知、广播消息、日志分发和多消费者同时接收同一事件。
  4. topic 按 routing key 的通配符模式匹配,* 匹配一个单词,# 匹配零个或多个单词,适合复杂主题路由,比如 order.createdorder.paiduser.* 这类事件分发。
  5. headers 根据消息头中的键值对匹配,适合按多个属性组合筛选的场景。实际业务里使用频率较低,因为配置和维护成本更高。
  6. 面试收口:业务里最常用的是 directtopic;简单精确路由用 direct,广播用 fanout,复杂主题匹配用 topic,多属性头部匹配用 headers

2026.05.19#

Redis 宕机如何处理?#

评级:中级八股、中频

重点:快速恢复服务、确认数据是否丢失、排查原因、后续优化。

  1. 先确认 Redis 是否真的宕机,可以从应用报错、监控告警、连接测试、端口状态、进程状态和实例健康检查入手。单机实例优先快速重启恢复服务;主从、哨兵或 Cluster 环境先查看节点状态、主从复制状态、故障转移结果和客户端路由情况,再确定重启、切主、扩容或摘除异常节点。
  2. 恢复后重点确认数据是否完整。Redis 主要依靠 RDB 和 AOF 做持久化恢复:RDB 是某个时间点的快照,恢复速度快,但会丢失快照之后的写入;AOF 记录写命令,数据更完整,恢复时会重放日志。实例拉起后要检查加载日志、key 数量、核心业务数据、主从一致性和应用读写结果。
  3. 查看日志和监控排查宕机原因。常见方向包括内存打满、maxmemory 配置异常、磁盘空间不足、AOF rewrite 或 fsync 抖动、RDB fork 卡顿、慢查询、大 Key、网络抖动、机器 OOM、容器资源限制、主从复制异常和版本缺陷。定位原因后按问题类型处理,比如清理大 Key、调整内存策略、扩容、优化持久化参数或修复部署环境。
  4. 生产环境要做高可用和兜底。推荐使用主从 + Sentinel 或 Redis Cluster,开启合适的 RDB/AOF 持久化策略,配置监控告警,关注内存、连接数、慢日志、复制延迟、fork 耗时和磁盘 I/O;业务侧准备本地缓存、限流、降级、熔断和数据回源策略。
  5. 面试收口:Redis 宕机处理顺序是先恢复服务,再确认数据,再排查原因,最后补齐高可用、持久化、监控和降级能力。

2026.05.18#

HTTPS 的 S 是什么?为什么要有 TLS?TLS 的过程?#

评级:中高级八股、中频

  1. 讲出 HTTPS 的含义:HTTPS 里的 S 是 Secure,工程含义是 HTTP over TLS,也就是 HTTP 运行在 TLS 安全层之上。TLS 给 HTTP 增加加密、身份认证和完整性保护。
  2. 讲出为什么要有 TLS:HTTP 明文传输,内容容易被窃听、篡改和伪造;TLS 提供机密性、身份认证和完整性。机密性保证传输内容被加密;身份认证通过证书确认服务端身份;完整性保证数据传输过程中被改动时能被发现。
  3. 讲出 TLS 握手过程:客户端先发送 ClientHello,携带支持的 TLS 版本、加密套件、随机数、SNI、ALPN,以及 TLS 1.3 里的 key share;服务端返回 ServerHello,选择 TLS 版本和加密套件,并返回证书链和自己的密钥协商材料;客户端校验证书链、域名和有效期,再通过 ECDHE 等算法和服务端协商共享密钥;双方基于共享密钥生成会话密钥。
  4. 讲出通信阶段:握手完成后,HTTP 数据使用对称加密传输,比如 AES-GCM 或 ChaCha20-Poly1305,同时结合认证标签完成完整性校验。整体思路是握手阶段完成身份认证和密钥协商,通信阶段使用对称加密传输数据。
  5. 面试收口:HTTPS 的 S 是 Secure,本质是 HTTP 运行在 TLS 之上;TLS 握手负责认证服务端身份、协商会话密钥并建立安全通道,后续 HTTP 数据使用对称加密传输。

2026.05.15#

sync.Map 和 map 的区别,sync.Map 原理#

评级:中级八股、中频

  1. 讲出普通 map 的特点:map 是 Go 的内置哈希表,性能高、类型安全,适合普通场景;多个 goroutine 共享读写时,通常配合 sync.Mutexsync.RWMutex 做并发控制。
  2. 讲出 sync.Map 的特点:sync.Map 是标准库提供的并发安全 map,适合读多写少、key 相对稳定、缓存类场景,比如 key 写入一次后多次读取,或者多个 goroutine 主要读写不同 key。
  3. 讲出工程取舍:sync.Map 通过读写分离降低读路径开销,同时 API 基于 any,使用时需要类型断言;写多、需要复杂复合操作、需要强类型约束的场景,普通 map + Mutex/RWMutex 更常用。
  4. 讲出核心原理:sync.Map 内部维护 readdirty 两份数据。read 是只读区,读操作先查 read,命中时通过原子操作读取;dirty 是写区,写操作和读穿透会进入加锁路径并访问 dirty
  5. 讲出晋升机制:misses 用来统计读 read 失败后访问 dirty 的次数;当穿透次数达到阈值时,dirty 会晋升为新的 read,后续读请求继续走低开销路径。entry 保存真实 value,并通过原子指针管理 value 的状态。
  6. 面试收口:普通 map 更适合大多数明确受控的场景;并发共享、读多写少、key 变化少的缓存类场景可以选择 sync.Map

2026.05.14#

MySQL 中常见的引擎,InnoDB 和 MyISAM 区别,应用的场景?#

评级:中高级八股、中低频

  1. 讲出常见存储引擎:MySQL 常见引擎有 InnoDB、MyISAM、Memory、CSV 等,线上业务最常用 InnoDB,MySQL 5.5 之后默认引擎是 InnoDB。
  2. 讲出核心区别:InnoDB 支持事务、行级锁、外键、崩溃恢复和 MVCC,适合高并发 OLTP;MyISAM 结构简单,使用表级锁,读多写少场景表现轻量。
  3. 讲出适用场景:InnoDB 适合大多数线上业务表,尤其是订单、支付、账户、库存等核心业务表;MyISAM 适合历史遗留系统、低并发读多写少、对事务一致性要求很低的场景。
  4. 面试收口:实际生产中核心业务表通常优先选择 InnoDB,因为它在事务、并发控制、崩溃恢复和数据一致性上更完整。

2026.05.13#

什么是 Buffer Pool?为什么要有?它的结构是什么,以及如何管理?和 Redo Log 的关系?#

评级:中高级八股、中低频

  1. 讲出 Buffer Pool 的概念:Buffer Pool 是 InnoDB 的内存缓存区,缓存数据页、索引页、undo 页等。查询一行数据时,InnoDB 会先定位这行数据所在的数据页,然后把整个页加载到 Buffer Pool 中;后续再次访问同一页时,可以直接从内存读取。
  2. 讲出为什么要有 Buffer Pool:核心目标是减少磁盘读取、提升更新性能、合并随机写、提高整体吞吐。读请求命中缓存后减少磁盘 I/O;写请求先改内存页,后续由后台线程按策略刷盘。
  3. 讲出 Buffer Pool 的结构:Buffer Pool 由多个缓存页 Frame 组成,每个缓存页都有对应的控制块 Control Block,控制块记录页号、表空间、脏页标记、访问状态等元信息;常见链表包括 Free List、Flush List、LRU List。
  4. 讲出如何管理:Free List 管理空闲页;LRU List 管理页的命中、冷热分层和淘汰;Flush List 管理脏页刷盘。管理重点是页的加载、命中、淘汰、刷盘。
  5. 讲出和 Redo Log 的关系:Buffer Pool 提升性能,Redo Log 保证崩溃恢复。事务更新数据时先修改 Buffer Pool 中的数据页并生成 redo 记录,提交时 redo 按策略落盘;即使脏页还在内存中,宕机后也能通过 Redo Log 重放恢复。
  6. 面试收口:Buffer Pool 让 InnoDB 把随机磁盘读写转成内存访问和后台批量刷盘,Redo Log 配合 WAL 机制保证已提交事务的持久性。

2026.05.11#

如何保证消息队列消息可靠投递以及重复消费的幂等性?#

评级:中级八股、中频

  1. 生产端:常见风险是业务数据已提交但消息发送失败、网络超时导致发送结果未知、重试产生重复消息。解决方案是开启生产者确认机制,比如 RabbitMQ 的 publisher confirm、Kafka 的 ack 机制;发送失败要重试,消息携带全局唯一 message_id 或业务幂等键;关键业务可用本地消息表或 Outbox 模式,在同一个本地事务里写业务数据和消息记录,再由后台任务扫描补偿投递,保证业务数据和消息发送最终一致。
  2. 服务端:常见风险是 Broker 收到消息后宕机、消息只在内存中、队列或分区副本异常。解决方案是开启消息持久化、队列或 Topic 持久化、落盘后确认、多副本或镜像队列;Kafka 场景可以配置 acks=all、合理的 min.insync.replicas 和副本数,并配合监控告警、重试队列、死信队列处理异常消息。
  3. 消费端:常见风险是业务处理完成前自动 ACK、业务处理成功后 ACK 失败、消费过程中宕机。解决方案是使用手动 ACK,在业务处理成功且状态落库后再确认;处理失败时重试,超过次数后进入死信队列;消费逻辑保留幂等能力,因为 ACK 丢失、重平衡、重试都会带来重复投递。
  4. 幂等性的概念:同一条消息被消费一次或多次,对业务产生的最终结果一致。比如扣库存、发券、创建订单这类操作,重复消费时要识别同一个业务动作,并让它只产生一次有效业务结果。
  5. 保证幂等性的方法:常见做法是“唯一 ID + 去重表”,消费前用 message_id 或业务唯一键插入去重表,并给该字段加唯一索引;插入成功后执行业务逻辑,插入冲突说明这条消息已经处理过,直接返回成功。也可以用业务唯一索引、状态机流转、Redis SETNX 短期去重、乐观锁版本号等方式,核心是让重复消息命中同一条业务记录或同一次状态流转。
  6. 面试可直接说:生产端靠确认、重试、本地消息表保证消息发出;服务端靠持久化、副本和确认机制保证消息存住;消费端靠手动 ACK、失败重试、死信队列保证消息被处理;重复消费靠全局唯一 ID、去重表、唯一索引和状态机做幂等。

2026.05.08#

TCP 三次握手四次挥手#

评级:基础八股、中频

  1. 讲出三次握手:客户端发送 SYN 请求建立连接;服务端收到后返回 SYN + ACK;客户端再发送 ACK,双方进入 ESTABLISHED 状态。这个过程完成双方收发能力确认和初始序列号同步。
  2. 讲出四次挥手:主动关闭方发送 FIN 表示自己发送方向关闭;被动关闭方返回 ACK;被动关闭方处理完剩余数据后发送 FIN;主动关闭方返回 ACK,等待 TIME_WAIT 后连接释放。
  3. 讲出挥手比握手多一次的原因:TCP 是全双工协议,两个方向的数据发送能力要分别关闭。收到对方 FIN 后,只代表对方发送方向关闭,本端可能还有数据要发,所以先回 ACK,等本端数据发送完成后再发 FIN
  4. 讲出两次握手的问题:两次握手只能让服务端确认客户端的发送能力和服务端的接收能力,客户端对服务端 SYN + ACK 的接收确认没有回到服务端;历史 SYN 报文如果延迟到达,服务端可能误建立连接并占用资源。

2026.05.07#

如何建立合适的索引?#

评级:基础八股、中频

  1. 先看查询语句和业务访问路径,再决定索引服务哪些 SQL。
  2. 选择适合建索引的字段,优先考虑高频过滤列、关联列、排序分组列,以及区分度高的字段。
  3. 设计联合索引时结合最左前缀原则,把高频等值查询列放在前面,再结合范围查询、排序、分组和覆盖索引需求安排顺序。
  4. 评估索引代价,索引会占用额外空间,也会增加写入、更新和删除时的维护成本,需要在查询收益和写入成本之间找到平衡点。
  5. EXPLAIN 查看执行计划,重点关注 typekeyrowsExtra,判断索引是否生效。

2026.05.06#

Redis 五种数据结构及其原理#

评级:基础八股、中频

  1. 讲出 Redis 五种基础数据结构:String、Hash、List、Set、ZSet。
  2. 讲出各个结构的底层实现:String 基于 SDS;Hash 小数据量常用 listpack,数据变大后转 hashtable;List 基于 quicklist;Set 小整数集合常用 intset,复杂集合用 hashtable;ZSet 小数据量常用 listpack,大数据量用 skiplist + dict。
  3. 讲出跳表的实现:跳表是多层有序链表,节点通过随机层数建立多级索引,查询时从最高层向右、向下逐步定位,平均查询、插入、删除复杂度是 O(logN),ZSet 借助跳表支持范围查询和有序遍历。

2026.04.30#

Redis 缓存穿透、击穿、雪崩是什么,如何避免?#

评级:基础八股、中高频

  1. 讲出三个概念:缓存穿透是查询无效数据,请求穿过缓存后继续打到数据库;缓存击穿是热点 key 过期或重建时,大量并发请求集中打到数据库;缓存雪崩是大量 key 集中过期或缓存服务故障,请求大面积打到数据库。
  2. 讲出发生场景:穿透常见于恶意请求、参数异常、业务数据被删除;击穿常见于爆款商品、热点用户、热点配置等高频 key 失效;雪崩常见于批量设置相同 TTL、缓存集群故障、缓存预热缺失。
  3. 讲出避免方案:穿透用参数校验、布隆过滤器、空值缓存短 TTL;击穿用互斥锁、singleflight、逻辑过期、后台异步刷新;雪崩用 TTL 随机化、分层缓存、限流降级、集群高可用和缓存预热。

2026.04.29#

RabbitMQ、Kafka、RocketMQ 对比#

评级:八股外知识、低频

  1. 讲出 RabbitMQ 的优势和适合场景:RabbitMQ 基于 AMQP,Exchange 路由能力强,支持 Direct、Topic、Fanout 等模型,生态成熟、使用简单,适合业务系统解耦、任务队列、后台异步处理、复杂路由和失败重试。
  2. 讲出 Kafka 的优势和适合场景:Kafka 吞吐量高,分区并行、顺序追加写、消费位点管理能力强,适合日志采集、埋点上报、实时计算、数据管道和事件流平台。
  3. 讲出 RocketMQ 的优势和适合场景:RocketMQ 的事务消息、顺序消息、延时消息、消息轨迹等业务能力完善,适合电商订单、金融支付、交易链路和分布式事务最终一致性。

2026.04.28#

Go 中的 Channel#

评级:基础八股、中频

  1. 讲出 CSP 思想:CSP(Communicating Sequential Processes)强调通过通信组织并发,Go 里用 goroutine 执行任务,用 channel 传递消息,让数据流动带动协作,减少共享可变状态竞争。
  2. 讲出 channel 的两种类型以及适用场景:无缓冲 channel 的发送和接收同步配对,适合强同步、任务交接、信号通知;有缓冲 channel 带队列能力,适合削峰、限流、生产消费解耦。
  3. 讲出关闭 channel 后发送和接收的行为:向已关闭 channel 发送会 panic;接收会先读完缓冲区剩余数据,缓冲区读尽后继续接收会得到元素零值,ok=false

2026.04.27#

MySQL 中 InnoDB 引擎三种日志及作用#

评级:基础八股、中高频

  1. 讲出三种日志的作用和使用场景:undo log 记录数据旧版本,支持事务回滚和 MVCC;redo log 记录数据页的物理修改,支持崩溃恢复和 WAL;binlog 记录数据库逻辑变更,支持主从复制、增量备份和基于时间点恢复。
  2. 讲出 redo log 和 binlog 的区别:redo log 属于 InnoDB 层物理日志,循环写,核心目标是 crash recovery;binlog 属于 MySQL Server 层逻辑日志,追加写,核心目标是复制和归档恢复。
  3. 讲出 redo log 的归属:redo log 是 InnoDB 引擎日志,和 Buffer Pool、checkpoint、脏页刷盘一起保障事务持久性。

2026.04.24#

输入一条 URL 会发生什么?#

评级:基础八股、中高频

  1. 讲出 IP 和域名的关系:域名是给人看的地址,IP 是网络通信真正使用的地址,DNS 负责把域名解析成 IP。
  2. 讲出 DNS 的原理:浏览器和操作系统会先查本地缓存,缓存里没有再向本地域名服务器发起查询,本地域名服务器再递归或迭代访问根服务器、顶级域服务器和权威 DNS 服务器,最终拿到目标 IP。
  3. 讲出完整链路:浏览器解析 URL 后先做 DNS 解析,再建立 TCP 连接;如果是 HTTPS 还要进行 TLS 握手;随后发送 HTTP 请求,服务器处理请求并返回响应,浏览器接收 HTML、CSS、JS 等资源,完成解析、渲染和页面展示。

2026.04.23#

内存分页的好处以及缺页中断是什么?#

评级:中级八股、中低频

  1. 讲出内存分页的概念:把虚拟内存和物理内存都按固定大小划分为一页一页,进程访问地址时通过页表把虚拟页映射到物理页。
  2. 讲出分页的好处:固定大小分配让内存管理更简单,也能减少外部内存碎片,提高内存利用率。
  3. 讲出缺页中断的概念及发生情况:进程访问的页面当前没有在物理内存中时,会触发缺页中断,由操作系统把目标页从磁盘调入内存,更新页表后再继续执行指令。

2026.04.22#

慢查询的排查与优化#

评级:中高级八股、中低频

  1. 先开启慢查询日志定位最慢的 SQL,重点关注执行时间、扫描行数、返回行数和调用频率,优先处理耗时高且出现频繁的语句。
  2. EXPLAIN 分析执行计划,重点看 typekeyrowsExtra,判断是否出现全表扫描、回表过多、临时表或文件排序。
  3. 根据查询条件、排序和分组优化索引,优先考虑高频过滤列、联合索引和覆盖索引,同时清理重复索引和低价值索引。
  4. 调整 SQL 写法,减少 SELECT *、深分页、索引列函数计算和隐式类型转换,让查询条件更容易命中索引。

2026.04.21#

Redis 缓存与数据库如何保证最终一致性?#

评级:中级八股、项目中缓存相关问题出现概率较高

  1. 讲清三种方案:更新数据库后删除缓存、删除缓存后更新数据库、延迟双删;其中主流写法是 Cache Aside 里的“更新数据库后删除缓存”。
  2. 讲出三种方案的并发风险:更新数据库后删除缓存会遇到删缓存失败和旧值回填;删除缓存后更新数据库会遇到数据库更新窗口里的旧值回填;延迟双删通过第二次删除清理并发窗口里的脏缓存,效果取决于延迟时间设置。
  3. 讲出优化手段:删除缓存失败重试、MQ 或 binlog 订阅做异步补偿删除、给缓存加 TTL 兜底、热点 key 回填时加互斥锁或 singleflight、一致性要求高的读请求走主库。

2026.04.20#

死锁的四个必要条件#

评级:基础八股、中频八股

  1. 讲出死锁的概念:多个线程或进程在运行过程中,因为争夺资源而相互等待,最终都无法继续执行。
  2. 讲出死锁的四个必要条件:互斥、请求并保持、不可剥夺、循环等待。
  3. 讲出如何避免死锁:核心是破坏其中一个条件,比如一次性申请资源、申请失败后释放已有资源、按照固定顺序加锁。

2026.04.16#

虚拟内存是什么?为什么要有?#

评级:中等八股、中频八股

  1. 讲出虚拟内存的概念与和物理内存(RAM)的对比:虚拟内存是进程看到的逻辑地址空间,需要通过映射才会落到物理内存。
  2. 讲出虚拟内存是分页管理的:按页划分内存,依赖页表完成虚拟地址到物理地址的转换,缺页时触发缺页中断并换入页面。
  3. 讲出虚拟内存的好处与缺陷:好处是进程隔离、按需加载、提升内存利用率、支持更大地址空间;缺陷是地址转换和页表维护有开销,缺页与频繁换页会导致性能下降(抖动)。

2026.04.15#

Redis 过期删除与内存淘汰#

评级:中等八股、中频八股

  1. 讲出过期删除与内存淘汰的区别及触发场景。
  2. 讲出过期删除策略及其优缺点对比。
  3. 讲出内存淘汰策略及其原理(主要是 LRU 与 LFU)。

2026.04.14#

为什么 MySQL 存储引擎普遍采用 B+ 树?#

评级:基础八股、高频八股

  1. 讲出 B+ 树的结构。
  2. 讲出 B+ 树相较于 B 树的优势。
  3. 重点讲出树查询时间复杂度主要取决于树的高度。
  4. 讲出 B+ 树相较于哈希的优势。
  5. 讲出更适合数据库页结构。

2026.04.13#

HTTP/1.1、HTTP/2、HTTP/3 的区别#

  1. 讲出 HTTP/1.1 队头阻塞。
  2. 讲出 HTTP/2 多路复用、二进制分帧、头部压缩。
  3. 讲出 HTTP/2 的缺陷以及 HTTP/3 是如何解决的(UDP + QUIC)。

2026.04.10#

GMP 是什么?核心作用是什么?#

  1. 在早期 GM 模型中,如果直接给每个 M 分配本地队列和上下文资源,是否也能缓解全局锁冲突?为什么仍需要在 G 和 M 之间引入 P 这个抽象层?
  2. M 是否可以直接进行任务窃取?为什么调度设计中仍需要 P?
  3. 如果 M 阻塞掉,P 会怎么处理?
  4. 怎么动态知道 M 会阻塞,并提前退回 P?
  5. M 被解绑后,P 的归属会如何变化?新的 M 如何接手该 P?
  6. 如果所有 M 都陷入系统调用,程序是否会停滞?调度器如何避免这种情况?

2026.04.09#

MySQL 索引失效的场景?#

  1. 不满足最左匹配原则。
  2. 对索引列做了函数操作。
  3. 对索引列做了运算。
  4. SELECT * 查询。
  5. LIKE '%xxx' 前置通配符。
  6. OR 条件使用不当。
  7. 索引列和查询条件类型不一致,发生隐式转换。
  8. 联合索引中,范围查询后面的列失效。

2026.04.08#

进程的 5 个状态是什么?状态在什么情况下会转变?#

  1. 讲出五种状态。
  2. 讲出状态之间如何转换。

2026.04.07#

如何保证消息队列的可靠性?#

  1. 从链路作答:生产者发送消息 -> Broker 存储消息 -> 消费者消费消息。
  2. 讲出生产者和消费者分别有哪些确认机制。
  3. 考虑消息重复消费。

2026.04.02#

Redis 的持久化机制?#

  1. 讲出 RDB 和 AOF 是什么。
  2. 讲出 RDB 和 AOF 优缺点对比。
  3. 讲出两者的底层原理。
  4. 讲出混合持久化。

结论:RDB 适合做容灾备份,AOF 适合保证数据一致性,而生产环境通常开启混合持久化。

2026.04.01#

MySQL 的事务是如何实现的?#

基础:答出 ACID。
深入:讲出每个特性怎么保障的。原子性靠 undo log,持久性靠 InnoDB 的 redo log,隔离性靠锁和 MVCC,一致性由约束、事务机制和业务逻辑共同保证。

2026.03.31#

AI 相关基础概念科普#

讲出你对以下概念的理解:

  1. token
  2. llm
  3. Context
  4. Prompt
  5. RAG
  6. Function call
  7. MCP
  8. Skills
  9. Agent
  10. Harness

2026.03.30#

自旋锁和互斥锁的区别#

自旋锁在获取锁失败后会持续循环尝试,不主动让出 CPU;互斥锁在获取锁失败后会阻塞线程并让出 CPU,等锁可用时再被唤醒。

  1. 自旋锁避免了线程阻塞和唤醒的上下文切换开销,但等待期间会持续消耗 CPU。
  2. 互斥锁等待时不占用 CPU,但阻塞和唤醒需要进行线程调度,存在上下文切换开销。
  3. 自旋锁适合临界区很短、锁竞争较轻且运行在多核 CPU 上的场景;互斥锁适合临界区较长或竞争较激烈的场景。
  4. 如果持锁时间不可控,使用自旋锁可能浪费大量 CPU,甚至降低系统整体吞吐,因此通常优先使用互斥锁,只在能够证明临界区足够短时考虑自旋锁。

结论:等待时间短时,自旋锁可以用 CPU 时间换取更低的切换延迟;等待时间较长时,互斥锁通过阻塞线程减少 CPU 浪费。

内容反馈

发现错误或想补充内容?

GoClub 是静态知识库,评论功能不再加载第三方脚本。你可以在 GitHub 仓库提交 Issue,反馈会直接进入项目协作流程。

提交 GitHub 反馈