在现代微服务架构中,Redis 和 Kafka 几乎成了标配。Redis 解决了高并发下的快速读写,Kafka 则处理复杂的消息流。然而,很多团队在引入这两者之后,反而面临新的挑战:缓存与消息链路中的数据一致性难以保障。特别是在订单、库存、支付等核心链路中,一次数据不一致,就可能带来业务异常甚至损失。
这篇文章就来详细讲讲,如何通过合理的架构设计与工程实践,构建一条“可靠、不乱序、最终一致”的 Redis + Kafka 消息链路。
我们时常会听到以下对话:
“Redis 里的数据是新的,数据库却是旧的?”
“Kafka 消息重复消费怎么办?”
“怎么知道 Redis 缓存是旧的,还是还没更新?”
这些问题归根到底都是 “异步架构下的一致性” 问题。在传统单体应用中,一致性可以靠事务来保障,但分布式系统天然缺乏全局事务机制,只能通过设计“最终一致性”链路来达成目标。
我们先来明确一下 Redis 和 Kafka 各自的职责:
这两者组合非常强大,但也很容易踩坑,比如:
// 示例:更新订单状态
await updateOrderStatusInDB(orderId, newStatus);
await deleteRedisCache(`order:${orderId}`); // 第一次删
setTimeout(() => {
deleteRedisCache(`order:${orderId}`); // 第二次删,兜底
}, 500);
原理:删除缓存比写数据库晚,避免“先删缓存后写数据库”导致的并发覆盖问题;第二次删除是为了防止并发读写时出现“缓存重建脏数据”。
// 示例:订单支付成功
await updateOrderPaidInDB(orderId);
await kafkaProducer.send({
topic: 'order-paid',
messages: [{ key: orderId, value: JSON.stringify({ orderId, paid: true }) }]
});
原理:数据库是系统的最终状态源,Kafka 是事件传播的中间件。业务更新后立即发送消息,通知其他系统处理(发货、账务等)。
我们构造一个简化的支付场景:用户支付成功 → 更新订单 → 删除缓存 → 发 Kafka 消息 → 消费消息重建缓存。
const redis = require('redis');
const { Kafka } = require('kafkajs');
const client = redis.createClient();
const kafka = new Kafka({ clientId: 'svc-order', brokers: ['localhost:9092'] });
async function handlePayment(orderId) {
// 写数据库
await updateOrderStatus(orderId, 'PAID');
// 删缓存
await client.del(`order:${orderId}`);
// 发送 Kafka 事件
const producer = kafka.producer();
await producer.connect();
await producer.send({
topic: 'order-paid',
messages: [{ key: orderId, value: JSON.stringify({ orderId }) }],
});
await producer.disconnect();
}
async function startConsumer() {
const consumer = kafka.consumer({ groupId: 'order-process-group' });
await consumer.connect();
await consumer.subscribe({ topic: 'order-paid', fromBeginning: false });
await consumer.run({
eachMessage: async ({ message }) => {
const { orderId } = JSON.parse(message.value.toString());
// 查询数据库,重建缓存
const order = await fetchOrderFromDB(orderId);
await client.set(`order:${orderId}`, JSON.stringify(order));
},
});
}
Q1:为什么不使用强一致数据库加事务来解决?
A1:强一致事务在分布式下代价太高,响应慢且扩展性差。而异步一致链路通过分布式事件补偿的方式,更灵活可控。
Q2:Redis 里的数据怎么防止被篡改?
A2:可通过设置 key 的过期时间、使用签名校验、Hash 存储结构来增强 Redis 的防护性。
Q3:Kafka 消息重复消费会不会导致脏数据?
A3:只要消费逻辑是幂等的(如:更新数据库某字段值、记录是否处理过),就不会造成脏数据。
Redis 和 Kafka 各自解决了系统的两个核心问题:高性能与解耦。但只有把它们合理组合、通过延迟双删、幂等消费、补偿重试等机制,才能真正构建一条 最终一致的高可用消息链路。这不仅是技术的选择,更是一种系统工程思维。
在未来,如果你负责的系统需要面对高并发、高一致、高可用的挑战,这套方案值得你认真参考、反复打磨。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。