如何保证消息的有序性
通过轮询所有队列的方式来确定消息被发送到哪一个队列(负载均衡策略)。订单号相同的消息会被先后发送到同一个队列中, 在获取到路由信息以后,会根据算法来选择一个队列,同一个 OrderId 获取到的肯定是同一个队列。 两条描述的是”同 key 路由到同队列”这一有序性基石(原文第 1 条的”轮询”表述与第 2 条的哈希路由略有矛盾:保序场景必须用 key 哈希而非轮询)。本文把有序性问题拆成三段链路(生产-存储-消费)各自的乱序来源与对策,并给出”需要多有序”的分级模型——多数业务其实不需要全局序。 先分级:你要哪种有序 全局有序:所有消息全序——单分区/单队列,牺牲并行度,极少需要 分区有序(业务键有序):同一订单/用户的事件有序——90% 需求的真实答案(同 key 同队列) 无序:只要最终都处理(日志采集类)——最大吞吐 分级决定成本:全局序=单点瓶颈;分区序=key 哈希+消费端单线程/保序处理;无序=全并行。先问业务”谁和谁之间需要序”,再选方案。 乱序来源一:生产端 多线程并发发送同 key 消息:异步发送...
自己如何实现消息队列
大体上的设计是由一条线程 1 执行从等待列表中获取任务插入任务队列再由线程池中的线程从任务队列中取出任务去执行. 添加一条线程 1 主要是防止在执行耗时的任务时阻塞主线程.当执行耗时任务时,添加的任务的操作快于取出任务的操作, 当任务队列长度达到最大值时, 线程 1 将被阻塞, 等待线程 2,3… 从任务队列取出任务执行。 这个”生产者线程 + 有界队列 + 消费者线程池”的设计是进程内消息队列的最小可行形态(本质是 BlockingQueue 模式)。但”自己实现消息队列”这个问题在面试与设计中通常指向更高一层:从零设计一个跨进程 MQ 需要哪些核心模块。本文两层都讲:进程内实现给出可运行代码,跨进程设计给出模块清单与关键取舍。 第一层:进程内队列(原文设计的完整实现)class InProcMQ<T> { private final BlockingQueue<T> queue; // 有界队列=背压来源 private final ExecutorService consumers; private fi...
消息的堆积解决思路
如果还没开始投入使用 Kafka,那应该在设计分区数的时候,尽量设置的多点(当然也不要太大,太大影响延迟,具体可以参考我前面提到的文章),从而提升生产和消费的并行度,避免消费太慢导致消费堆积。 增大批次瓶颈在消费吞吐量的时候,增加批次也可以改善性能 增加线程数如果一些消费者组中的消费者线程还是有 1 个消费者线程消费多个分区的情况,建议增加消费者线程。尽量 1 个消费者线程对应 1 个分区,从而发挥现有分区数下的最大并行度。 分区数预设计、增大批次、增加线程三条是消费侧提速的正确动作,但堆积治理的完整方法论还缺三块:堆积的根因分类(消费慢 vs 生产突增 vs 消费者故障)、紧急处置预案(积压已发生怎么办)、以及预防体系(监控与容量)。本文补全。 根因分类:先诊断再动手 根因 信号 对策方向 消费逻辑慢 单条处理耗时长(含下游 RPC/DB 慢) 优化消费逻辑/批量/并行 消费者数不足 分区数 > 消费者数,或线程<分区 扩消费者/线程(原文第三条) 生产突增 生产速率曲线尖峰(活动/重试风暴) 上游...
消息的幂等性解决思路
查询操作查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select 是天然的幂等操作 删除操作删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个) 唯一索引,防止新增脏数据比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录 Token 机制防止页面重复提交 悲观锁获取数据的时候加锁获取 select * from table_xxx where id='xxx' for update; 注意:id字段一定是主键或者唯一索引,不然是锁表,会死人的 悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用 乐观锁乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。 分布式锁还是拿插入数据的例子,如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的...
消息的重发补偿解决思路
可靠消息服务定时查询状态为已发送并超时的消息 可靠消息将消息重新投递到 MQ 组件中 下游应用监听消息,在满足幂等性的条件下,重新执行业务。 下游应用通知可靠消息服务该消息已经成功消费。 通过消息状态确认和消息重发两个功能,可以确保上游应用、可靠消息服务和下游应用数据的最终一致性。 五条描述的是”本地消息表/可靠消息服务”模式的重发闭环。要把它落地成可靠系统,还需补全:消息丢失的三个环节与各自对策(重发只解决其一)、本地消息表与事务消息两种实现形态、以及重发策略的参数设计。本文补全成完整的可靠性方案。 消息丢失的三环节与对策全景 环节 丢失原因 对策 生产→Broker 发送超时/Broker 宕机 同步发送+重试 / 事务消息 / 本地消息表补偿(本文重点) Broker 存储 未刷盘宕机 / 副本未同步 同步刷盘/同步复制配置(权衡吞吐) Broker→消费 自动 ack 后消费失败 / 重平衡丢消息 手动 ack(处理成功才确认)+ 消费失败重试 重发补偿解决的是第一环节(生产侧...
消息队列的使用场景.
校验用户名等信息,如果没问题会在数据库中添加一个用户记录 如果是用邮箱注册会给你发送一封注册成功的邮件,手机注册则会发送一条短信 分析用户的个人信息,以便将来向他推荐一些志同道合的人,或向那些人推荐他 发送给用户一个包含操作指南的系统通知 注册流程这个例子(写库同步做、发邮件/短信/分析/通知异步化)是 MQ 最经典的教学案例,但它只展示了 MQ 三大价值中的”异步”。本文以该例为起点,补全异步、解耦、削峰三大价值的工程形态,以及”不该用 MQ”的反面清单与引入 MQ 必须配套的三件事。 从注册例子看异步化收益同步链路:注册 = 写库(20ms) + 发邮件(300ms) + 短信(500ms) + 分析(200ms) + 通知(100ms) ≈ 1.1s 响应。异步化后:注册 = 写库 + 发消息(5ms) ≈ 25ms 响应,其余四件事由消费者各自完成。用户体验从 1.1s 到 25ms,就是 MQ 的第一价值:把”用户不必等的事”移出请求路径。 三大价值的工程形态1. 异步(提速):主链路只保留强一致部分,弱一致环节发消...
使用缓存的合理性问题
热点数据,缓存才有价值 频繁修改的数据,看情况考虑使用缓存 数据不一致性 缓存更新机制 缓存可用性 缓存服务降级 缓存预热 缓存穿透 八条要点是缓存评审的 checklist,但每条都只给了名词。本文把八条展开为可判断的准则与可执行的机制:什么样的数据值得缓存(量化判据)、一致性怎么管(更新模式)、可用性怎么保(三防+降级)、以及上线前的预热与穿透治理。 准则一:热点数据才值得缓存(量化判据)缓存的收益 = 命中次数 × 单次回源成本 − (内存成本 + 一致性维护成本)。判据: 读写比:读多写少(如 10:1 以上)才有正收益;写多读少的数据缓存命中率低且频繁失效,纯增加复杂度 访问分布:符合幂律(少数 key 承担多数流量)时缓存效率高;访问均匀分散(如随机主键点查全量数据)则命中率低,缓存价值存疑 回源成本:回源是简单主键查询(毫秒级)还是聚合计算/多表 join(百毫秒级)?后者缓存收益大一个量级 频繁修改的数据:不是不能用,而是换模式——接受短窗口不一致用 TTL 兜底;要求强一致则考虑”更新时主动失效+短 TTL”或干脆不缓存(修改频繁且读...
缓存降级
页面降级在大促或者某些特殊情况下,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级,以达到丢卒保帅; 页面片段降级比如商品详情页中的商家部分因为数据错误了,此时需要对其进行降级; 页面异步请求降级比如商品详情页上有推荐信息/配送至等异步加载的请求,如果这些信息响应慢或者后端服务有问题,可以进行降级; 服务功能降级比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可; 读降级比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景; 写降级比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。 爬虫降级在大促活动时,可以将爬虫流量导向静态页或者返回空数据,从而保护后端稀缺资源。 自动开关降级自动降级是根据系统负载、资源使用情况、SLA等指标进行降级。 超时降级当访问的数据库/http服务/远程调用响应慢或者长时间响应慢,且该服务不是核心服务的话可以在超时后自动降级;比如商...
Redis 为什么是单线程的
因为 CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存或者网络带宽。(以上主要来自官方 FAQ)既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。但这个回答只解决了”为什么敢”,没解决”为什么快”,本文把这两层都拆开讲。 “单线程”到底指什么先澄清概念:Redis 的单线程指的是网络 I/O 解析与命令执行由同一个主线程串行完成(accept 读取请求、解析命令、执行、写回响应都在一个线程里)。它并不是说整个进程只有一个线程——持久化的 fork、异步删除(unlink)、集群同步等都有后台线程参与。Redis 6.0 之后引入的”多线程”,也仅仅是把网络数据的读写和协议解析交给多个 I/O 线程,命令执行依然在主线程串行完成。 为什么单线程反而快直觉上多线程才能榨干 CPU,但 Redis 的场景特殊: 纯内存操作:一次命令执行通常是微秒级的哈希表/跳表查找,CPU 计算量极小,真正的时间花在网络 I/O 和内存带宽上 多路复用:主线程用 epoll(IO multiplexing)...
Redis 持久化机制
Redis 有两种持久化机制: RDBRDB 持久化方式会在一个特定的间隔保存那个时间点的一个数据快照 AOFAOF 持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟 Redis 协议一致,以追加的方式进行保存 Redis 的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。两种方式的持久化是可以同时存在的,但是当 Redis 重启时,AOF 文件会被优先用于重建数据。 RDB/AOF 的定义与”重启优先 AOF”的规则正确,但生产决策还需要四层细节:RDB 的 fork-COW 机制与代价、AOF 三种刷盘策略与重写、混合持久化、以及故障恢复的取舍矩阵。本文补全。 RDB:快照的 fork + 写时复制 触发:save(阻塞,禁用)/ bgsave(后台)/ 配置 save 900 1 等阈值 / 主从全量同步 机制:主进程 fork 子进程,子进程共享父进程内存页(COW copy-on-write):父进程继续服务写请求,写到某页时...















