11.30 学习笔记

一、Redis 核心问题

1. 为什么使用 Redis?

优势维度 具体说明
高性能 内存级操作,响应速度达微秒级,有效缓解数据库查询压力
多功能 支持 String/Hash/List/ZSet 等丰富数据结构,适配缓存、计数器、分布式锁等场景
高可用 主从复制 + 哨兵 + 集群架构,保障服务不间断运行
原子性 天然支持 INCR、SET NX 等原子操作,无需额外加锁实现并发控制

2. Redis 为什么快?

  1. 内存存储:核心数据驻留内存,无磁盘 IO 延迟;
  2. 单线程模型:避免多线程上下文切换、锁竞争等开销;
  3. IO 多路复用:基于 epoll(Linux)/kqueue(BSD),单线程处理海量客户端连接;
  4. 高效数据结构:String 用动态字符串、Hash 用压缩列表,操作复杂度低;
  5. 异步 IO:持久化、网络发送等 IO 操作异步执行,不阻塞主线程。

3. 追问 1:Redis 的 IO 操作包含哪些?

IO 类型 具体操作
网络 IO 客户端连接(accept)、请求读取(read)、响应发送(write)
磁盘 IO 持久化(RDB 快照写入、AOF 日志追加)、数据加载(启动读 RDB/AOF)、过期键删除

4. 追问 2:Redis 用什么让 IO 更快?

IO 类型 优化手段
网络 IO 1. IO 多路复用(epoll+ET 模式);2. 非阻塞 IO;3. 批量处理请求
磁盘 IO 1. 异步刷盘(AOF 分每秒刷/事务提交刷/不刷三种策略);2. 内存映射(mmap)减少 RDB 拷贝;3. 顺序写(AOF/RDB 均为顺序写,比随机写快 10 倍以上)

二、Go & Java 对比

1. 优势对比

维度 Go Java
性能 编译型语言,接近 C,无 JVM 开销 解释型(JIT 编译),性能略低
并发 轻量级协程(Goroutine),百万级并发 重量级线程(依赖 OS 线程),受线程数限制
语法 简洁,少封装,易上手 面向对象完善,生态丰富
生态 云原生/中间件(Docker/K8s)强 全场景生态(电商/金融/企业级)
部署 编译为单二进制文件,无依赖 需 JRE,依赖多

2. 跨平台实现

  • Go:编译时通过跨平台编译器,指定 GOOS/GOARCH 生成对应系统(Linux/Windows/Mac)的二进制文件,运行时无依赖;
  • Java:一次编译生成字节码(.class),JVM 针对不同系统实现字节码解释器/编译器,实现"一次编译,到处运行"。

三、场景题

1. 扫码登录(防篡改)

核心流程
步骤 操作说明
生成二维码 1. 服务端生成 UUID 作为二维码 ID,关联"未登录状态+5 分钟过期时间"存入 Redis;2. 二维码内容包含:二维码 ID + 服务端域名 + 时间戳
用户扫码授权 1. 手机端扫码获取二维码 ID,携带用户 token 调用登录接口;2. 服务端验证 token,更新 Redis 中二维码 ID 状态为"已授权"并关联用户 ID
PC 端轮询验证 1. PC 端每秒轮询查询二维码 ID 状态;2. 状态为"已授权"时生成 PC 端登录 token 并返回
登录完成 PC 端携带 token 访问资源,服务端验证 token 有效性
防篡改措施
  1. 二维码加签:HMAC-SHA256 对"二维码 ID+时间戳+密钥"签名,手机端扫码后验签;
  2. 传输加密:全程 HTTPS 防止中间人篡改;
  3. 一次性验证:二维码 ID 登录成功后立即失效;
  4. 防重放:时间戳+RSA 非对称加密,服务端验证 30 秒超时。

2. 社区关注/被关注功能设计

核心模块
模块名 核心职责
关系管理模块 1. 关注/取消关注;2. 查询关注列表/粉丝列表;3. 检查是否已关注
存储模块 1. 数据库:user_follow(id, user_id, follow_id, create_time);2. Redis 缓存关注数/粉丝数(ZSet/HSet)
通知模块 关注后基于 MQ 异步推送"XX 关注了你"通知
权限模块 限制高频操作(如 1 分钟最多关注 10 人),防止刷量
统计模块 Redis 计数器(INCR/DECR)实时统计关注数,定时同步到 DB
设计细节
  1. 数据库:user_follow 表建立复合索引(user_id+follow_id),防止重复关注;
  2. 性能优化:关注/粉丝列表分页查询,Redis 缓存前 100 条数据;
  3. 高并发:关注操作加分布式锁,避免重复写入。

四、Redis GEO

1. 核心原理

基于 GeoHash 算法,将经纬度编码为字符串,支持距离计算与范围查询。

2. 核心命令

命令格式 功能 示例
GEOADD key lon lat member 添加地理位置 GEOADD driver:location 116.403874 39.914885 driver1
GEODIST key m1 m2 [unit] 计算成员距离 GEODIST driver:location driver1 driver2 km
GEORADIUS key lon lat radius unit [WITHCOORD] [WITHDIST] [COUNT] 按坐标查范围内成员 GEORADIUS driver:location 116.403874 39.914885 5 km WITHCOORD WITHDIST COUNT 10
GEORADIUSBYMEMBER key member radius unit ... 按成员查范围内其他成员 GEORADIUSBYMEMBER driver:location driver1 3 km COUNT 5
GEOHASH key member... 获取 GeoHash 编码 GEOHASH driver:location driver1

3. 实战场景

  1. 滴滴打车:GEORADIUS 查询用户 5 公里内可用司机;
  2. 外卖配送:GEODIST 计算骑手与商家/用户距离;
  3. 本地生活:GEORADIUSBYMEMBER 查询店铺 3 公里内用户。

4. 注意事项

  1. 精度:GeoHash 精度 1-10 米,不适合高精度定位;
  2. 数据结构:底层是 Sorted Set,删除用 ZREM 命令;
  3. 性能:单 Key 成员数≤10 万,超量按区域拆分(如 driver:location:beijing)。

五、数据库相关

1. MySQL & MongoDB 区别

维度 MySQL(关系型) MongoDB(文档型 NoSQL)
数据模型 结构化(表+行+列),预定义 Schema 非结构化(BSON 文档),Schema 灵活
事务支持 支持 ACID(InnoDB),强一致性 4.0+ 支持多文档事务,默认最终一致性
查询能力 支持复杂 SQL(JOIN/子查询/聚合) 支持简单聚合,不支持复杂 JOIN
索引类型 主键/普通/联合/唯一/全文索引 单字段/复合/地理空间/文本索引
适用场景 结构化数据(订单/用户/财务),需强事务 非结构化数据(日志/商品详情/用户画像),高写入
扩展性 垂直扩展为主,水平扩展需分库分表 原生支持分片集群,水平扩展灵活

2. 关系型数据库命名原因

  1. 数据存储为二维表结构,表间通过主键-外键建立"关系",符合集合论中"关系"的数学定义;
  2. 遵循 ACID 事务特性,保证数据可靠性;
  3. 支持 SQL 结构化查询,标准化操作数据。

3. 并发事务问题(脏读/不可重复读/幻读)

问题类型 定义 示例
脏读 读取到未提交事务的数据,回滚后失效 事务 A 改余额为 1000(未提交),事务 B 读取,A 回滚后 B 读的是脏数据
不可重复读 同一事务内多次读同一数据,结果不一致 事务 A 首次读余额 500,事务 B 改余额为 800 并提交,A 再次读为 800
幻读 同一事务内多次范围查询,行数不一致 事务 A 查余额>500 的用户有 3 人,事务 B 插入 1 人并提交,A 再次查为 4 人

4. 可重复读(MySQL 默认隔离级别)

核心定义

同一事务内多次读取同一数据/范围数据,结果始终一致,不受其他并发事务修改影响。

实现细节
  1. MVCC 多版本控制 :事务启动生成 Read View,仅能看到 Read View 生成前已提交的数据;
    • 示例:事务 A(ID=100)生成 Read View,事务 B(ID=101)修改数据并提交,A 仍读旧数据;
  2. 锁机制:行锁防止修改,间隙锁(范围查询时)防止插入,解决大部分幻读问题。
场景验证
sql 复制代码
-- 事务 A
BEGIN;
SELECT balance FROM user WHERE id=1; -- 结果 500
-- 事务 B
BEGIN;
UPDATE user SET balance=800 WHERE id=1;
COMMIT;
-- 事务 A
SELECT balance FROM user WHERE id=1; -- 结果仍为 500
COMMIT;
SELECT balance FROM user WHERE id=1; -- 结果 800

六、编程题

32 位有符号整数反转

java 复制代码
public class ReverseInteger {
    public int reverse(int x) {
        int res = 0;
        while (x != 0) {
            // 弹出最后一位
            int digit = x % 10;
            x = x / 10;
            
            // 预判溢出
            if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && digit > 7)) {
                return 0;
            }
            if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && digit < -8)) {
                return 0;
            }
            
            // 拼接数字
            res = res * 10 + digit;
        }
        return res;
    }

    // 测试用例
    public static void main(String[] args) {
        ReverseInteger solution = new ReverseInteger();
        System.out.println(solution.reverse(123));    // 321
        System.out.println(solution.reverse(-123));   // -321
        System.out.println(solution.reverse(120));    // 21
        System.out.println(solution.reverse(2147483647)); // 0
    }
}
代码解释
  1. 弹出数字:x%10 取最后一位,x/10 去除最后一位(负数除法仍为负);
  2. 溢出预判:通过 res > MAX/10res < MIN/10 提前判断,避免直接溢出;
  3. 拼接数字:无溢出则更新结果,循环至 x=0

岛屿数量(Go BFS 版)

go 复制代码
func numIslands(grid [][]byte) int {
    if len(grid) == 0 {
        return 0
    }
    count := 0
    rows, cols := len(grid), len(grid[0])
    dirs := [][]int{{-1,0}, {1,0}, {0,-1}, {0,1}} // 上下左右
    
    for i := 0; i < rows; i++ {
        for j := 0; j < cols; j++ {
            if grid[i][j] == '1' {
                count++
                // BFS 队列
                queue := [][]int{{i,j}}
                grid[i][j] = '0' // 标记已访问
                
                for len(queue) > 0 {
                    curr := queue[0]
                    queue = queue[1:]
                    // 遍历四个方向
                    for _, dir := range dirs {
                        x, y := curr[0]+dir[0], curr[1]+dir[1]
                        if x >=0 && y >=0 && x < rows && y < cols && grid[x][y] == '1' {
                            queue = append(queue, []int{x,y})
                            grid[x][y] = '0'
                        }
                    }
                }
            }
        }
    }
    return count
}
面试加分点
  • 时间复杂度:O(M*N)(M 行 N 列,每个格子仅访问一次);
  • 空间复杂度:DFS 为 O(M*N)(递归栈),BFS 为 O(min(M,N))(队列大小);
  • 拓展:超大网格可改用并查集,避免递归栈溢出。
相关推荐
诺狞猫1 小时前
黄山派 TF卡使用
科技·学习·黄山派·思澈·sifli
卡提西亚1 小时前
数据库笔记-0-MYSQL安装
数据库·笔记·sql
ljt27249606612 小时前
Compose笔记(五十九)--BadgedBox
android·笔记·android jetpack
优选资源分享2 小时前
维克日记 v1.5.0:本地隐私日记工具
笔记·实用工具·本地日记
雷工笔记2 小时前
MES学习笔记之MES系统的作用和定位及与SCADA的关系
大数据·笔记·学习
c***93772 小时前
Spring Security 官网文档学习
java·学习·spring
韩曙亮2 小时前
【人工智能】AI 人工智能 技术 学习路径分析 ③ ( NLP 自然语言处理 )
人工智能·pytorch·学习·ai·自然语言处理·nlp·tensorflow
雷工笔记2 小时前
MES学习笔记之MES常见的类别
笔记·学习
丝斯20112 小时前
AI学习笔记整理(20)—— AI核心技术(深度学习4)
人工智能·笔记·学习