import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class KafkaConsumer {
@Autowired
private AsyncKafKaTask asyncKafKaTask;
@Autowired
private StringRedisTemplate redisTemplate; // 注入 Redis 模板
// 锁的 Key 前缀
private static final String LOCK_KEY_PREFIX = "scada:task:lock:";
@KafkaListener(topics = {"scada"}, groupId = "${random.id}")
public void onMessage(List<ConsumerRecord<String, String>> records) {
// 1. 获取当前时间
LocalDateTime now = LocalDateTime.now();
int hour = now.getHour();
// 2. 构造 Redis Key,按小时隔离,例如:scada:task:lock:2023102710
// 这样每个小时的锁都是独立的,互不干扰
String lockKey = LOCK_KEY_PREFIX + now.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHH"));
// 3. 尝试获取锁 (setIfAbsent 即 SETNX 操作)
// 设置过期时间为 70 分钟,防止极端情况下锁无法释放(虽然按小时逻辑不太可能发生)
Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 70, TimeUnit.MINUTES);
if (Boolean.TRUE.equals(isLocked)) {
// --- 拿到锁,说明我是这一小时内第一个来处理消息的 ---
log.info("捕获到整点任务窗口,开始执行每小时任务...");
try {
// 执行业务逻辑
asyncKafKaTask.consumeTask(records);
log.info("每小时任务执行成功");
} catch (Exception e) {
log.error("每小时任务执行失败", e);
// 注意:这里可以选择是否删除锁。
// 如果希望下次重试,可以删除锁;如果希望这一小时内不再重试,则保留锁直到过期。
// redisTemplate.delete(lockKey);
}
} else {
// --- 没拿到锁,说明这一小时内已经有人执行过了 ---
log.debug("本小时内任务已执行,跳过当前消息。");
// 直接返回,不抛出异常,让 Kafka 正常提交偏移量,避免死循环重试
return;
}
}
}

01-15
1857
1857
12-16
1442
1442
10-06
1626
1626

被折叠的 条评论
为什么被折叠?



