跳转至

奇安信 AI 全栈开发 — 一面

9.10 面试,耗时一小时,体验不错,面试官非常友好 以下内容根据回忆记录,未录音


1. 自我介绍

常规开场,简要介绍个人背景、技术栈和项目经历。

回答框架

  • Who:姓名、学历、工作年限
  • What:核心技术栈(如 Go + Redis + MySQL + AI)
  • Highlight:1-2 个最有亮点的项目
  • Why:为什么投这个岗位

2. 两段实习哪一段最具有挑战性 & 若干实习追问

面试官希望了解实习经历中的深度思考和技术挑战,而非简单罗列做了什么。

STAR 法则回答框架

  • Situation:项目的背景和业务上下文
  • Task:你负责的具体任务和目标
  • Action:你采取的技术方案和关键决策
  • Result:量化的成果

回答要点:

  • 选择技术难度最高或业务影响最大的那段实习
  • 重点描述遇到的技术挑战和解决方案
  • 面试官会根据回答进行深入追问,需要对项目细节了然于胸

3. 滑动窗口分布式限流 & 数据结构 & 过期时间设置

3.1 滑动窗口限流原理

滑动窗口限流是对固定窗口的改进,解决了固定窗口在窗口边界处可能出现的突发流量问题。

graph LR
    subgraph 固定窗口
        W1[窗口1<br/>00:00-01:00<br/>计数=100] --> W2[窗口2<br/>01:00-02:00<br/>计数=0]
    end
    subgraph 滑动窗口
        S1[00:30-01:30<br/>统计最近60s]
        S2[00:45-01:45<br/>统计最近60s]
    end

核心思想:统计当前时刻往前推 N 秒内的请求数量,窗口随时间持续滑动。

3.2 Redis 实现方案 — Sorted Set

最常用的实现方式是使用 Redis 的 ZSET(有序集合):

字段 说明
Member 请求唯一标识(UUID 或雪花 ID)
Score 请求的时间戳(毫秒级)

核心逻辑:

// 滑动窗口限流 — Lua 脚本保证原子性
local key = KEYS[1]                -- 限流 key
local now = tonumber(ARGV[1])      -- 当前时间戳(ms)
local window = tonumber(ARGV[2])   -- 窗口大小(ms)
local limit = tonumber(ARGV[3])    -- 窗口内最大请求数

-- 1. 移除窗口外的过期成员
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)

-- 2. 统计窗口内的请求数
local count = redis.call('ZCARD', key)

-- 3. 判断是否限流
if count < limit then
    redis.call('ZADD', key, now, ARGV[4])  -- 添加当前请求
    redis.call('PEXPIRE', key, window)      -- 设置过期时间
    return 1  -- 允许
else
    return 0  -- 拒绝
end

3.3 数据结构选择

方案 数据结构 精确度 内存开销 复杂度
ZSET 有序集合 精确 较高(每请求一个 member) O(log N)
Bitmap 位图 时间片级别 极低 O(1)
滑动窗口计数器 Hash 近似 低 O(1)

3.4 过期时间设置

关键细节

ZSET 本身不会自动删除过期 member,需要配合 PEXPIRE 设置 key 的 TTL。

  • TTL 设置为窗口大小:每次写入时用 PEXPIRE key window_ms 刷新 key 的过期时间
  • 惰性清理:每次请求时先执行 ZREMRANGEBYSCORE 清理窗口外的数据
  • 避免内存泄漏:如果 key 长期无访问,TTL 到期后 Redis 会自动删除整个 key

4. MULTI 能否保证原子性 & MULTI/EXEC 和 Lua 有什么区别

4.1 MULTI/EXEC 的原子性

MULTI/EXEC 提供的是**部分原子性**(也称为"事务"),但与关系型数据库的事务有本质区别:

特性 MULTI/EXEC 数据库事务
命令入队 命令在 EXEC 之前只是入队,不执行 语句逐条执行
执行方式 EXEC 时一次性按序执行 逐条执行,可回滚
回滚 不支持回滚,某条命令失败不影响其他命令 支持回滚
隔离性 单线程保证不会被其他命令插入 通过锁和 MVCC 保证
sequenceDiagram
    participant C as 客户端
    participant R as Redis
    C->>R: MULTI
    R-->>C: OK
    C->>R: SET key1 value1
    R-->>C: QUEUED
    C->>R: SET key2 value2
    R-->>C: QUEUED
    C->>R: EXEC
    R-->>C: [OK, OK]

MULTI 不支持回滚

如果 EXEC 中某条命令执行失败(如对 string 执行 LPUSH),Redis **不会回滚**其他已执行的命令。这是 Redis 的设计哲学:保持简单和高性能。

4.2 MULTI/EXEC vs Lua 脚本

维度 MULTI/EXEC Lua 脚本
原子性 命令按序执行,但不支持条件逻辑 完全原子,支持条件分支
条件逻辑 不支持(无法根据前一条结果决定后续操作) 支持 if/else/循环
网络开销 多条命令 + EXEC,多次网络往返(入队阶段) 一次性发送整个脚本
错误处理 某条失败继续执行后续命令 脚本内可处理错误逻辑
可复用 不支持 可用 EVALSHA 缓存脚本,减少传输
阻塞风险 不会长时间阻塞 复杂脚本可能阻塞 Redis

选择建议:

  • 简单批量操作(无条件逻辑)→ MULTI/EXEC
  • 需要条件判断(如限流:先查计数再决定是否放行)→ Lua 脚本
  • 高性能要求 → Lua 脚本(一次网络往返 + 原子执行)

5. Redis 单线程为什么快 & Redis 不可用如何处理

5.1 Redis 单线程为什么快

因素 说明
纯内存操作 数据存储在内存中,读写速度是磁盘的 10 万倍
单线程避免上下文切换 不存在多线程的锁竞争和上下文切换开销
I/O 多路复用 使用 epoll/kqueue 实现单线程同时处理大量连接
高效数据结构 SDS、ZIPLIST、SKIPLIST 等针对场景优化的数据结构
简单通信协议 RESP 协议解析简单高效
graph TD
    subgraph Redis 单线程模型
        EP[epoll 多路复用] -->|就绪事件| EL[事件循环 Event Loop]
        EL -->|读事件| RH[命令解析与执行]
        EL -->|写事件| WH[响应发送]
        RH -->|写回客户端| WH
    end

Redis 6.0+ 的多线程

Redis 6.0 引入了 I/O 多线程,但仅用于网络 I/O(读写 socket),命令执行仍然是单线程。这样既利用多核加速网络处理,又保持了命令执行的简单性和原子性。

5.2 Redis 不可用如何处理

graph TD
    A[Redis 不可用] --> B{部署模式?}
    B -->|单机| C[降级方案]
    B -->|主从| D[哨兵自动故障转移]
    B -->|集群| E[Cluster 自动故障转移]
    C --> C1[本地缓存兜底]
    C --> C2[直接访问数据库 + 限流]
    C --> C3[返回默认值/缓存空值]
    D --> D1[Sentinel 检测主节点下线]
    D --> D2[选举新主节点]
    D --> D3[客户端自动切换]
    E --> E1[节点间 Gossip 检测]
    E --> E2[故障节点的从节点晋升]

降级策略:

策略 适用场景 实现方式
本地缓存兜底 读多写少 使用 sync.Map 或 LRU Cache 缓存热点数据
直接查库 + 限流 数据一致性要求高 限制穿透到数据库的 QPS,防止雪崩
返回默认值 容忍短暂不一致 返回预设的默认值或静态数据
熔断降级 非核心功能 使用 Hystrix/sentinel 熔断,返回降级结果

6. 多级缓存相关问题

6.1 多级缓存架构

graph LR
    R[请求] --> L1[L1 本地缓存<br/>进程内 / sync.Map]
    L1 -->|未命中| L2[L2 分布式缓存<br/>Redis]
    L2 -->|未命中| DB[数据库 MySQL]
    DB -->|回写| L2
    L2 -->|回写| L1

6.2 各级缓存对比

层级 介质 延迟 容量 一致性 适用数据
L1 进程内存(Go: sync.Map/bigcache) 纳秒级 小(受 JVM/Go 内存限制) 难以保证 极热点、变更少
L2 Redis 毫秒级 大(集群可扩展) 较好保证 通用热点数据
L3 MySQL / 磁盘 十毫秒级 海量 强一致 全量数据

6.3 常见问题

缓存一致性问题:

  • 写时失效:更新数据库时同时删除缓存(Cache Aside 模式)
  • 延迟双删:先删缓存 → 更新数据库 → 延迟 N 毫秒 → 再删缓存
  • 消息队列异步:数据库变更事件发送到 MQ,消费者更新缓存

L1 缓存的特殊挑战:

  • 多实例间 L1 缓存不一致 → 需要广播失效(如 Redis Pub/Sub)
  • L1 缓存容量有限 → 需要精心设计淘汰策略(LRU / LFU / TTL)

7. 认证链路 & SAML 是什么 & OAuth2.0 原理

7.1 认证(Authentication)vs 授权(Authorization)

概念 含义 典型问题
认证(AuthN) 你是谁 用户名密码、MFA、SSO
授权(AuthZ) 你能做什么 RBAC、ACL、OAuth Scope

7.2 SAML(Security Assertion Markup Language)

SAML 是一种基于 XML 的**联邦身份认证**标准,主要用于企业级 SSO(单点登录)。

sequenceDiagram
    participant U as 用户/浏览器
    participant SP as 服务提供方(SP)
    participant IdP as 身份提供方(IdP)
    U->>SP: 访问受保护资源
    SP->>U: 302 重定向到 IdP
    U->>IdP: 携带 SAMLRequest
    IdP->>IdP: 认证用户(登录页)
    IdP->>U: 返回 SAML Response(XML 断言)
    U->>SP: 提交 SAML Response
    SP->>SP: 验证签名、解析断言
    SP->>U: 授权访问

SAML 核心组件:

组件 说明
IdP(身份提供方) 负责认证用户,如 Okta、ADFS
SP(服务提供方) 需要认证的应用
Assertion(断言) 用户身份信息的 XML 声明
XML Signature 保证断言的完整性和不可否认性

7.3 OAuth 2.0 原理

OAuth 2.0 是一种**授权框架**,核心目标是让第三方应用在**不接触用户密码**的情况下,获取对用户资源的有限访问权限。

四种授权模式:

模式 适用场景 安全性
授权码模式 有后端的 Web 应用 最高(推荐)
隐式模式 纯前端 SPA 较低(已不推荐)
密码模式 高度信任的第一方应用 中(不推荐第三方使用)
客户端凭证 服务间通信 高(无用户参与)

授权码模式完整流程:

sequenceDiagram
    participant U as 用户
    participant C as 客户端(第三方应用)
    participant A as 授权服务器
    participant R as 资源服务器
    U->>C: 点击"授权"
    C->>A: 重定向:/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&scope=xxx
    A->>U: 显示授权页面
    U->>A: 确认授权
    A->>C: 302 重定向 redirect_uri?code=AUTH_CODE
    C->>A: POST /token (code + client_secret)
    A->>C: 返回 access_token + refresh_token
    C->>R: 请求资源 + Bearer access_token
    R->>C: 返回受保护资源

7.4 SAML vs OAuth 2.0

维度 SAML OAuth 2.0
目的 联邦身份认证(SSO) 资源授权
数据格式 XML JSON
典型场景 企业 SSO(登录 Google Workspace) 第三方应用授权(GitHub 登录)
Token 类型 SAML Assertion Access Token (JWT)
复杂度 重(XML 解析、签名验证) 轻(REST + JSON)

8. 缓存穿透 & 布隆过滤原理

8.1 缓存穿透

缓存穿透是指查询一个**一定不存在**的数据,由于缓存未命中,每次都直接查询数据库。

graph LR
    R[请求 id=-1] --> C[缓存]
    C -->|未命中| DB[数据库]
    DB -->|不存在| C2[返回空]
    C2 --> R2[下次请求 id=-1]
    R2 --> C
    C -->|仍未命中| DB

解决方案:

方案 原理 适用场景
缓存空值 查询为空时也写入缓存(短 TTL) 数据量可控
布隆过滤器 预判断 key 是否可能存在 大量 key,允许一定误判
接口参数校验 在入口层拦截非法参数 参数有明确约束

8.2 布隆过滤器原理

布隆过滤器是一种**概率型数据结构**,用于判断一个元素**是否可能存在于集合中**。

判断结果 实际含义
不存在 一定不存在(100% 准确)
存在 可能存在(有假阳性率)

核心原理:

graph TD
    subgraph 布隆过滤器
        B[位数组 Bit Array<br/>m 位,初始全 0]
    end
    subgraph 插入过程
        E[元素 x] --> H1["Hash1(x) → 位置 i1"]
        E --> H2["Hash2(x) → 位置 i2"]
        E --> H3["Hashk(x) → 位置 ik"]
        H1 -->|置 1| B
        H2 -->|置 1| B
        H3 -->|置 1| B
    end
    subgraph 查询过程
        Q[查询 y] --> C1["Hash1(y) → 位置 i1"]
        Q --> C2["Hash2(y) → 位置 i2"]
        Q --> C3["Hashk(y) → 位置 ik"]
        C1 --> J{所有位都为 1?}
        C2 --> J
        C3 --> J
        J -->|是| R1[可能存在]
        J -->|否| R2[一定不存在]
    end

关键参数:

参数 含义 关系
m 位数组大小 m 越大,误判率越低
n 预期插入元素数量 n 越大,误判率越高
k 哈希函数个数 k = (m/n) × ln2 时最优
p 误判率(假阳性率) p ≈ (1 - e(-kn/m))k

布隆过滤器的局限

  • 不支持删除:置 0 可能影响其他元素(需要删除时考虑 Counting Bloom Filter)
  • 有假阳性:判断"存在"时可能误判
  • 需要预估容量:容量不足时误判率快速上升

9. MySQL 引擎有哪些 & InnoDB 特性

9.1 MySQL 存储引擎对比

特性 InnoDB MyISAM Memory Archive
事务支持 ✅ ❌ ❌ ❌
行级锁 ✅ ❌(表锁) ❌(表锁) ❌
外键 ✅ ❌ ❌ ❌
MVCC ✅ ❌ ❌ ❌
全文索引 ✅(5.6+) ✅ ❌ ❌
崩溃恢复 ✅(redo log) ❌ ❌ ❌
存储限制 64TB 256TB 受限于内存 无限制
适用场景 OLTP、高并发 读多写少 临时表、缓存 日志归档

9.2 InnoDB 核心特性

graph TD
    subgraph InnoDB 架构
        subgraph 内存
            BP[Buffer Pool<br/>数据页 + 索引页]
            LG[Log Buffer<br/>redo log 缓冲]
            CB[Change Buffer<br/>非唯一索引变更缓冲]
        end
        subgraph 磁盘
            SYS[系统表空间 ibdata1]
            UD[用户表空间 .ibd]
            RL[Redo Log ib_logfile]
            UL[Undo Log undo_001]
        end
        BP -->|脏页刷新| UD
        LG -->|顺序写| RL
        CB -->|合并| UD
    end

核心特性详解:

特性 说明
Buffer Pool 将数据页和索引页缓存在内存中,通过 LRU 变体算法管理
Change Buffer 对非唯一二级索引的 DML 操作先缓存,后台异步合并,减少随机 I/O
Redo Log WAL(Write-Ahead Logging)机制,保证事务的持久性,崩溃后可恢复
Undo Log 保存数据修改前的版本,用于事务回滚和 MVCC 一致性读
聚簇索引 数据按主键顺序存储,主键查询极快
MVCC 多版本并发控制,读操作不阻塞写操作
自适应哈希索引 InnoDB 自动为热点页建立哈希索引,加速等值查询

10. 幻读 & 隔离级别 & MVCC

10.1 幻读定义

幻读(Phantom Read):在同一事务中,两次执行相同的范围查询,第二次查询返回了第一次查询没有看到的**新行**(由其他事务 INSERT 产生)。

sequenceDiagram
    participant T1 as 事务 T1
    participant T2 as 事务 T2
    T1->>T1: SELECT * FROM users WHERE age > 20<br/>结果:3 行
    T2->>T2: INSERT INTO users(name, age) VALUES('新用户', 25)
    T2->>T2: COMMIT
    T1->>T1: SELECT * FROM users WHERE age > 20<br/>结果:4 行(幻读!)

10.2 四种隔离级别

隔离级别 脏读 不可重复读 幻读 实现方式
READ UNCOMMITTED ✅ 可能 ✅ 可能 ✅ 可能 无锁,直接读最新数据
READ COMMITTED (RC) ❌ 避免 ✅ 可能 ✅ 可能 每次读生成新 ReadView
REPEATABLE READ (RR) ❌ 避免 ❌ 避免 ⚠️ 部分避免 事务开始时生成 ReadView
SERIALIZABLE ❌ 避免 ❌ 避免 ❌ 避免 读加共享锁,写加排他锁

InnoDB 在 RR 级别下如何解决幻读

InnoDB 的 RR 级别通过 MVCC + Next-Key Lock 两种机制配合解决幻读: - 快照读(普通 SELECT):MVCC 保证读到事务开始时的快照,看不到新插入的行 - 当前读(SELECT ... FOR UPDATE / INSERT / UPDATE / DELETE):Next-Key Lock 锁住范围,阻止其他事务在该范围内插入新行

10.3 MVCC 机制

MVCC(Multi-Version Concurrency Control)通过保存数据的多个版本,实现读写不阻塞。

核心组件:

组件 说明
隐藏列 DB_TRX_ID 最近修改该行的事务 ID
隐藏列 DB_ROLL_PTR 回滚指针,指向 undo log 中的上一个版本
隐藏列 DB_ROW_ID 隐式主键(无显式主键时使用)
ReadView 事务在某一时刻看到的数据快照

ReadView 结构:

字段 含义
m_ids 创建 ReadView 时,系统中活跃(未提交)的事务 ID 列表
min_trx_id m_ids 中的最小值
max_trx_id 系统应分配给下一个事务的 ID(当前最大 ID + 1)
creator_trx_id 创建该 ReadView 的事务 ID

可见性判断规则:

func isVisible(rowTrxID uint64, rv *ReadView) bool {
    // 1. 如果行的事务 ID 等于创建者 ID,可见(自己修改的)
    if rowTrxID == rv.creatorTrxID {
        return true
    }
    // 2. 如果行的事务 ID 小于最小活跃事务 ID,可见(事务已提交)
    if rowTrxID < rv.minTrxID {
        return true
    }
    // 3. 如果行的事务 ID 大于等于最大事务 ID,不可见(事务在 ReadView 之后开始)
    if rowTrxID >= rv.maxTrxID {
        return false
    }
    // 4. 如果行的事务 ID 在活跃列表中,不可见(事务未提交)
    if rv.m_ids.contains(rowTrxID) {
        return false
    }
    // 5. 否则可见(事务已提交)
    return true
}

RC vs RR 的关键区别:

隔离级别 ReadView 生成时机 效果
RC 每次 SELECT 都生成新的 ReadView 能看到其他事务已提交的最新数据
RR 事务中第一次 SELECT 时生成,后续复用 整个事务看到一致的快照

11. Channel 阻塞非阻塞 & 应用 & context

11.1 Channel 阻塞与非阻塞

Go 的 channel 天然是阻塞的,但可以通过 select + default 实现非阻塞操作。

阻塞行为:

操作 有缓冲 Channel 无缓冲 Channel
ch <- v 缓冲区满时阻塞 没有接收者时阻塞
<-ch 缓冲区空时阻塞 没有发送者时阻塞

非阻塞实现:

// 非阻塞发送
select {
case ch <- value:
    // 发送成功
default:
    // channel 满或无接收者,跳过
}

// 非阻塞接收
select {
case msg := <-ch:
    // 接收到消息
default:
    // channel 空或无发送者,跳过
}

11.2 Channel 的典型应用

模式 说明 示例
生产者-消费者 解耦生产和消费速率 日志收集、任务队列
扇入(Fan-in) 多个 channel 合并到一个 聚合多个数据源
扇出(Fan-out) 一个 channel 分发给多个消费者 并行处理
超时控制 配合 time.After 实现超时 HTTP 请求超时
退出信号 配合 done channel 通知退出 优雅关闭
// 生产者-消费者模式
func producer(ch chan<- int) {
    for i := 0; i < 100; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch <-chan int) {
    for v := range ch {
        fmt.Println(v)
    }
}

11.3 Context 的作用

context.Context 是 Go 并发控制的核心,用于在 goroutine 之间传递**取消信号**、超时**和**请求级值。

方法 说明
context.Background() 根 Context,通常在 main/init 中使用
context.WithCancel(parent) 返回可手动取消的 Context
context.WithTimeout(parent, d) 超时后自动取消
context.WithDeadline(parent, t) 到达指定时间后自动取消
context.WithValue(parent, k, v) 携带请求级键值对
func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("收到取消信号:", ctx.Err())
            return
        default:
            // 执行业务逻辑
            doWork()
        }
    }
}

// 使用
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go worker(ctx)

Context 使用规范

  • Context 应作为函数的**第一个参数**,命名为 ctx
  • 不要将 Context 存储在 struct 中,而是显式传递
  • context.WithValue 只用于传递请求级元数据(如 traceID),不要传递业务参数

12. gin 特性 & 错误传递及处理

12.1 gin 核心特性

特性 说明
高性能路由 基于 radix tree(压缩前缀树),路由匹配 O(k) 复杂度
中间件支持 链式中间件,支持 c.Next() 和 c.Abort() 控制流程
参数绑定 自动将请求数据绑定到结构体(JSON、Query、Form、URI)
分组路由 路由分组,支持嵌套和中间件复用
错误管理 c.Errors 收集错误,支持统一错误处理

12.2 错误传递与处理

gin 提供了 c.Error() 方法收集错误,配合 c.Errors 统一处理。

错误收集:

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            // 收集错误,而不是直接返回
            c.Error(errors.New("missing authorization header"))
            c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
            return
        }
        c.Next()
    }
}

统一错误处理中间件:

func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()

        // 请求处理完成后,统一处理错误
        if len(c.Errors) > 0 {
            err := c.Errors.Last().Err
            // 根据错误类型返回不同状态码
            switch e := err.(type) {
            case *ValidationError:
                c.JSON(400, gin.H{"error": e.Message})
            case *NotFoundError:
                c.JSON(404, gin.H{"error": e.Message})
            default:
                c.JSON(500, gin.H{"error": "internal server error"})
            }
        }
    }
}

12.3 错误处理最佳实践

// 定义业务错误类型
type AppError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

func (e *AppError) Error() string { return e.Message }

// Handler 中使用
func GetUser(c *gin.Context) {
    id := c.Param("id")
    user, err := userService.GetByID(id)
    if err != nil {
        // 使用 c.Error 收集,由中间件统一处理
        _ = c.Error(&AppError{Code: 404, Message: "user not found"})
        return
    }
    c.JSON(200, user)
}

13. AI 编程工具 & 交互过程

13.1 主流 AI 编程工具

工具 定位 核心能力
Cursor AI-first IDE 内联补全、多文件编辑、上下文感知
GitHub Copilot 代码补全插件 实时代码建议、广泛语言支持
Claude Code CLI Agent 深度代码理解、工具调用、项目级重构
Windsurf AI IDE Cascade 流式编辑、多步推理

13.2 AI 辅助编程交互过程

graph TD
    U[开发者] -->|描述需求| AI[AI 编程工具]
    AI -->|生成代码| R[代码初稿]
    R -->|Review| U
    U -->|反馈修改| AI
    AI -->|迭代优化| R2[改进代码]
    R2 -->|测试验证| U
    U -->|确认| F[最终代码]

13.3 高效使用 AI 编程的实践

实践 说明
精准上下文 提供项目结构、依赖关系、编码规范,而非泛泛描述
任务拆解 将大任务拆成小的、可验证的子任务
迭代式开发 AI 生成初稿 → 人工 Review → 反馈修改 → 再次验收
Prompt 模板化 对高频场景沉淀可复用的 Prompt
验证闭环 AI 生成的代码必须经过单测、Lint、人工 Review

面试中如何回答

面试官问 AI 编程工具时,重点不是列举工具名称,而是: 1. 你用了哪些工具,在什么场景下用 2. 交互过程是什么样的,如何给 AI 提供上下文 3. 提效效果如何,有没有量化数据 4. 遇到过什么问题,如何保证代码质量


面试总结

这次奇安信一面考察面较广,涵盖了后端开发的核心知识:

考察维度 题目 核心考察点
缓存 滑动窗口限流、多级缓存、缓存穿透、布隆过滤器 缓存系统设计深度
Redis MULTI/Lua、单线程模型、高可用 Redis 原理和实战
MySQL 引擎对比、幻读、隔离级别、MVCC 数据库原理理解
认证 SAML、OAuth 2.0 安全认证体系
Go 语言 Channel、Context、gin Go 工程实践
AI 工程 AI 编程工具使用 AI 辅助开发经验

面试准备建议

  • 缓存和 Redis 是后端面试的高频考点,需要理解原理 + 能写代码
  • MySQL MVCC 需要理解到 ReadView 的可见性判断规则
  • Go 的 Channel 和 Context 是并发编程的核心,需要掌握各种模式
  • AI 编程相关问题需要有实际使用经验,不能只谈理论