分析并发问题的通用方法

概念定义 到底什么是并发 并发 = 多个操作在同一时间窗口内同时进行,无论来自同一人还是不同人。 因为数据只有一份,多人同时操纵就会有风险:谁说了算呢?所以会产生竟态问题。cpu的并发就是最好的理解方式了:cpu核只有一个的情况下,不可能同一时刻给多人使用,所以只能大家轮着时间切片用。 一条数据会不会在同一时刻被两个操作同时修改?是的话就是存在并发问题。 什么是分布式 分布式:系统本身被拆分成了多个服务/节点。系统架构是分散的 一个人讲话给自己听,很难搞错; 三个人ABC一起传话,中间的B听错了,那传到C耳朵里的就变了。大家拿着的信息不一样了,所以分布式场景就是要解决怎么尽量去统一大家信息的问题。 什么是幂等 幂等就是:同一个操作,执行一次和多次,结果不变。 为什么我要单独把他拎出来讲?因为不幂等的设计,既是并发问题,也是分布式问题。 并发如何体现: 多个人同时下单; 一个人同一时刻点赞十次; 分布式如何体现: 项目有三个角色,分别是生产者、MQ、消费者; 还可能是更复杂的微服务项目; 通用分析方法 首先我想声明一句: 在分布式并发问题中,任何不根据具体情景分析,只讲技巧的,都是耍流氓!并发场景信息杂糅、需求各不一致。技术本身是为了解决问题的,脱离了真实情况的技术毫无用处,反而成了心智负担。 四个问题,四个数 遇到一个百万级QPS以内的分布式、并发场景,依次问四个问题,就能精准锁定架构与技术方案方向: 1. 人数 这个操作是 “单人” 还是 “多人”? 单人操作(比如用户修改自己的头像):不会有多人写冲突,只可能有重复提交(浏览器重试),用幂等键就够了(如 Redis SETNX)。 多人操作(比如点赞、关注、下单扣库存):有并发争抢、写冲突风险。 2. 数据库行数 & 表数 这个操作是 “单行单表数据” 还是 “多行 / 跨表数据”? 单行单表(点赞计数、关注关系):数据库层的唯一索引 + 状态字段可以直接解决。 多行或跨表(下单:扣库存→生成订单→扣钱):数据库行锁或乐观锁(version) 是核心,唯一索引只能防重复记录,但防不了库存超卖。 3. 重试数 错误可以 “直接返回” 还是必须 “排队等待”?也就是业务对失败的容忍度如何? 可以直接返回 “请稍后重试”(如点赞冲突、重复领券):用乐观锁或唯一键冲突,返回失败由客户端自行重试。 必须排队串行执行、不能失败丢弃(如秒杀扣库存、限量抢购):用行锁或消息队列串行化,牺牲部分吞吐,强保数据不出错。 4. 一致数 业务需要 “强一致性” 还是 “最终一致性”? 强一致性(转账、钱包扣款、资金交易):必须即时数据一致,不能异步兜底,要用分布式事务、行锁、悲观锁,不能靠异步补偿。 最终一致性(普通下单、发积分、返优惠券、日志统计):允许短暂数据不一致,可异步慢慢补齐,优先用MQ 异步、本地消息表、事务消息解耦,提升吞吐与可用性。 情景演练 情景一:关注 / 取关 问题 答案 人数 多人操作(多人同时对同一博主关注/取关,粉丝数共享) 表数 单行单表(核心socials一行,accounts计数的更新属于事务外 MQ) 重试 直接返回成功(重复操作不报错,与幂等一致) 一致性 核心强一致(关系状态原子翻转),计数最终一致(MQ 异步) ...

May 7, 2026 · 2 min · 378 words · Jamaisvu

并发控制与缓存一致性技术选型之权衡术

核心权衡点:锁的量级与性能损耗的权衡 选择哪种并发控制或一致性手段,本质是在 一致性强度 与 系统吞吐 / 延迟 之间做交易。不存在普适最优解,只有基于回源成本、并发量级和跨实例协调代价的按需组合。 量级轻 → 延迟低,但保护弱:进程内 singleflight,非阻塞软标记,纯 TTL 过期。 量级重 → 一致性强,但代价高:跨实例分布式锁,强同步写穿透。 下面从“数据流”与“控制面”两个维度梳理关键技术,并给出组合原则。 1. 数据一致性模式(如何同步缓存与数据库) 这些模式关注当源数据变更时,缓存如何更新或失效,不直接涉及并发控制,但影响选型。 Cache Aside(旁路缓存) 模式:读未命中则查 DB 并回写缓存;写直接更新 DB,然后删除缓存。 锁量级:写操作无锁,仅单一 DEL;读可配合 singleflight 防击穿。 权衡:最终一致窗口 = 删缓存到下次重建之间;删除失败需重试或 TTL 兜底。 适用:读多写少,可接受短暂不一致的场景(大多数互联网业务)。 Read/Write Through(读写穿透) 模式:缓存层代理所有 DB 读写,业务只与缓存交互。 锁量级:同步写,需保证缓存与 DB 的原子更新,往往引入分布式锁或事务消息。 权衡:一致性强,但每次写都要同时操作缓存+DB,写入延迟高,实现复杂。 Write Behind(异步回写) 模式:写只更新缓存,异步批量刷回 DB。 锁量级:缓存写入轻量,但需要队列/日志保证不丢数据。 权衡:写入性能极高,一致性很弱,允许丢数据窗口。 延迟双删与订阅刷新 延迟双删:写 DB 前先删缓存,DB 更新完成后延迟再删一次(用于规避主从延迟导致的不一致)。 Binlog 订阅(如 Canal):异步监听 DB 变更流水,精确删除或更新缓存。 锁量级:均为异步,无额外锁竞争,但引入消息延迟和架构复杂度。 本系统选择:Cache Aside + MQ 异步删除,利用已有死信队列保障最终一致性,兼顾简单和可靠。 2. 并发控制机制(如何防击穿、防并发重建) singleflight(进程内请求合并) 量级:极轻,无网络开销,仅内存 map + 阻塞等待。 保护范围:单进程内相同请求的合并。 性能损耗:少数请求等待首次执行完毕,延迟可忽略。 适用:高并发下同一批缓存键临时穿透 Redis 的场景。 分布式锁(如 Redis SET NX EX) 量级:重,涉及网络往返、轮询等待、锁 TTL 管理和 Lua 释放。 保护范围:跨实例互斥,确保单点重建。 性能损耗:锁竞争导致额外延迟(轮询 100ms+),可能成为瓶颈。 适用:回源成本极其高昂且要求强一致的场景(如复杂报表、详情缓存重建)。 软标记 / SETNX 提示(非阻塞跨实例通知) 量级:极轻,一次 Redis SETNX 无等待。 保护范围:跨实例“知情权”,让其他实例主动降级而非常规等待。 性能损耗:基本无延迟,仅需判断标记存在与否。 适用:重建允许降级或短暂不一致的场景(批量读、冷缓存预热)。 仅靠 TTL 量级:无。 保护:无任何并发控制。 损耗:零,但可能发生缓存击穿或雪崩。 适用:数据可大量容忍陈旧,或变更极低频。 3. 组合决策矩阵 场景 回源成本 推荐组合 理由 视频实体批量读取(高频、可降级) 中(批量主键查询) singleflight + 软标记 进程内去重 + 跨实例非阻塞防多余穿透 视频详情页读取(中频、不期望旧数据) 中高(复杂 SQL 或关联查询) 分布式锁 + double-check 强控单实例重建,避免多实例重复计算 关注流冷缓存重建(低频、允许最终一致) 高(多表聚合排序) 软标记 + 单一执行 不强制等待,接受少量重复重建成本优于锁等待 写操作后缓存失效(Cache Aside) 极低(DEL 命令) 无需锁,直接 DEL 并发写由 DB 串行化,缓存仅打扫战场 4. 一句话选型指南 先上 singleflight,解决绝大多数进程内并发穿透。 多实例部署且重建轻量时,补 软标记 提示降级,避免引入锁。 只有当“多个实例并发重建会造成无法接受的成本或数据矛盾”时,再引入 分布式锁。 缓存更新一律走 Cache Aside 异步删除,放弃复杂同步,靠重试和 TTL 兜底。 记住:锁是最后的手段,不是默认选项。 能靠最终一致和降级解决的,不要用强同步去惩罚高并发。

May 5, 2026 · 1 min · 165 words · Jamaisvu

一篇讲清api与webhook

基本区别 许多小白第一次接触webhook的时候常常会与api混淆,那么我们先来看看两者的本质差别在哪: api 一般用于用户主动发起请求,目标系统返回用户需要的数据、结果。API 用途广泛,涉及查询、修改、执行等 webhook 一般用于用户提前设置好触发条件与通知方式,当符合条件,目标系统会主动给用户推送信息、执行动作,无须用户主动查询。webhook 用途单一,仅用于事件发生时的实时推送通知。 **ps:**webhook 是 API 的一种,所有 webhook 都是 API,但不是所有 API 都是 webhook。 深入了解webhook webhook的特征 webhook基于HTTP/HTTPS协议,以POST为主,少数用GET 常用 JSON(简洁易解析),也有用 XML、表单(form-data)的场景 下面,我们来看看webhook服务的两边各有什么特点。 provider(提供者) **谁来当?**通常是 “被触发动作的系统”(比如企业微信消息推送、GitHub、钉钉机器人等)。 做什么? 提供一个 “专属的 webhook 地址”(就是https://qyapi.weixin.qq.com/...?key=xxx 这类 URL),作为接收请求的 “入口”; 定义 “规则”:比如请求必须用什么格式(JSON / 表单)、需要带什么验证信息(比如你的 key)、支持触发哪些动作(比如企业微信只支持 “推送消息”,GitHub 支持 “代码提交通知”); 收到合法请求后,执行预设动作(比如企业微信把消息推到群里,GitHub 把代码更新信息发给你)。 举例:企业微信就是典型的 “webhook 提供者”—— 它给了你带 key 的 URL,规定了必须发 JSON 格式的请求,并且收到后会执行 “推送消息” 的动作。 caller(调用者) **谁来当?**通常是 “主动触发动作的一方”(比如本地电脑、服务器脚本、第三方工具等)。 做什么? 知道提供者的 webhook 地址和规则(比如 “必须用 JSON 格式,要带 key”); 在需要的时候,按照规则构造请求并发送(比如你用 curl 命令发消息内容); 目的是 “让提供者执行某个动作”(而不是向提供者要数据)。 举例:你用 curl 命令发送请求时,你的本地电脑就是 “webhook 调用者”—— 你按照企业微信的规则发了 JSON 请求,目的是让企业微信执行 “推送消息” 的动作。 ...

September 28, 2025 · 1 min · 139 words · Jamaisvu