分类: 未分类

  • 一些工具链/中间件的面经(可能算是面经)(纯原创哦)

    有部分排版问题,懒得改了,这是 Markdown 复制过来的。

    Kafka & NATS

    Kafka:高吞吐、持久化的消息系统

    端口:9092

    通俗来说干嘛的?把各种服务的消息丢进去的日志,然后提供接口给分析系统分析数据。(不是 Kafka 里的,是开发者写的)

    Apache Kafka 是一个分布式、高吞吐、持久化日志系统,常用于日志收集、实时流处理、大数据管道。

    Kafka 是什么?

    • Kafka 是由 Apache 基金会开发的 Java 程序
    • 运行 Kafka 需要:
    • 启动 Kafka 服务器(kafka-server-start.sh
    • 依赖 ZooKeeper(新版已支持移除)
    • 你的程序(用 Go、Java、Python 都可以)通过 Kafka 客户端 SDK 与 Kafka 服务交互

    Kafka 是一个独立运行的服务进程

    Go 程序只是客户端(通过 confluent-kafka-go 等包)

    模块

    模块说明
    Producer负责向 Kafka 集群发送消息,支持异步、分区路由、压缩等
    BrokerKafka 服务端组件,接收消息、保存日志、处理消费者请求
    Topic & Partition逻辑主题划分单位,Partition 是并发和负载均衡的核心
    Consumer订阅并消费消息,支持 Consumer Group 机制实现负载均衡
    Zookeeper / Kafka ControllerKafka 的控制面组件(早期依赖 ZK,现在逐步用 Raft 替代)
    Storage Layer (Log)顺序写入磁盘、段文件、索引文件、高效日志存储
    Replication分区副本机制,用于高可用,支持 ISR(in-sync replica)机制

    特点

    • 持久化强:数据写入磁盘,可保留几天或几周
    • 高吞吐:适合处理大量数据(TB 级别)
    • 可分区/可复制:天生支持分布式
    • 消费者分组:支持一个 topic 多个消费者组并发消费

    使用场景

    • 日志收集(ELK)
    • 实时指标分析
    • 流式数据处理(如 Spark、Flink)
    • 解耦微服务的数据依赖

    Kafka 怎么保证消息不丢失

    Kafka 消息可靠性保证主要依赖以下机制:

    1. 磁盘顺序写(持久化)
      消息写入后立即追加到分区日志文件(log segment),并调用 fsync 或定期刷盘,防止进程崩溃或重启导致数据丢失。
    2. 多副本机制(Replication)
      每个分区有多个副本(replica),分布在不同 broker 上,一个 leader 负责读写,follower 从 leader 拉取数据。
    3. ACK 确认机制
      生产者发送消息时可设置 acks 参数:
    • acks=0:不等待确认(可能丢失)
    • acks=1:leader 写入成功即返回(follower 可能还没同步)
    • acks=-1 / all:leader 和 ISR(in-sync replicas)中所有副本写入成功才返回,可靠性最高
    1. ISR(同步副本集合)
      Kafka 维护一个与 leader 数据完全同步的 follower 列表,只有 ISR 中的副本才参与 acks=-1 的确认,保证数据一致性。

    NATS:轻量、低延迟的消息系统

    端口:4222

    通俗来说干嘛的?微服务之间快速传递消息、实时反应。它可以把一个服务的消息转发给多个服务,例如下单服务把信息发给风控、通知、日志。

    NATS 是一个轻量级、高性能、低延迟的消息系统,强调简洁与速度,使用 Go 实现。

    NATS 是什么?

    • NATS 是一个用 Go 写的消息服务器程序
    • 核心组件是 nats-server,你需要像运行 Redis 一样运行它
      nats-server -p 4222
    • 客户端用各种语言 SDK 连接(如 github.com/nats-io/nats.go

    NATS 也是一个独立运行的服务进程

    你写的 Go 程序用 SDK 与它通信,不嵌入服务逻辑。

    特点:

    • 低延迟:通信延迟非常小(微秒级)
    • 轻量简洁:安装快,配置少
    • 适合内网微服务通信:默认不做持久化(JetStream 扩展支持持久化)
    • 支持 Pub/Sub、Request/Reply

    使用场景:

    • 微服务之间通信
    • 实时事件广播(如游戏、IoT)
    • 替代 RPC 通信

    对比

    特性KafkaNATS
    持久化强,磁盘存储,适合大数据默认无(JetStream 才支持)
    吞吐非常高高,但主要强调低延迟
    延迟毫秒级微秒级
    安装复杂度较复杂非常轻量
    编程模型Topic + 分区 + 消费者组Pub/Sub、Req/Reply、Queue
    使用语言Java 生态(客户端支持广)原生 Go 实现(跨语言支持)
    典型使用场景日志处理、大数据管道微服务通信、事件通知

    Kafka 和 NATS 都是独立运行的程序(Server)

    就像 MySQL 一样,它们是需要单独部署和运行的服务进程,而不是 Go 的普通库。

    Redis – Remote Dictionary Server

    端口:6379、6380(TLS)

    Redis 是一个巨大的 Map,存储 key-value。


    〇、为什么快

    1. 内存数据库
    2. 单线程避免上下文切换
    3. I/O 多路复用,同时监听大量连接,实现高并发处理。
    4. 高效的数据结构
    5. 避免磁盘随机 I/O。绝大部分操作在内存中完成,持久化是异步写磁盘,减少阻塞。
    6. 命令简单且优化到位

    一、数据结构

    核心类型

    1. String:最基础的类型,支持整数、浮点、位操作等。
    2. List:双向链表,支持队列、栈操作。
    3. Set:无序集合,元素唯一。
    4. Hash:键值对集合,适合表示对象。
    5. ZSet(Sorted Set):有序集合,基于分数排序。
    6. Bitmap:位图,节省空间进行布尔标记。
    7. HyperLogLog:用于基数估算,节省内存。
    8. Geo:地理空间索引。
    9. Stream:可持久化的消息队列结构。

    二、底层实现

    Set

    • intset:元素全为整数,数量少时使用。
    • hashtable:元素多或包含非整数时自动转换。

    ZSet

    • 跳表(skiplist):按分数有序,用于范围查询、排名。
    • 哈希表:存储成员到分数的映射。
    1. ZSet 的使用场景:
    • 实时排行榜(按分数排序)
    • 延时队列(score 为时间戳)
    • 社交平台点赞、活跃度排序
    • 分布式优先级任务队列
    • 电商按权重排序推荐
    1. 分数相同时的排序规则:
    • Redis 在分数相同情况下,按成员名的字典序排序

    三、跳表机制

    跳表简介

    跳表是一种分层链表结构,每一层都是下层的索引。它以概率方式构建索引,平均查找、插入、删除 时间复杂度为对数级别。跳表的节点指针较少,适合内存存储。

    在 Redis 中,ZSet 的排序部分由跳表实现,支持按分数范围查询、按排名获取等操作。

    复杂度

    • 查找:O(log n)
    • 插入:O(log n)
    • 删除:O(log n)
    • 区间范围查找:O(log n + m),m 为结果集大小

    还有哪些查找数据结构复杂度为 O(log n)

    • 平衡二叉搜索树(AVL 树、红黑树)
    • B 树 / B+ 树(数据库索引结构)
    • 二分查找(在有序数组上)
    • 堆(heap)查找最大/最小值是 O(1),插入/删除是 O(log n)
    • Treap(随机平衡树)

    为什么不用二叉树而用跳表

    • 二叉树需要复杂的旋转维持平衡,实现和维护难度大;跳表实现简单,插入删除无需全局重平衡。
    • 链表结构天然支持范围扫描,跳表的多级索引能直接跳过不相关区间,非常适合范围查询(ZREVRANGEBYSCORE 等)。
    • 内存利用率和局部性:跳表是分层链表结构,插入删除局部修改;二叉树在大量插入删除下可能退化(或需要频繁旋转)。
    • Redis 压缩结构优化:跳表在节点数量小的时候可以用压缩列表节省空间。

    最大高度

    Redis 跳表默认最大高度是 32 层,由常量定义。实际构建过程中,大多数节点只参与低层索引。

    建立层数算法

    Redis 使用随机算法决定层数,采用几何分布(概率为 1/4)。具体算法是:

    • 初始为 1 层
    • 每次以概率 1/4 决定是否增加一层
    • 最终不超过最大高度 32 层

    这个设计保证了跳表整体平衡,同时插入开销较小。


    四、持久化机制 AOF 与 RDB

    AOF(Append Only File):每次写操作都追加一条命令到日志,恢复时可以按顺序重放。

    RDB(Redis Database Backup):定时将内存快照保存为二进制文件。

    项目RDBAOF
    落盘机制周期性快照每次写操作记录为命令
    文件格式二进制压缩快照文本命令日志(可读)
    典型内容紧凑、序列化后的 key-value原始命令,例如:SET x 123
    恢复速度快,加载快照慢,重放命令
    数据安全可能丢失几分钟丢失几秒甚至 0
    占用空间小,可压缩大,命令多,冗余
    使用场景快速恢复、冷备实时恢复、热备

    推荐方式

    • AOF + RDB 混合模式
    • 配置 appendfsync 策略控制写频率

    五、分布式与一致性问题

    分布式 + 主从

    Redis 是内存数据库,如果数据太大存不下怎么办?

    分布式存储 + 哈希槽。

    存不下就做多个 Redis 节点。数据通过哈希计算,落入某个哈希槽,每个分布式节点负责一部分 哈希槽。每个节点都记录了其它节点负责的哈希槽。

    哈希槽在这里是一种中间件,负责逻辑上存储一个数据,然后被一个节点负责存储。当添加节点,我们只需要把部分哈希槽挪进新的节点即可。

    当客户端查询 Redis,它可能访问了错误的节点,这个节点没有对应数据。但由于每个节点都存储了其它节点负责的哈希槽,所以它会返回一个 error,并告诉客户端,到哪里去找正确的数据。这叫 重定向

    如果某节点正在迁移入数据,此时客户端来查找数据,该节点并不知道这个数据是不存在还是未迁入。它通过重定向,让客户端访问数据迁出的节点,即可查到或知道数据不存在。

    每个 Redis 节点可以设置多个从节点,当主节点失效,使用从节点替代。

    主从的哨兵机制

    Redis 的哨兵机制是 Redis 官方提供的高可用组件。它独立于 Redis 服务器进程,负责实时监控主从节点的健康状态。一旦主节点宕机,会自动完成主从切换,并通知客户端更新连接配置,从而实现自动故障转移和服务可用性保障。

    哨兵的三个核心功能:

    1. 监控(Monitoring)
      哨兵不断 ping 主从 Redis 节点,检测其是否宕机。
    2. 通知(Notification)
      当发现某个 Redis 实例出现问题,会通知管理员或系统。
    3. 自动故障转移(Automatic failover)
      当主节点不可用,哨兵会自动将某个从节点升级为主节点,并更新其他从节点的主节点指向。

    注意事项

    • 哨兵本身也是一个独立的进程,需要部署多个实例(通常奇数个)以实现多数投票机制。
    • 客户端通常要支持哨兵协议,或借助中间件实现连接重定向。

    一致性

    1. Redis 不是强一致性的,你下发通知可能会失败的;
    2. 修改全局内存涉及并发安全。
    3. 关于 Redis 不强一致性的问题:
      • Redis 的 Pub/Sub 是 “至多送达”模型,如果订阅者挂了或重启、网络抖动,可能 漏掉消息
      • 所以可以采用如下改进方案:
        • 使用 可靠消息队列 替代(如 Kafka、NATS)
        • 或者在 Redis 里再维护一份 “修改版本号 + 数据快照”
        • 每次订阅后拉取当前版本,与本地版本比对是否同步,不一致则全量拉取

    Redis 的 Pub/Sub 不保证强一致

    • 采用“至多送达”,可能丢失消息
    • 改进方案:
    • Kafka、NATS 等消息队列替代
    • 增加版本号和快照机制进行补偿

    替代中间件(配置中心)

    • Nacos / Apollo / etcd / Consul
    • 特性:
    • Watch 监听变更
    • 版本控制、回滚
    • 多副本一致性(Raft)

    六、MySQL 为主,Redis 为 cache 的正确做法:Cache Aside 模式

    一般缓存的顺序:用户请求->HTTP 缓存->CDN 缓存->代理服务器缓存->进程内缓存->分布式缓存->数据库

    本地缓存(Local Cache) 和 分布式缓存(Distributed Cache)。本地缓存更注重 访问速度,而分布式缓存则关注 数据一致性和扩展性。

    写流程

    1. 客户端写数据 → 写入 MySQL
    2. 写成功后 → 删除对应 Redis 缓存
    3. 下次查询会从 MySQL 读,然后写入 Redis(懒加载)

    读流程

    1. 查询先查 Redis(命中 → 返回)
    2. 未命中 → 查 MySQL → 回写 Redis

    为什么写后要删 Redis?

    • 避免读到旧值(写 MySQL 后 Redis 还留着旧缓存)

    延迟双删策略

    SET MySQL
    DELETE Redis
    sleep 50ms
    DELETE Redis again

    问题背景:并发写入场景下的数据不一致

    我们来举一个具体的例子:

    假设两个请求并发发生:

    1. 请求 A:查询数据
    • 查 Redis,没有命中 → 查数据库 → 读到旧值 → 写入 Redis 缓存
    1. 请求 B:更新数据
    • 写 MySQL 成功
    • 立即删除 Redis 缓存

    这时出现问题了:

    • 请求 B 删除了缓存
    • 但请求 A 随后写入了旧数据到 Redis
    • 最终 Redis 缓存反而是“脏的”,与数据库不一致!

    延迟双删就是为了解决这个时序问题。

    缓存穿透

    突发大流量查询,Redis 中没有缓存,MySQL 中也没有。造成大量无效查询。这是一种恶意攻击。可能造成数据库宕机。如果 key 变化不大,就把这个 key 放进 Redis,注意设置过期时间,避免大量占用 Redis。

    其它方法:

    • 接口验证筛掉明显不合理的查询
    • 针对 IP 或者用户限流
    • 布隆过滤器:用于判断:“某个元素可能在集合中”或“一定不在集合中”。

    缓存击穿

    大流量对小部分 key 查询,Redis 可以扛住,但如果 Redis 中的缓存突然失效(超时),大量流量就会进入数据库,造成宕机。

    解决方案:

    • 设置热点数据永不过期或者热点时期不过期。
    • 加互斥锁,保证只有少量的请求到达数据库。

    缓存雪崩

    大流量对大量数据查询,由于某些原因,大量的 key 几乎同时过期。原因比如不合理的过期时间,或者 Redis 失效。这样会导致数据库压力大甚至死机。

    解决方案:

    • 保证 Redis 可用。使用 Redis 集群、主从。持久化,重启后自动加载数据。
    • 多级缓存,使流量不会全部涌入数据库。
    • 设置随机的失效时间,避免大量数据同时过期。
    • 提前预热,热 key 提前加载。
    • 数据库限流,限制大量流量进入数据库。

    七、常见问题与优化建议

    1. 避免大 key 和 big value

    • 大 key 占内存高,迁移慢,删除慢,可能导致阻塞。
    • big value 影响网络传输效率,压垮带宽。
    • 建议 key 控制在几十字节以内,value 不超过几 KB。

    2. 避免慢查询(slowlog)

    • 单条命令执行时间超 1 毫秒可视为慢查询。
    • 常见慢命令:KEYS、SCAN 大量遍历,SORT、ZRANGE 大范围排序。
    • 使用 slowlog 命令排查问题。

    3. 合理设置过期时间

    • 防止缓存击穿、雪崩。
    • 为所有缓存 key 设置 TTL,避免内存泄露。
    • 使用随机过期时间,防止同时过期造成突发流量。
       EXPIRE key $((300 + RANDOM % 60)) # 随机过期时间

    4. 谨慎使用危险命令

    • KEYS:全库扫描,易阻塞
    • FLUSHALL:清空库
    • EVAL:脚本执行时间长可能阻塞

    5. 连接数限制

    • 默认支持的最大连接数有限(一般 10000 左右),建议使用连接池。
    • 服务端设置 maxclients 限制连接总数。

    6. 限制内存使用,防止 OOM

    • 设置 maxmemory 和 maxmemory-policy(如 volatile-lru、allkeys-lru)
    • 设置淘汰策略防止写满内存导致 Redis crash

    7. 持久化配置建议

    • AOF 可靠但体积大,写频繁时会影响性能。
    • RDB 性能高但可能丢数据。
    • 推荐 AOF + RDB 混合模式,设置合适的 appendfsync 策略。

    8. 单线程阻塞问题

    • Redis 单线程处理命令,阻塞命令会拖慢所有请求
    • 保证每条命令都轻量快速,必要时拆分任务逻辑。

    9. 安全性建议

    • Redis 默认无密码,不能暴露在公网
    • 使用 bind、requirepass、rename-command 等限制访问。
    • 使用防火墙或内网隔离。

    10. 事务和原子性

    Redis 的事务采用 Lua 脚本来完成。

    优点:

    • 原子性强,天然保证“要么都成功,要么都失败”
    • 脚本里可以自由组合对任意 key 和数据结构的操作(支持 string、hash、list、zset、set)
    • 简单、高效、业务侧无感

    适用场景:

    • key 数量可控(Redis 限制 8000 key 内)
    • 不依赖外部状态(Lua 脚本无法调用网络或持久化)

    其实也用(MULTI / EXEC)语句来做事务,但

    • 仅保证命令按顺序执行,不支持失败自动回滚
    • 一旦某个命令失败,其他命令不会自动撤销

    11. 扩展能力

    • 使用 Redis Cluster 进行分片
    • 使用 Codis、Twemproxy 做代理分布式扩展

    12. Redis 热 key 如何优化?

    热 key 问题指某个 key 被高频访问或更新,导致 Redis 单线程被拖慢或热点迁移时压力集中。

    优化方案:

    1. 加本地缓存,如使用 Go 的 sync.Map 或 LRU 缓存,避免频繁请求 Redis
    2. 用 Nginx 或业务层加缓存 TTL 分散访问压力
    3. 使用分片 key(如给 key 加随机后缀),读时做合并处理
    4. 利用 Redis Cluster,将热 key 分散到不同 slot(但不能 hash 到同一 slot)
    5. 对频繁写的热 key,引入消息队列异步落入 Redis
    6. 使用 Lua 脚本或 pipeline 合并访问

    八、大 key 删除方法

    Redis 删除大 key(比如:包含百万元素的 list、set、hash、zset)时,不能直接使用 DEL key,否则可能阻塞 Redis 主线程,导致服务卡顿甚至宕机

    Redis 是单线程模型,任何慢操作都会阻塞整个服务,包括大 key 的删除。下面是应对这种情况的几种推荐做法。

    为什么不能直接 DEL 大 key?

    • DEL key 是同步删除
    • 会立刻释放该 key 占用的内存
    • 如果 key 很大(如 set 有 1 百万个元素),这个释放过程耗时可能达数百毫秒甚至几秒
    • Redis 是单线程,这期间其他命令无法响应

    正确做法:使用非阻塞删除机制

    1. 使用 Redis 4.0+ 的 UNLINK 命令(推荐)
        UNLINK mybigkey
    - **非阻塞删除**
    - Redis 会将 key 从主线程移除,然后在后台线程中异步释放内存
    - 非常适合删除大 key
    - 和 DEL 命令相比,UNLINK 不会阻塞主线程
    支持版本:Redis 4.0 及以上
    1. 分批删除 key(适用于集合类型)
      如果你用的是老版本 Redis(不支持 UNLINK),或者你想更细致控制,可以手动分批删除 key 内的元素
      举例:删除一个大 hash
        HSCAN myhash 0 COUNT 1000
        HDEL myhash field1 field2 ... field1000
    你可以通过脚本或后台服务批量扫描并删除字段,确保每次操作耗时短。
    优点:控制删除粒度
    缺点:逻辑复杂、过程长、删除期间 key 仍存在
    1. 设置过期时间(让 Redis 自己异步删除)
        EXPIRE mybigkey 60
    - 给大 key 设置过期时间,交由 Redis 定时删除
    - Redis 的过期删除是**惰性 + 定期 + 分批策略**,不会一下子删完,也不会阻塞主线程
    - 适用于可接受数据短时间“滞留”的场景
    1. 后台迁移到空数据库,再删除(高级操作)
      • 将大 key 转移到 Redis 空闲的 DB(如 SELECT 15)然后对整个 DB 执行 FLUSHDB ASYNC(Redis 4.0+)
      bash
      MOVE mybigkey 15
      SELECT 15
      FLUSHDB ASYNC
     - FLUSHDB ASYNC 会在后台清除整个数据库,不阻塞主线程
    
     - 非常适合清理批量垃圾数据或灰度下线数据
    
    ---
    
    ## 九、分布式锁
    
    Redis 分布式锁是基于 Redis 的原子操作实现的一种跨进程、跨服务的互斥机制,常用于分布式系统中协调资源访问、保证操作串行化。
    
    1. **基本原理**
    
       利用 Redis 提供的 `SET` 命令的原子性,在设置 key 的同时带上互斥标志和过期时间,达到加锁效果。
    
       核心命令:

    bash
    SET lock_key lock_value NX PX 3000

       含义:
    
       - `NX`:只有当 key 不存在时才设置,确保互斥
       - `PX 3000`:设置过期时间(毫秒),防止死锁
       - `lock_value`:唯一标识当前持锁方(如 UUID)
    
       释放锁:

    bash
    if redis.call(“GET”, KEYS[1]) == ARGV[1] then
    return redis.call(“DEL”, KEYS[1])
    else
    return 0
    end

       避免误删:只有当前持锁者才能释放锁
    
    2. **使用流程**
    
       1. 客户端尝试加锁,使用 `SET NX PX`
       2. 加锁成功:继续执行业务
       3. 加锁失败:可以重试(带退避)或放弃
       4. 业务执行完毕后,校验后释放锁
    
    3. **Redlock 分布式锁协议(官方推荐)**
    
       适用于多节点 Redis 的高可用场景,基本思想是:
    
       - 同时向多个 Redis 实例写入锁(一般是 5 个)
       - 成功获取过半实例的锁(如 3 个),视为加锁成功
       - 设置统一过期时间,所有节点自动释放
       - 释放锁时向所有 Redis 释放本客户端持有的锁
    
       Redlock 优点:
    
       - 容错性强,任意少数实例故障不影响锁可用性
       - 避免主从不一致或节点抖动造成的锁误用
    
       但也存在争议,要求系统时钟稳定、Redis 之间时延低
    
    4. **常见应用场景**
    
       - 秒杀场景:防止超卖,控制库存扣减操作的并发性
       - 防止重复提交表单
       - 分布式定时任务:控制定时任务只在一个节点执行
       - 创建唯一资源(如订单号、快照 ID)
    
    5. **实现时的注意事项**
    
       1. 锁必须设置过期时间,防止程序异常死锁
       2. 锁标识必须唯一,释放锁前必须比对持有者身份
       3. 锁不可重入(默认),如需可重入需自行扩展
       4. 尽量用 Lua 脚本执行解锁逻辑,避免非原子性
       5. 不推荐使用 `SETNX` 和 `EXPIRE` 分开写,可能造成非原子加锁
    
    6. **示例伪代码**(Go)

    go
    // 加锁
    success := redisClient.Set(“lock:order”, uuid, “NX”, “PX”, 3000).Result()

    // 解锁(Lua 脚本)
    script := if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end
    redisClient.Eval(script, []string{“lock:order”}, uuid)

    ---
    
    ## 十、实战设计
    
    1. 生成以小时为单位的前 100 首歌曲榜单,怎么设计?
    
       - 每小时一个 zset,key 格式为 `rank:songs:2025080710`
       - 成员为歌曲 ID,score 为播放次数
       - 业务写入时使用 `ZINCRBY rank:songs:2025080710 1 song123`
       - 使用 `ZREVRANGE key 0 99 WITHSCORES` 获取排行榜
    
    2. 听歌业务和榜单业务如何通信?
    
       - 听歌服务负责更新计数
       - 榜单服务定时统计,可以使用:
         - Redis Stream(推送播放事件)
         - 发布订阅 PubSub(轻量级广播)
         - Kafka 等异步日志(可跨服务)
    
    3. 听歌用户量非常大,都写入一个 zset 会不会有问题?
    
       是,会导致高并发写入冲突和大 key 问题。
    
       解决方案:
    
       - 按时间窗口分 key(小时或分钟)
       - 按用户 ID 取模 hash 到多个 zset,汇总时用 `ZUNIONSTORE`
       - 使用队列异步写入,分批更新
       - 关键写操作用 Lua 脚本合并多操作
    
    ---
    
    # MongoDB
    
    端口号:27017
    
    JSON-like 存储 → 和 Go 的 struct/json 转换天然兼容;无需严格表结构,开发快。
    
    查询语言:BSON 文档查询语言--MongoDB Query Language
    
    查找语句

    mariadb
    db.users.find({id:”123″})

    由于 Json 不支持二进制,所以 Json + Binary = Bson。MySQL 的一行对应的就是 MongoDB 的一个 Bson 文档。一个表就是一个 `xxx.wt` 文件。
    
    ## 数据页
    
    直接读取大的 wt 文件很慢,所以一个 wt 内部拆成多个页。读取时按页读取即可。一般每个页是 32 KB。
    
    ### 快速找到某数据的数据页
    
    每个页有页号,每条记录有 ID。把每个 ID 的页号放在一个页里快查,这就是 B+ 树索引。
    
    ## 写时复制机制
    
    MySQL 在更新 B+ 树索引时加锁,而 MongoDB 更新索引,把要写的复制出来写,而原数据则继续对外供读。后面再找机会合并。
    
    ## Cache 和 Journal
    
    MongoDB 带有内存 Cache 机制,为防止 内存的 Cache 丢失,用 Journal Buffer 机制,把所有 写 操作同步到 内存的 Journal Buffer 里,再定时把这个 Journal Buffer 写入磁盘的 Journal 文件里。为什么不直接写入 DB?因为 Journal 文件顺序写入,MongoDB 分散,顺序写入性能高很多。所以这也是日志备份重放的一个重要原因。定时从内存 Cache 写入磁盘 MongoDB,就是 CheckPoint 机制,写入后可以安全删除写入前的 Journal 记录。
    
    ## WiredTiger
    
    以上这些机制,加上对数据库增删查改的接口,就是一个存储引擎,称为 WiredTiger。
    
    ## Server 层
    
    分为
    
    1. 连接管理,管理外部对数据库的连接
    2. 解析器,解析语句,检查语法
    3. 优化器,选择索引生成执行计划
    4. 执行器,执行计划
    
    ## 解耦合
    
    存储引擎层和 Server 层通过接口函数进行解耦,只要实现了需要的接口,就可以替换。
    
    ## 扩展性
    
    分库分表分节点。把数据分成多个节点存。要怎么找呢?加一层中间层“路由服务” `Mongos`。每个节点将自身资源信息上报给 `Config Server`,然后 Mongos 从中读取,选择适当的路由方法。
    
    ## 高可用
    
    类似 Redis 主从和集群。为了保证节点掉了数据还能读,就做主从。当主节点挂了,就把副本节点通过选举机制选成主节点。
    
    # MySQL
    
    端口:3306
    
    MySQL 是一个**多存储引擎框架**,最常用的是 **InnoDB**,其底层数据结构包括:
    
    | 层次                 | 描述                                                         |
    | -------------------- | ------------------------------------------------------------ |
    | 页(Page)           | InnoDB 的最小存储单位,默认 16KB,一个页存多个行记录         |
    | 行(Row)            | 表中一行数据,存储在页中(支持变长字段)                     |
    | 表空间(Tablespace) | 存放多个页的集合,支持表独立文件(.ibd)或共享文件(ibdata) |
    | 数据组织结构         | 使用 **B+ 树** 作为索引结构,包括主索引(聚簇索引)和二级索引(辅助索引) |
    | 缓存结构             | 包括 buffer pool(缓存页)、redo log、undo log、change buffer 等 |
    
    ## InnoDB 简介
    
    InnoDB 是 MySQL 默认且最常用的存储引擎,它具有以下特性:
    
    | 特性                        | 说明                                     |
    | --------------------------- | ---------------------------------------- |
    | 支持事务                    | 使用 redo log、undo log 实现 ACID        |
    | 支持行级锁                  | 并发性能更高                             |
    | 支持 MVCC                   | 多版本并发控制,提升读性能               |
    | 聚簇索引(Clustered Index) | 主键索引即为数据本身,数据按主键排序存储 |
    | 外键支持                    | 可定义引用完整性约束                     |
    | 使用 B+ 树 作为索引结构     | 高效查找、范围查询、排序                 |
    
    ## MyISAM 引擎对比
    
    | 项目       | InnoDB                          | MyISAM                           |
    | ---------- | ------------------------------- | -------------------------------- |
    | 事务支持   | 支持 ACID,支持事务与回滚       | 不支持事务                       |
    | 锁粒度     | 行级锁,支持并发                | 表级锁,写时阻塞其他操作         |
    | 外键支持   | 支持外键约束                    | 不支持                           |
    | 数据缓存   | 缓存数据和索引                  | 只缓存索引,不缓存数据           |
    | 主索引结构 | 聚簇索引                        | 非聚簇索引                       |
    | 崩溃恢复   | 支持崩溃恢复(redo、undo 日志) | 崩溃后需手动修复                 |
    | 适用场景   | 更新频繁、事务要求高的业务      | 查询为主、数据只读或更新少的业务 |
    
    ## 聚簇索引和非聚簇索引
    
    - 聚簇索引:数据行存储在主键索引的叶子节点上,索引本身就是数据。例如 InnoDB 主键索引
    - 非聚簇索引:索引的叶子节点存储的是指向数据行的地址或主键值,需要回表查询
    
    总结:
    
    - 聚簇索引读取主键快,但只能有一个
    - 非聚簇索引可建多个,查询非主键字段时需要回表
    
    ## 缓存的机制
    
    MySQL 缓存包括:
    
    1. 查询缓存(早期版本,8.0 已移除)
       - 相同 SQL 和结果可命中缓存
       - 对动态数据支持差,命中率低
    2. InnoDB Buffer Pool(主流缓存机制)
       - 缓存数据页和索引页,减少磁盘 IO
       - 默认占用内存 75%,支持冷热页淘汰策略
    3. File System Cache(操作系统层)
       - OS 页缓存也会提升数据访问效率
    4. 内存临时表(如 GROUP BY 或 ORDER BY 中间结果)
       - 超过内存限制后会转为磁盘临时表
    
    ## 事务的几种机制以及 MVCC 实现原理
    
    InnoDB 实现事务的机制包括:
    
    1. redo log(重做日志):保证已提交事务的持久性
    2. undo log(回滚日志):支持事务回滚、MVCC 读快照
    3. binlog(二进制日志):主从同步、恢复数据
    4. MVCC(多版本并发控制):实现快照读,减少加锁冲突
    
    MVCC 实现原理:
    
    - 每行记录附带两个隐藏字段:创建版本号和删除版本号
    - 读取时比较当前事务的版本号,只读取可见版本(读未提交、读提交、可重复读、串行化)
    
    ## 幻读是怎么解决的
    
    幻读指同一事务中执行相同语句两次,第二次多了“幻影”记录(例如 insert)
    
    - 可重复读(默认隔离级别)下,通过 MVCC 避免幻读
    - 若需要防止 insert 造成的幻读,可使用 next-key lock(行锁 + 间隙锁)
    
    总结:InnoDB 在可重复读隔离级别下,通过加锁和 MVCC 共同解决幻读
    
    ## 慢 SQL 排查与优化思路
    
    1. 使用工具定位慢 SQL
       - 开启 slow_query_log,查看日志
       - 使用 `SHOW PROCESSLIST` 查看当前执行的 SQL
       - 使用 `EXPLAIN` 分析执行计划
    2. 优化方法
       - 确认是否走了索引(extra 字段中是否有 Using index)
       - 避免全表扫描、filesort、临时表
       - 确保 WHERE 条件能命中合适索引
       - 减少函数调用、类型转换、模糊查询
       - 分库分表、缓存热点数据
    3. 索引设计
       - 用覆盖索引减少回表
       - 联合索引设计考虑最左匹配原则
    4. 结构优化
       - 表太大时拆表或分区
       - 热字段做单独表做冗余
    
    ## 超大表建表分表分页查询优化方法
    
    问题:大分页如 offset 1000000,会造成极大的扫描负担
    
    优化方案:
    
    1. ID 设计
       - 不建议用 MySQL 自增主键,分布式环境难以协调
       - 推荐使用分布式 ID(如雪花算法、UUIDV4/Base62 编码等)
    2. 避免 offset
       - 使用“延续性分页”或“游标分页”
       - 例如使用 `WHERE id > last_id LIMIT N` 替代 offset
    3. 建索引
       - 对分页字段建立合适索引(如时间戳、id)
       - 避免冗余索引,只为高频查询字段建索引
       - 联合索引遵循最左匹配原则
    4. 覆盖索引
       - 优先只查索引中的字段,避免回表
    5. 延迟关联
       - 第一步只查主键列表,第二步再根据主键查完整数据
       - 分两步执行,减轻查询负担
    6. 缓存热点分页
       - 对前几页缓存(如第一页),避免频繁访问数据库
    
    总结:越往后的分页效率越低,应尽量用主键位置替代 offset
    
    ## 主从
    
    ### 主从同步机制如何保证实时性与完整性
    
    1. **MySQL 主从同步流程**
       - 主库将写操作记录到 binlog(二进制日志)
       - 从库 I/O 线程拉取主库 binlog,写入 relay log
       - 从库 SQL 线程读取 relay log 并执行,重放数据变化
    2. **如何确认同步完整且实时**
       - **半同步复制(semi-sync replication)**:
         - 主库写完 binlog 后,必须等待至少一个从库确认已收到,才返回客户端成功
         - 提高了数据可靠性(不是强一致性)
       - **GTID 模式(全局事务 ID)**:
         - 可唯一标识每个事务,防止重复或丢失执行
         - 从库可基于 GTID 精确知道同步到哪个事务点
       - **延迟监控**:
         - 查询 `Seconds_Behind_Master` 判断从库滞后
         - 使用 `pt-heartbeat` 工具精确监测复制延迟
       - **主从一致性校验**:
         - `pt-table-checksum` 检查主从数据是否一致
         - 定期做冷备 + 校验
    3. **性能建议**
       - 开启并行复制(多线程复制)
       - 主库启用 binlog group commit
       - 合理设置 innodb_flush_log_at_trx_commit,减少刷盘瓶颈
    
    ## 什么是最左匹配原则?
    
    联合索引(如 `INDEX(col1, col2, col3)`)在使用时,只有**从最左列开始连续匹配**时,索引才会生效。

    sql
    — 联合索引 (a, b, c)
    WHERE a = 1 — ✅ 使用索引
    WHERE a = 1 AND b=2 — ✅ 使用索引
    WHERE b = 2 — ❌ 不使用索引(跳过了 a)
    WHERE a = 1 AND c=3 — ✅ 部分使用 (a),c 不参与索引
    “`

    为什么会这样?

    • B+ 树索引是按从左到右依次排序的
    • 索引条目按 (a, b, c) 排序,一旦跳过 a,就没法快速定位分支
    • 所以必须“从左往右连续使用”

    联合索引用于 ORDER BY 时,也要遵守最左匹配原则吗?

    是的:排序使用联合索引,也需要最左匹配原则。

    原因:

    排序是否能用索引,依赖于“查询的 WHERE 子句 + ORDER BY 是否与索引方向一致”。

    为什么 MySQL 不用单线程

    • 并发需求
      MySQL 需要同时处理多个连接和 SQL 语句,单线程会导致阻塞等待。
    • 磁盘 I/O 等待
      MySQL 有大量磁盘读写操作,如果是单线程,一个查询阻塞会影响所有请求。
    • 事务隔离与并行执行
      多线程可利用多核 CPU 提高吞吐,事务间可以并发执行,减少响应时间。
    • 网络延迟影响
      单线程容易被慢连接拖慢,影响整体处理速度。

    索引的实现

    MySQL 本身只是一个数据库框架,索引的具体实现由存储引擎决定

    • InnoDB(默认):使用 B+ 树 作为索引结构
    • MyISAM:也用 B+ 树 作为索引结构,但组织方式和 InnoDB 不同
    • Memory 引擎:默认用 哈希索引

    B+ 树为什么适合做索引

    • 有序性:范围查找快(支持最左匹配、区间查询)
    • 磁盘友好:节点存多个 key,减少 IO 次数
    • 链表结构:叶子节点之间有指针,方便范围遍历

    与 B 树对比

    特性B 树B+ 树
    数据存储位置内部节点 + 叶子节点仅叶子节点
    叶子节点连接无连接叶子节点通过链表连接
    查询效率中等更高(尤其是范围查询)
    内存占用较大较小
    插入/删除性能较好稍差(因移动数据到叶子节点)
    支持范围查询不如 B+ 树高效(叶子节点链表)

    为什么 B+ 树比 B 树更适合做数据库索引?

    B+ 树的 所有数据都在叶子节点,并且 叶子节点通过链表连接,这使得 顺序查询和范围查询 比 B 树更高效,尤其是大规模的数据量中,能显著提高数据库性能。

    B 树因为内部节点存储数据,增删时不仅涉及叶子节点,还需要修改内部节点,这使得增删操作较为复杂,性能也稍低。B+ 树因为内部节点只存索引,增删时只影响叶子节点,操作简单高效,性能较好。

    B+ 树的优势是什么?

    主要优势在于它能高效处理范围查询顺序扫描,并且由于只在叶子节点存储数据,内存占用更小

    哈希索引(Memory 引擎默认)

    • 基于哈希表,O(1) 查找
    • 只适合等值查询(=、IN),不支持范围查找、排序
    • InnoDB 的自适应哈希索引(AHI)也是类似的原理,但由引擎自动维护

    字符集

    MySQL 字符集

    MySQL 字符集(Character Set)是一套符号和编码的规则。它告诉 MySQL 如何存储和处理文本数据。MySQL 中有几个关键的字符集设置:

    • 数据库(Database)字符集:这是创建数据库时指定的默认字符集。
    • 表(Table)字符集:这是创建表时指定的默认字符集。如果未指定,它会继承数据库的字符集。
    • 列(Column)字符集:这是为表的每一列(例如 VARCHARTEXT)指定的字符集。如果未指定,它会继承表的字符集。
    • 连接(Connection)字符集:这是客户端与 MySQL 服务器通信时使用的字符集。

    UTF-8 和 UTF8mb4 的区别

    这是 MySQL 中一个非常常见的问题。简而言之,utf8utf8mb4 都是 UTF-8 编码,但它们支持的字符范围不同:

    • utf8 (MySQL 的):这个字符集是 MySQL 在早期实现的 UTF-8 版本。它最多支持 3 个字节的 UTF-8 编码。这意味着它能存储大多数常用语言的字符,比如中文、英文等。然而,它无法存储一些需要 4 个字节的字符,比如:
    • 某些生僻字或特殊符号。
    • Emoji 表情符号。
    • utf8mb4:这个字符集是 MySQL 对完整 UTF-8 编码的实现。mb4 的意思是 “most bytes 4″,它最多支持 4 个字节的 UTF-8 编码。
    • 它能存储所有 3 个字节的字符。
    • 它能存储 4 个字节的字符,因此完全支持 Emoji 表情和所有 Unicode 字符。

    总结:为了避免潜在的字符集问题,强烈建议在所有新项目中默认使用 utf8mb4。它提供了更好的兼容性和未来扩展性,而性能开销几乎可以忽略不计。

    表字符集和字段字符集的关系

    表字符集是该表的默认字符集。当你为表指定了字符集后,任何没有单独指定字符集的新字段都会自动继承这个表的字符集。

    字段字符集是针对具体字段设置的字符集。你可以为表中的某个字段指定一个不同于表字符集的字符集。

    关系:字段字符集是表的字符集的一个子集,但它可以被独立地配置。表字符集提供了一个默认值,而字段字符集提供了更细粒度的控制。

    字段字符集和表的字符集不一样会怎么样?

    这完全是允许的,而且在某些情况下是有用的。

    当你为某个字段指定了不同于表字符集的字符集时,MySQL 会只为这个字段使用你指定的字符集来存储和排序数据。

    例子:假设你有一张名为 products 的表,其默认字符集是 latin1。但你有一个 description 字段需要存储中文,所以你为这个字段单独指定了 utf8mb4 字符集。

    在这个例子中,name 字段只能存储 latin1 编码的字符,而 description 字段则可以存储中文和 Emoji 表情。

    连接字符集和库字符集不一致会发生什么?

    这是一个非常常见的问题,也是导致乱码的罪魁祸首。

    连接字符集指的是客户端和服务器之间进行数据传输时使用的编码。当客户端发送 SQL 语句和数据给服务器时,它会使用连接字符集进行编码。当服务器返回查询结果给客户端时,也会使用连接字符集进行编码。

    数据库字符集是数据在存储时使用的编码。

    如果这两种字符集不一致,就会导致乱码。这个过程可以用一个简单的模型来理解:

    1. 客户端发送一条 SQL 语句,比如 INSERT INTO table (col) VALUES ('你好');
    2. 客户端会用它的连接字符集(比如 latin1)将 '你好' 这两个字进行编码,然后发送给服务器。
    3. 服务器接收到数据后,它会根据客户端声明的连接字符集(latin1)去解码。
    4. 然后,服务器会试图用表的字符集(比如 utf8mb4)对解码后的字符重新编码,并存储到数据库中。

    如果连接字符集和数据库字符集不匹配,服务器可能无法正确地将数据从连接字符集转换到数据库字符集,从而导致存储的数据变成乱码。

    如何避免?

    最简单的办法是在建立连接后,立即通过以下命令设置连接字符集为你的数据库字符集(通常是 utf8mb4):SET NAMES utf8mb4;

    这条命令会同时设置 character_set_clientcharacter_set_connectioncharacter_set_results 三个变量,确保客户端和服务器之间的所有通信都使用 utf8mb4 字符集,从而避免乱码问题。

    MongoDB 对比 MySQL

    对比维度MySQL(关系型数据库)MongoDB(文档型数据库)
    数据模型表(table)+ 行(row)+ 列(column)集合(collection)+ 文档(document)
    数据结构固定字段,强类型灵活字段,BSON 文档,可嵌套
    查询语言SQLJSON 风格的查询语法
    事务支持原生支持 ACID 事务(InnoDB 引擎)4.0+ 支持事务,但性能不如 MySQL
    索引机制支持 B+ 树索引、多列索引支持单字段、多字段、复合索引,查询灵活
    扩展性主从复制、分库分表较复杂原生支持分片(Sharding)和副本集
    一致性模型强一致性默认最终一致性,可配置强一致性
    性能特点结构化、高并发读写、聚合强写入快、扩展性好、适合灵活查询和大数据量写入
    适用场景银行、电商、ERP 等结构固定、强一致业务CMS、日志系统、IoT、社交应用等灵活结构场景
    模型适配性面向关系建模,适合规范化数据接近对象模型,适合非结构化、变化快的业务
    部署难度架构成熟但分布式扩展较重天然支持分布式,易于横向扩展

    Kubernetes(简称 K8s

    是什么

    K8s 是一个容器编排平台,用来管理容器化应用的部署、扩缩容、负载均衡和更新。它的核心是通过 Pod、Service、Deployment 等资源,结合 API Server、Scheduler、kubelet 等组件,实现自动化运维和弹性伸缩。

    Kubernetes(简称 K8s)是一个开源的容器编排平台,用于自动化部署、扩缩容、负载均衡以及管理容器化应用。它最初由 Google 开发,后来捐赠给 CNCF(Cloud Native Computing Foundation)维护。

    • 容器编排:管理成百上千个容器的部署、更新和运行状态。
    • 自动化运维:自动扩容/缩容、故障容器自动替换。
    • 服务发现与负载均衡:容器的 IP 可能变化,K8s 自动提供稳定的访问入口。
    • 资源调度:根据 CPU、内存等资源限制,把容器分配到合适的节点。
    • 滚动更新与回滚:平滑更新应用,出问题可一键回滚。

    工作流程(简化版)

    1. 编写配置文件(YAML/JSON),定义应用部署方式(副本数、镜像、端口等)。
    2. kubectl apply 提交到 API Server
    3. Scheduler 选择合适的节点运行 Pod。
    4. kubelet 在节点上启动容器,并监控健康状态。
    5. Service 暴露服务,外部或集群内部可访问。
    6. 出现故障时,K8s 会自动重建 Pod,保证服务可用。

    核心组件

    组件作用
    PodK8s 中最小的部署单元,封装一个或多个容器
    Node容器运行的主机(物理机或虚拟机)
    Deployment定义应用的期望状态(副本数、镜像版本等),K8s 会自动维持它
    Service稳定的访问入口,屏蔽 Pod IP 变化
    Ingress提供 HTTP/HTTPS 访问规则(如域名路由)
    ConfigMap / Secret配置管理和敏感信息管理
    Namespace资源隔离(如测试环境、生产环境分开)
    kube-apiserver控制平面的核心,提供 API
    etcd分布式键值存储,用于存储集群状态
    kube-scheduler负责调度 Pod 到合适的 Node
    kubelet节点上运行的代理,负责启动容器、汇报状态

    问题

    背景

    • a 服务有 10 个 Pod(IP1、IP2、…)
    • b 服务有 10 个 Pod(IP21、IP22、…)
    • Pod IP 是 临时的(Pod 删除重建后会变)
    • a 服务要访问 b 服务中某一个 Pod

    K8s 的访问原则

    在 K8s 里,Pod 之间的直接 IP 调用是不推荐的,因为:

    • Pod IP 会变,不稳定
    • 无法做负载均衡
    • 没有健康检查

    所以 K8s 引入了 Service(ClusterIP / NodePort / LoadBalancer) 来作为稳定入口

    访问流程(默认情况:访问整个 b 服务,而不是指定某个 Pod)

    1. a 服务访问 b 服务时,用的是 Service 名字(比如 http://b-service:8080)。
    2. Pod 内的 kube-dns(CoreDNS) 会把 b-service 解析为 ClusterIP(一个虚拟 IP)。
    3. ClusterIP 不是真实 Pod 的 IP,而是 kube-proxy 维护的虚拟地址。
    4. kube-proxy 在每个 Node 上用 iptables / IPVS 建立规则,把 ClusterIP 请求转发到 b 服务的 10 个 Pod 之一
    5. 转发过程有负载均衡策略(轮询、随机、IP 哈希)。
    6. 最终,a 服务访问的实际上是 b 服务的某个 Pod 的 IP,但这个 IP 是由 kube-proxy 动态选择的。

    如果要访问 b 服务的某个 特定 Pod

    有几种方式:

    • 直接用 Pod IP(需要 a 服务和 b 服务在同一网络,且 Pod 没被删除)
      不推荐,因为 Pod 重建后 IP 会变。
    • Headless ServiceClusterIP: None
    • DNS 不解析成一个 ClusterIP,而是直接解析成所有 Pod 的 IP 列表。
    • a 服务解析到多个 IP 后,可以自己选择某个 Pod。
    • Pod 直接暴露(HostPort / NodePort)
    • 一般用于调试或特定场景,不常用。
    • StatefulSet + 固定 Pod 名称
    • 每个 Pod 都有稳定的 DNS,比如 b-0.b-serviceb-1.b-service,这样可以精确访问某个 Pod。

    简答版

    在 K8s 中,a 服务访问 b 服务通常是通过 b 的 Service 名称解析成 ClusterIP,然后由 kube-proxy 根据负载均衡策略把流量转发到某个 Pod。如果需要访问 b 的特定 Pod,可以使用 Headless Service 或 StatefulSet 来获取固定的 Pod DNS 名称。

    gRPC

    gRPC 是 Google 开源的一种 高性能、通用的 RPC(Remote Procedure Call)框架,基于 HTTP/2 协议Protocol Buffers(protobuf)序列化
    它的主要特点:

    • 跨语言:支持 Go、Java、Python、C++、Node.js 等十几种语言。
    • 高性能:HTTP/2 支持多路复用、头部压缩、二进制传输,提升效率。
    • IDL(接口描述语言):用 .proto 文件定义服务和消息结构,自动生成多语言客户端和服务端代码。
    • 多种通信模式
    • 一元 RPC(普通请求-响应)
    • 服务端流式 RPC(服务端连续推送数据)
    • 客户端流式 RPC(客户端连续上传数据)
    • 双向流式 RPC(两边同时流式通信)
    1. gRPC 和 REST 的区别是什么?
    • 协议层:gRPC 基于 HTTP/2 + protobuf,REST 一般基于 HTTP/1.1 + JSON。
    • 性能:gRPC 二进制更小、更快;REST 文本可读性更强但开销大。
    • 接口约定:gRPC 通过 .proto 强类型定义;REST 主要靠文档。
    • 通信模式:REST 只能单次请求响应;gRPC 支持双向流。
    1. gRPC 为什么要用 HTTP/2?
    • HTTP/2 支持 多路复用,避免队头阻塞。
    • 支持 双向流,天然适合 gRPC 的流式调用。
    • 支持 头部压缩,减少带宽开销。
    1. gRPC 为什么使用 protobuf 而不是 JSON?
    • protobuf 是二进制序列化,体积小、解析快;
    • JSON 可读性强但开销大,适合调试,不适合高性能场景。
    1. gRPC 的四种通信模式?
    • Unary RPC:普通请求-响应
    • Server streaming RPC:客户端一次请求,服务端多次响应
    • Client streaming RPC:客户端多次请求,服务端一次响应
    • Bidirectional streaming RPC:客户端和服务端都可以持续发送数据
    1. gRPC 的负载均衡是怎么做的?
    • 客户端内置 轮询负载均衡
    • 结合 服务发现(如 etcd、Consul、k8s service)实现动态负载均衡。
    1. gRPC 的拦截器(Interceptor)是什么?
    • 类似中间件,可以在 RPC 调用前后插入逻辑。
    • 常见用途:日志、认证、限流、监控。
    1. gRPC 如何保证安全性?
    • 基于 TLS(SSL)保证加密通信;
    • 支持认证(如 token 校验、OAuth);
    • 也可以结合 API Gateway 做额外安全控制。
    1. gRPC 的应用场景?
    • 微服务之间的高性能通信(内部 RPC)
    • 流式数据传输(日志、监控数据上报)
    • 移动端与服务端之间的高效通信

    Protocol Buffers(Protobuf)

    Protocol Buffers 是 Google 开发的一种 语言无关、平台无关、可扩展的序列化协议,简称 Protobuf
    主要作用:

    • 序列化:把结构化数据(对象)转成二进制,方便网络传输或持久化存储;
    • 反序列化:从二进制还原出原始对象。

    核心点:

    • 通过 .proto 文件定义数据结构和服务接口;
    • 编译 .proto 文件,自动生成各种语言的类/结构体和序列化代码;
    • 支持前向和后向兼容(字段可新增、删除)。
    1. Protobuf 和 JSON、XML 的区别?
    • 体积:Protobuf 是二进制,数据体积比 JSON、XML 小很多;
    • 性能:Protobuf 序列化/反序列化速度比 JSON 快;
    • 可读性:JSON/XML 可读性好,Protobuf 可读性差(需依赖 .proto)。
    • 扩展性:Protobuf 有严格的字段编号和类型定义,支持版本兼容;JSON 没有强约束。
    1. Protobuf 的工作流程?
    2. 定义 .proto 文件,描述数据结构和服务接口;
    3. protoc 编译器生成对应语言的代码;
    4. 在应用程序中使用生成的类进行序列化和反序列化。
    5. Protobuf 为什么要用字段编号(tag)?
    • 每个字段都有一个唯一的 tag number(如 string name = 1;),二进制格式里只存 tag,不存字段名;
    • 这样能减少体积,同时确保版本兼容(新增字段不会影响老版本)。
    1. Protobuf 如何保证前向兼容和后向兼容?
    • 新增字段:老版本会忽略未识别的字段,不会报错;
    • 删除字段:不要复用旧的 tag number,可以标记为 reserved
    • 修改字段类型:尽量保持兼容,比如 int32 -> int64 是可以的,但 int32 -> string 会有问题。
    1. Protobuf 的数据类型有哪些?
    • 标量类型int32, int64, uint32, bool, string, bytes
    • 复合类型message(嵌套结构)、enum(枚举)
    • 集合repeated 表示数组/列表
    • 映射map<key_type, value_type>
    1. Protobuf v2 和 v3 的区别?
    • v2:字段必须加 optionalrequired
    • v3:默认字段是 optional,取消了 required(避免兼容性问题);
    • v3:增加了 map 类型,更方便表示键值对。
    1. Protobuf 的使用场景?
    • gRPC 通信(默认序列化方式就是 Protobuf);
    • 分布式系统内部通信;
    • 存储配置文件、日志数据等高性能场景;
    • 移动端和服务端之间的高效通信。
    1. 为什么 gRPC 选择 Protobuf 而不是 JSON?
    • 二进制传输,性能更高、体积更小
    • .proto 提供强类型约束,避免接口文档和代码不一致;
    • 跨语言自动生成代码,减少手写代码。

    etcd

    etcd 是一个 分布式键值存储系统,由 CoreOS 开发,用 Go 语言实现。
    基于 Raft 共识算法 实现高可用,保证数据在集群中的一致性。
    etcd 最主要的用途是:

    • 服务发现:保存服务注册信息,让服务可以互相发现;
    • 配置中心:存储分布式系统的配置,支持动态更新;
    • 分布式协调:类似 ZooKeeper,提供锁、Leader 选举等能力;
    • Kubernetes 的底层依赖:K8s 所有的资源对象(Pod、Service、ConfigMap 等)都存储在 etcd 中。
    1. etcd 的核心特点是什么?
    • 强一致性:基于 Raft 算法,保证数据在多副本下的一致性;
    • 高可用性:支持集群部署,少数节点宕机仍可用;
    • 高性能:顺序写日志,支持高吞吐;
    • Watch 机制:客户端可以实时订阅 key 变化;
    • TTL / 租约机制:支持临时 key,用于服务注册和心跳检测。
    1. etcd 和 ZooKeeper 的区别?
    • 实现语言:etcd 用 Go 写的,ZooKeeper 用 Java 写的;
    • 协议:etcd 用 Raft,ZooKeeper 用 ZAB;
    • API 友好度:etcd 提供 HTTP/gRPC API,更易用;ZooKeeper 使用 Java 客户端;
    • 性能:etcd 在高并发下通常优于 ZooKeeper;
    • 生态:etcd 是 Kubernetes 的核心组件,云原生领域更常用。
    1. etcd 是如何保证一致性的?
    • 使用 Raft 算法:所有写请求先由 Leader 节点处理,然后复制到 Follower,超过半数确认后提交;
    • 线性一致性读写:保证客户端看到的数据是最新的。
    1. etcd 的 Watch 机制是什么?
    • 客户端可以 Watch 某个 key 或前缀,当 key 变化时,etcd 主动推送通知;
    • 用于服务发现(监听服务上下线)、动态配置更新。
    1. etcd 的 Lease / TTL 有什么用?
    • Lease(租约):key 可以绑定一个租约,租约过期时 key 自动删除;
      用途:
    • 服务注册(自动过期代表服务下线);
    • 分布式锁(锁超时自动释放);
    • 心跳检测。
    1. etcd 在 Kubernetes 中的作用?
    • Kubernetes 所有数据(Pod、Service、ConfigMap、Deployment 等)都存放在 etcd;
    • API Server 负责与 etcd 交互,保证 Kubernetes 状态持久化;
    • etcd 相当于 K8s 的数据库。
    1. etcd 集群节点数为什么建议是奇数?
    • Raft 需要 超过半数节点 同意才能提交日志;
    • 偶数节点和奇数节点在容错能力上是一样的,但奇数更节省资源。
      例如:
    • 3 节点集群,允许 1 个宕机;
    • 4 节点集群,也只允许 1 个宕机。
    1. etcd 的使用场景有哪些?
    • Kubernetes(最典型应用);
    • 微服务注册与发现(替代 Consul、ZooKeeper);
    • 分布式配置中心;
    • 分布式锁、Leader 选举。
    1. etcd 的性能优化方法?
    • 合理设置集群节点数(3/5/7 个);
    • 定期压缩历史版本(避免存储过大);
    • 使用 SSD 磁盘,提升 IO 性能;
    • 调整 gRPC KeepAlive,优化客户端连接。
  • Golang 面经

    最近在找工作,emmm 真是行业深秋了,活人微死了已经。发个网上大佬做的面经,我大概改了一小半,表述甚至答案都改了。

  • 世界,你好!

    这是什么?

    这是半糖(站主)的博客发布网站,会尽量保证内容的正确性。

    有什么内容?

    学得比较杂乱,计算机相关、或者书法一类的东西,偶尔可能还有福利。?

    可以注册吗?

    不可以哦,也不许评论,也不许盒我。

    如果内容有误请发送邮件至 [email protected] ,感谢指正。有些文章缺失图片是因为我懒得搞图床了,我会找一个好的方法来共享 PDF 资料。