《Java 消息中间件·可靠消息与消费治理》13.2 SASL/ACL 与传输加密
配额管的是速率,认证和授权才管得住身份。本篇在本机单节点上起一个 SCRAM-SHA-256 加 ACL 的 broker,贴出未授权读取的 TopicAuthorizationException、错误密码的 SaslAuthenticationException、以及 topic 放行但 group 被拒的原始异常;并用同一份生产者对照 PLAINTEXT、SSL、SASL 的握手与稳态延迟。
tag
配额管的是速率,认证和授权才管得住身份。本篇在本机单节点上起一个 SCRAM-SHA-256 加 ACL 的 broker,贴出未授权读取的 TopicAuthorizationException、错误密码的 SaslAuthenticationException、以及 topic 放行但 group 被拒的原始异常;并用同一份生产者对照 PLAINTEXT、SSL、SASL 的握手与稳态延迟。
客户端配额让吵闹的邻居变慢,而不是被踢掉。本篇在本机单节点上把 producer_byte_rate 配成 1024 B/s,用同一个 Java 生产者跑配额前后两组对照:385 ms 变成 15943 ms,客户端 produce-throttle-time 平均值 782 ms,16 条消息全部 ack、没有一条被拒;再给出按 client-id、按 user、按前缀与物理隔离的多租户方案。
fetch.min.bytes、fetch.max.wait.ms、max.poll.records 三个参数一起调时,批次不是越大越好。本机用 20 万条固定积压跑四组参数,1MB 组比 100 条组快 1.4 倍,而 8MB 组因为超过单次 fetch 上限,每次等待 1 秒、吞吐掉到二十四分之一。附 poll 延迟 p99 与调参顺序。
commitAsync 返回 void,失败既不抛异常也不改返回值;错误要么被丢弃,要么只能等下一次 poll 才在回调里出现。本机复现了被踢出组后异步提交失败、committed 仍为空的全过程,并对照 commitSync 的异常,附回调线程、阻塞与重入的实测。
一个消费者跑满一核、lag 却不降时,加线程有三种写法:每线程一个消费者、一个拉取线程加共享工作池、按分区亲和路由。本机用 4 分区 16 万条消息实测三种模型,贴出吞吐、线程数与逆序次数;结论是保序不贵、跨线程交接贵,池化模型必须把提交点压在处理完成之后。
消息里只放 URI、SHA-256 与长度,内容进对象存储,这是大消息的常规出路,但它把一次写入拆成两个系统,失败窗口也随之翻倍。本篇给出最小 claim-check 实现,实测文件被删与校验和不符两条失败路径,演示重试、兜底主题与位移提交的先后顺序。
同一批事件,gzip、snappy、lz4、zstd 的压缩比可以差两倍以上,CPU 可以差五倍;批次太小或数据本身不可压时,压缩还会让字节数变大。本篇用同一份真实结构的事件跑四种算法,贴出压缩后字节、发送耗时与 broker 落盘字节,并给出负收益判据。
一条消息要过四层尺寸参数:客户端 max.request.size、批量 batch.size、broker 与 topic 的 message.max.bytes、消费端 fetch 上限。本篇在本机逐级放大消息,记录每一档在哪一层被拦、异常原文与错误码,并实测压缩如何让 2 MiB 消息穿过 1 MiB 的 broker 上限。
分区数是 key 映射公式里的模数,改一次就有近一半的 key 要换分区。本篇用 20 000 个 key 算出 4→8、4→5、8→12 的落点变化比例,再跑一遍 6 000 条、3 000 个 key 的桥接迁移与双写切换,验证顺序不丢、条数对得上,并给出位移重置与回滚的检查清单。
单调递增的 id 在 Kafka 默认分区器下其实很均匀,12 000 条实测最大分区只有平均值的 1.04 倍;真正打满一个分区的是范围分区器、单个热 key 和错误的加盐。本篇用六组 12 000 条实验量出 12 倍、10.9 倍、2.85 倍的分布,并给出盐值与分区的落点校验方法。
同一条消息在单分区里排队,才轮得到顺序。本篇把全局、分区、业务键三种粒度拆开,用 200 条同 key 消息在 in-flight 1/2/5 下各跑三遍,再用暂停 broker 四秒触发超时重试:幂等组 9 次运行全部 0 乱序、0 重复,非幂等组在途 5 时 20 处逆序、5 条重复。
最终一致不是一个形容词,而是一组可以画在面板上的数字。本篇给每条事件带上产生时间戳,在下游落库时计算端到端延迟,本机跑出基线 P50/P95/P99 与最长窗口,再注入 10% 的慢路径看尾延迟怎么变;最后写一个对账任务,故意漏发两条、重发一条,验证它能把条数、金额和重复同时报出来。
补偿会被重试、重放和并发再次执行;补偿本身不幂等时,一次失败会变成两次超还。本篇在 libraryd 库存场景里分别跑无条件反向更新、WHERE 守卫和补偿令牌三种写法,贴出同一条补偿执行两次的真实差异,并演示补偿服务持续不可用时进入人工队列、恢复后幂等重放的完整路径。
Saga 把跨服务的长事务拆成一组可补偿的本地动作。本篇用 libraryd 借阅场景实现一个可运行的编排式 Saga:创建借阅、预留库存、冻结额度三步,在第二步和第三步各失败一次,贴出本机真实的补偿轨迹与最终状态,并给出状态机表、崩溃恢复分析与落地清单。
集中式 schema 管理能给你版本查找、兼容性门禁和跨语言契约,代价是多一个服务、多一次依赖和一套客户端耦合。本篇不引入任何第三方 registry 客户端,只用消息头、schema 文件和 CI 脚本,搭一个能跑的最小自管方案,并给出该不该上 registry 的判定表。
加字段、删字段、改类型、改字段名,四种变更分别能不能兼容?本篇用同一份 JSON payload,让老消费者读新消息、新消费者读旧消息,八个方向逐个跑,贴出 ClassCastException 与 NullPointerException 的原文,并给出双写双读的发布顺序、回滚判据与七条上线前检查清单。
事件是已经发生的事实,不是请求也不是状态快照。本篇先划清事件体该放什么、不该放什么,再给出 libraryd 六个事件的 record 定义,逐字段说明它为什么存在、缺了会怎样;最后用一次本机实测说明事件 ID 与增量字段如何决定重放是否可重复。
只存当前状态,你能回答「现在是什么」,回答不了「为什么变成这样」。事件溯源把状态变更本身作为唯一事实来源,状态由重放推导。本篇用纯 JDK 实现最小事件存储,追加 8 个库存事件后从 0 重放得到 stock=9、reserved=12,再用 version=4 的快照加 4 条增量重放,两条路径结果逐字段相等;并实测乐观并发与重复 eventId 两种拒绝。
日志挖掘读数据库的变更日志,查询轮询读一张带水位列的表,两者在 op 语义、删除可见性和业务侵入上完全不同。本篇手写轮询版 CDC 实测 c/u/d 三种变更,用同一毫秒的两行证明单列时间水位会漏数据,再用快照前取水位与快照后取水位两种顺序,说明衔接为什么只有前者能得到一致状态;Debezium 仅做架构对照并标注未在本机执行。
先写库再发消息,会在进程崩溃时留下「有业务行、无事件」的孤儿数据。本篇用同库同事务的 outbox 表消除这个窗口,给出轮询发布器的完整实现,并在本机用嵌入式数据库真实 kill -9:写入 5 笔后崩溃、发送 3 条未标记就崩溃,重启补发后业务行 5、outbox 行 5、Kafka 8 条记录 5 个事件 ID,逐条对上。