
一、面试复习通信、锁与代理这部分是前几天面试题的展开讲解也是写高并发程序的基础。1.1 什么是 IPC如何进行进程间通信IPC是 Inter-Process Communication进程间通信的缩写。之所以需要进程间通信是因为进程之间数据是隔离的。两种情况同一台机器上的两个进程通信如同一个程序开启的多个进程Python多进程。不同机器上的两个进程通信跨主机通信。如何通信方式说明共享内存 / 共享变量线程间变量天然共享进程间需借助共享内存队列 / 管道如 Pythonmultiprocessing.Queue、Pipe消息队列Redis、RabbitMQ、Kafka 等Socket 套接字服务与服务的接口调用HTTP、RPC、MySQL socket 自定协议文件方式通过文件交换数据1.2 线程间通信与锁重点补充为什么需要锁多线程共享同一个变量时多个线程同时读写就会产生数据竞争导致数据错乱因此需要加锁保证数据安全。互斥锁LockfromthreadingimportThread,Lockdefwork():globaln lock.acquire()# 加锁保证同一时刻只有一个线程修改共享数据tempn time.sleep(0.001)ntemp-1lock.release()join与锁的区别在start()后立即join()会让 100 个任务整体串行执行虽然数据安全但效率很低。加锁只是把“修改共享数据”的那部分代码串行化不加锁的部分仍能并发效率更高。死锁两个或两个以上的进程/线程执行时因争夺资源而互相等待若无外力干预都无法继续推进。典型如“科学家吃面”问题每个科学家需要同时拿两把叉子才能吃面若各拿一把又互相等待另一把就会死锁。递归锁RLock/ 可重入锁允许同一个线程多次acquire内部维护一个计数器记录获取次数同一次数释放后其他线程才能获取。fromthreadingimportRLock lockRLock()lock.acquire()lock.acquire()# 同一线程可以再次获取不会死锁print(123)lock.release()lock.release()信号量Semaphore可以理解为“多把锁”同时允许多个线程执行某段代码。例如Semaphore(5)表示最多 5 个线程同时进入。事件Event某些线程需要等其他线程执行到某个节点后再继续。例如女神者等待对方event.set()后多个备胎线程event.wait()再启动。GIL 与普通互斥锁的区别GIL 是解释器级别的锁保证同一时刻只有一个线程执行 Python 字节码。普通互斥锁是应用层面的锁用来保护共享数据。GIL 无法替代数据保护所以仍需互斥锁。I/O 密集型与计算密集型的选型单核 CPU无论 I/O 密集还是计算密集都用线程或协程。多核 CPU计算密集型用多进程I/O 密集型用多线程I/O 等待不占用 CPU。1.3 正向代理与反向代理代理本质是一个中介原本 A 和 B 可以直连中间插入一个 CC 就是代理。正向代理 —— 代理的是客户端访问原本无法访问的资源如 Google。做缓存加速访问。对客户端访问授权、上网认证。记录用户访问记录上网行为管理对外隐藏用户身份。典型如 VPN、爬虫代理池。反向代理 —— 代理的是服务端保护内网安全阻止 Web 攻击。大型网站通常把反向代理作为公网访问入口Web 服务器放在内网。负载均衡通过反向代理服务器优化网站负载。典型如 nginx、apache。项目上线升级常结合反向代理做灰度发布与平滑升级。1.4 什么是粘包粘包是TCP 流式协议的现象TCP 没有消息边界客户端发送的多个数据包会像水流一样流向服务端多个包“粘”在一起无法区分是几个数据包。解决思路每个包设置结束标志如 HTTP 用\r\n\r\n。每个包设置固定大小的头头里包含包的大小再按长度读取。二、补充表单表设计结合业务动态表单可以用“两张表 一个提交内容字段”来设计表单表定义一个表单id创建人创建时间1用户 id时间表单详情表定义表单字段id表单 id类型key是否必填值11单行文本申请人true21单选框用途true[测试, 生产]31单选框是否第一次true[是, 否]41多选框云厂商true[腾讯云, 阿里云, xx云]填写表单用户提交记录id表单 id用户 id内容111{1:lqz, 2:测试, 3:是, 4:[腾讯云, 阿里云]}224{1:是, 2:测试, 3:是, 4:[腾讯云, 阿里云]}内容字段直接用 JSON 存储灵活但不易汇总统计适合字段不确定的动态表单场景。三、通用命令info# 查看内存、CPU、主从信息可用于写 Redis 监控client list# 查看正在连接的会话clientkillip:port# 断开某个客户端dbsize# 统计总共有多少个 keyflushall# 清空所有数据库危险慎用flushdb# 只清空当前数据库select数字# 选择某个库默认共 16 个库0~15monitor# 记录/监控操作日志会一直输出注意会夯住终端生产环境请谨慎使用flushall、flushdb、monitor。其中monitor会持续打印所有命令影响性能且占用终端。管理 Redis 实例的 Web 项目可选参考https://gitee.com/bijingrui/repollhttps://gitee.com/dromara/mayfly-gohttps://gitee.com/careyjike_173/redis_web_clienthttps://gitee.com/Alcex/QMySQLAdminhttps://gitee.com/CloudWise/OMPhttps://gitee.com/lustlost/ubackup四、字符串String类型String 是 Redis 最基本也最常用的数据类型value 是二进制安全的可以存图片、序列化对象等。4.1 基本使用 get / set / delsetname lqz# 设置 key 的 valueO(1)get name# 获取 key 的 valueO(1)del name# 删除 keyO(1)4.2 自增自减 incr / decr / incrby / decrbyincr age# value 自增 1decr age# value 自减 1incrby age10# value 增加 10decrby age10# value 减 10应用场景统计网站访问量Redis 单线程执行命令天然无竞争适合做计数器。分布式 ID 生成多台机器并发生成也不会重复。说明INCR/DECR要求 value 是整数字符串否则会报错。这类命令用于计数非常高效。4.3 setnx / setxxsetname lqz# 不管 key 是否存在都设置setnx name lqz# key 不存在时才设置新增setname lqz nx# 等价于 SETNXsetname lqz xx# key 存在时才设置更新分布式锁用SET key value NX EX 秒数实现“不存在才设置 自动过期”是分布式锁的常见做法。setlock_code1NX EX10# 加锁最多持有 10 秒del lock_code# 释放锁注意SETNX与EXPIRE分开写不是原子的可能出现“已 setnx 但还没 expire 时进程崩溃锁永不释放”的问题因此更推荐SET key value NX EX。4.4 批量操作 mget / msetmget key1 key2 key3# 批量获取O(n)mset key1 value1 key2 value2 key3 value3# 批量设置O(n)n次get与一次mget的差别n次get n 次命令时间 n 次网络往返时间。一次mget 1 次网络往返 n 次命令时间省去大量网络开销。4.5 getset / append / strlengetset name lqznb# 设置新值并返回旧值O(1)append name666# 把 value 追加到旧值后面O(1)strlen name# 计算字符串长度注意中文按 UTF-8 字节数计算O(1)4.6 incrbyfloat / getrange / setrangeincrbyfloat age3.5# value 自增 3.5传负值表示自减O(1)getrange key03# 获取指定下标范围的子串O(1)setrange key2xyz# 从指定下标开始覆盖写入 valueO(1)4.7 String 的其他要点底层编码String 底层为SDS简单动态字符串根据长度有int、embstr、raw三种编码embstr用于较短字符串raw用于较长字符串。常用场景缓存、计数器、分布式锁、会话Session、短信验证码、限流等。五、哈希Hash类型Hash 类似字典一个 key 对应多个 field-value适合存对象或一行的字段。5.1 基本操作 hget / hset / hdelhset key field value# 设置 hash key 对应的 field 的 valueO(1)hget key field# 获取 hash key 对应的 field 的 valueO(1)hdel key field# 删除 hash key 对应的 fieldO(1)示例hset user:1:info age23hget user:1:info age hset user:1:info name lqz hgetall user:1:info hdel user:1:info age5.2 判断与计数 hexists / hlenhexists user:1:info name# 判断 field 是否存在O(1)hlen user:1:info# 获取 field 数量O(1)5.3 批量操作 hmget / hmsethmget key field1 field2... fieldN# 批量获取O(n)hmset key field1 value1 field2 value2# 批量设置O(n)5.4 全量操作 hgetall / hvals / hkeyshgetall key# 返回所有 field 和 valueO(n)hvals key# 返回所有 field 的 valueO(n)hkeys key# 返回所有 fieldO(n)小心使用hgetall它返回全部字段当字段很多时会阻塞 Redis生产环境建议用hscan游标式代替。两个典型应用统计网站每个用户主页的访问量hincrby user:1:info pageview count。缓存 MySQL 的一条记录把行字段作为 field 存入 hash如hmset user:1 name lqz age 23。5.5 hsetnx / hincrby / hincrbyfloathsetnx key field value# field 不存在才设置O(1)hincrby key field intCounter# field 的 value 自增O(1)hincrbyfloat key field floatCounter# field 的 value 自增浮点数O(1)小结Hash 适合存“一个对象的多个字段”比把整个对象序列化成字符串更省内存、方便单字段更新如只更新 age。六、算法与数据结构复杂度6.1 为什么重要算法和数据结构是编程的基石算法代码的执行逻辑和流程如for、if例如排序。数据结构变量的组织结构如list、map、set。写 Redis 时理解每条命令的时间复杂度能避免在数据量巨大时把服务“打停”。6.2 大 O 表示法记号含义例子O(1)常数时间与数据量无关GET、SET、HSET、LPUSH、SADDO(log n)对数时间ZSet的ZADD/ZRANGEBYSCORE底层跳表O(n)线性时间随数据量线性增长KEYS、SMEMBERS、HGETALL、LRANGE全量O(n²)平方时间某些嵌套遍历算法时间复杂度消耗的时间空间复杂度消耗的内存。6.3 生产环境注意点keys、smembers、hgetall等全量命令是O(n)key 很多时会阻塞 Redis。用scan、hscan、sscan代替全量命令逐批游标式遍历不阻塞。单条命令尽可能快批量操作mget、mset、pipeline减少网络往返。想刷题可参考力扣LeetCode难度分简单、中等、困难排序等算法可查阅 Python 常用排序教程。七、列表List类型List 是有序的字符串列表可用来实现队列、栈、消息队列等。7.1 插入操作rpush key value1 value2... valueN# 从右侧插入O(1~n)lpush key value1 value2... valueN# 从左侧插入O(1~n)linsert key before|after value newValue# 在 value 前/后插入O(n)需遍历列表示例linsert listkey before bjavalinsert listkey after b php7.2 删除操作lpop key# 从左侧弹出一个 itemO(1)rpop key# 从右侧弹出一个 itemO(1)lrem key count value# 按 count 删除 valueO(n)ltrim key start end# 按索引范围修剪列表O(n)lrem的count规则count 0从左到右删除最多 count 个 value 相等的项。count 0从右到左删除最多Math.abs(count)个 value 相等的项。count 0删除所有 value 相等的项。示例lrem listkey0a# 删除列表中所有值 alrem listkey-1c# 从右侧删除 1 个 cltrim listkey14# 只保留下标 1~4 的元素7.3 查询操作lrange key start end# 获取指定索引范围所有 item包含 endO(n)lindex key index# 获取指定索引的 itemO(n)llen key# 获取列表长度O(1)示例lrange listkey02# 下标 0~2lrange listkey1-1# 从第 1 个到倒数第 1 个lindex listkey0lindex listkey-17.4 修改操作lset key index newValue# 设置指定索引的值O(n)lset listkey2ppp# 把第 2 个位置设为 ppp7.5 阻塞式操作 blpop / brpopblpop keytimeout# LPOP 的阻塞版本timeout 为超时时间0 表示一直阻塞brpop keytimeout# RPOP 的阻塞版本timeout 为超时时间0 表示一直阻塞7.6 实战用 List 组合出常见结构# 实现栈先进后出lpush lpop# 实现队列先进先出lpush rpop# 固定大小的列表只保留最近 N 条lpush ltrim# 阻塞式消息队列生产者-消费者lpush brpop实现时间轴timeLine把关注的人的微博按时间顺序lpush进列表即可按时间轴展示。八、集合Set类型Set 是无序、去重的字符串集合适合做标签、点赞、抽奖、共同好友等。8.1 基础命令sadd key element# 向集合添加元素重复添加失败O(1)srem key element# 移除元素O(1)scard key# 计算集合大小sismember key element# 判断元素是否在集合中srandmember key count# 随机取出 count 个元素不破坏集合spop key# 随机弹出一个元素并移除smembers key# 获取所有元素无序O(n)小心使用会阻塞8.2 集合运算sdiffkey1 key2# 差集在 key1 中但不在 key2 中sinter key1 key2# 交集同时在两个集合中sunion key1 key2# 并集两个集合所有元素把结果保存到新集合sdiffstore dest key1 key2# 差集存入 destsinterstore dest key1 key2# 交集存入 destsunionstore dest key1 key2# 并集存入 dest8.3 集合典型应用抽奖系统spop弹出用户 id活动取消直接删除集合。点赞 / 点踩 / 喜欢用户点了赞就把用户 id 放到该条记录的集合中。标签给用户/文章打标签sadd user:1:tags 标签1 标签2也可反向保存“关注某标签的人有哪些”。共同好友使用集合的交集 / 并集 / 差集计算。总结sadd标签相关。spop/srandmember随机数相关抽奖、随机推荐。sadd/sinter社交相关共同好友、共同关注。九、面试题与参考答案1. HTTP 协议详情、版本、请求头HTTP 协议基于 TCP 的“请求-响应”式无状态应用层协议客户端发起请求服务端返回响应。版本HTTP/1.0短连接每次请求都要新建连接。HTTP/1.1持久连接keep-alive、管线化、chunked分块传输解决队头阻塞能力有限。HTTP/2二进制分帧、多路复用、头部压缩HPACK、服务端推送解决队头阻塞。HTTP/3基于 QUIC底层 UDP进一步降低连接建立延时。常见请求头Host # 请求主机 User-Agent # 客户端标识 Accept # 可接受的响应类型 Accept-Encoding # 可接受的压缩编码 Content-Type # 请求体类型如 application/json Content-Length # 请求体长度 Authorization # 认证信息 / Token Cookie # 会话 Cookie Cache-Control # 缓存策略 Connection # 连接控制keep-alive / close Referer # 来源页面 Origin # 跨域来源 X-Forwarded-For # 经过代理的客户端 IP常见响应头Content-Type、Content-Length、Set-Cookie、Cache-Control、Location、Server、Date等。2. 如何实现服务器给客户端发送消息WebSocket 是什么普通的 HTTP 是“客户端主动请求、服务端被动响应”无法直接由服务端主动推消息。实现服务端推送的常见方式轮询Polling客户端定时请求浪费资源。长轮询Long Polling服务端挂起请求直到有新消息减少请求次数。SSEServer-Sent Events服务端单向推送基于 HTTP。WebSocket推荐方案。WebSocket 是什么一种基于 TCP 的全双工通信协议。客户端通过 HTTP 发起握手Upgrade: websocket升级后建立一条持久连接双方可随时互相发消息适合聊天、实时通知、在线游戏、实时行情等场景。常见实现Django Channels、FastAPI WebSocket、Node.js 的 Socket.IO 等。3. 悲观锁和乐观锁如何实现悲观锁假设一定会发生冲突访问数据前先加锁其他线程/事务被阻塞。实现MySQLSELECT ... FOR UPDATE、Redis 分布式锁SET NX。乐观锁假设很少冲突不加锁提交时检查版本号是否变化变了则重试。实现MySQL 版本号字段version、Redis 使用WATCHMULTIEXEC的乐观事务。在 Redis 中的实现方式# 悲观锁分布式锁key 不存在才写入并设置过期时间SET lock:order1NX EX5DEL lock:order# 乐观锁WATCH 监视 key在事务期间若被修改则 EXEC 返回失败WATCH stock:1 MULTI DECR stock:1 EXEC UNWATCH