面试题答案
一键面试1. 幂等性处理
- 技术手段:在消费端实现业务逻辑的幂等性。例如,如果消息是用于更新数据库某条记录的操作,对于更新操作可以设计为无论执行多少次,结果都是一样的。以更新用户积分场景为例,假设消息内容是给用户增加10积分。可以先根据用户ID查询当前积分,然后计算新积分并更新,这样即使重复消费消息,积分增加的结果也是一致的。
- 实现思路:对于写操作,将操作设计成可重复执行且结果不变。对于数据库操作,可以使用
INSERT ... ON DUPLICATE KEY UPDATE
语句(在MySQL中),这样如果插入数据的主键或唯一索引已存在,就执行更新操作,避免重复插入导致数据不一致。在代码层面,对于可能重复执行的业务逻辑,先进行状态判断,只有满足特定条件才执行实际操作。
2. 消息去重表
- 技术手段:创建一张消息去重表,该表至少包含消息的唯一标识字段(如消息ID)。当消息到达消费端时,先根据消息ID查询去重表。如果该消息ID已存在,说明该消息已被消费过,直接丢弃;如果不存在,则插入该消息ID到去重表,并正常处理消息。
- 实现思路:在消费端代码中,每次收到消息,先调用数据库查询语句(如
SELECT COUNT(*) FROM deduplication_table WHERE message_id = 'xxx'
)判断消息是否已处理。如果查询结果为0,则执行插入操作(如INSERT INTO deduplication_table (message_id) VALUES ('xxx')
),然后处理业务逻辑;如果查询结果不为0,直接忽略该消息。同时要注意对去重表的维护,避免数据量过大影响查询性能,可以定期清理已过期的消息记录。
3. 分布式缓存去重
- 技术手段:利用分布式缓存(如Redis)来实现消息去重。当消息到达消费端时,使用Redis的
SETNX
命令(SET if Not eXists),以消息ID作为键,任意值作为值进行设置。如果SETNX
返回1,表示该消息ID首次被设置,即该消息未被消费过,此时正常处理消息;如果返回0,表示该消息ID已存在,即消息已被消费过,直接丢弃。 - 实现思路:在消费端代码中,通过Redis客户端库调用
SETNX
方法。例如在Java中使用Jedis库,代码如下:
Jedis jedis = new Jedis("localhost", 6379);
String messageId = "your_message_id";
if (jedis.setnx(messageId, "processed") == 1) {
// 处理消息业务逻辑
jedis.del(messageId); // 处理完成后删除键,避免键堆积
} else {
// 消息已处理,丢弃
}
jedis.close();
这种方式利用了Redis的单线程特性和 SETNX
命令的原子性,确保在高并发场景下消息去重的准确性。同时,处理完消息后及时删除Redis中的键,防止键过多占用内存。
4. 消费端ACK机制优化
- 技术手段:在消息队列的消费端,确保ACK(确认消息已接收)机制的可靠性。当消费端成功处理消息并完成所有业务逻辑后,再发送ACK给消息队列。如果在处理消息过程中发生故障,消息队列不会收到ACK,会认为消息未被成功消费,从而进行重试。
- 实现思路:以RabbitMQ为例,在消费者代码中设置手动确认模式(
channel.basicConsume(queueName, false, consumer)
)。在处理消息的回调方法中,完成所有业务逻辑后调用channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false)
发送ACK。这样即使消息重复投递,只要消费端未成功处理并发送ACK,消息队列就会持续重试,直到消费端成功处理并确认,保证消息不会因为消费端故障而丢失或重复处理导致数据不一致。同时,消费端要做好故障恢复和重试逻辑,确保在故障恢复后能继续正确处理未确认的消息。