一、概述:

在分布式环境下,定时任务的幂等性问题需要考虑多个节点之间的数据一致性和事务处理。

一种解决方法是使用分布式锁来保证同一时间只有一个节点能够执行该任务。具体实现可以使用Redis或Zookeeper等分布式协调工具提供的分布式锁功能。

另一种解决方法是使用消息队列来保证任务的幂等性。当一个节点执行任务时,先将任务发送到消息队列中,然后等待其他节点确认任务已经执行完毕后再进行后续操作。如果有节点出现故障或者网络异常导致任务未能成功执行,则可以重新发送任务并等待其他节点确认。

二、示例

如下图,有三台机器同时启动定时任务,将数据保存到Redis中,如何保证数据的幂等性?

Springboot 定时任务,分布式下幂等性如何解决_幂等性


解决方法–Redission分布式锁:

  1. 在启动定时任务时,获取分布式锁,保证只有一个线程进入
  2. 在获取锁之后,锁定10秒
  3. 然后执行业务
  4. 业务执行完成后,释放分布式锁
@Scheduled(cron = "*/10 * * * * ? ")    
public void uploadSeckillSkuLatest3Days() {
    // 重复上架无需处理
    log.info("上架秒杀的商品...");

    // 分布式锁(幂等性)
    RLock lock = redissonClient.getLock(SeckillConstant.UPLOAD_LOCK);
    try {
        lock.lock(10, TimeUnit.SECONDS);
        // 上架最近三天需要秒杀的商品
        seckillService.uploadSeckillSkuLatest3Days();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

上架商品时,判断redis是否有该商品,没有才上架

Boolean hasKey = redisTemplate.hasKey(key);
 if (!hasKey) {
 }

全部代码

private void saveSessionInfos(List<SeckillSessionWithSkusTO> sessions) {
        if (!CollectionUtils.isEmpty(sessions)) {
            sessions.stream().forEach(session -> {
                // 1.遍历场次
                long startTime = session.getStartTime().getTime();// 场次开始时间戳
                long endTime = session.getEndTime().getTime();// 场次结束时间戳
                String key = SeckillConstant.SESSION_CACHE_PREFIX + startTime + "_" + endTime;// 场次的key

                // 2.判断场次是否已上架(幂等性)
                Boolean hasKey = redisTemplate.hasKey(key);
                if (!hasKey) {
                    // 未上架
                    // 3.封装场次信息
                    List<String> skuIds = session.getRelationSkus().stream()
                            .map(item -> item.getPromotionSessionId() + "_" + item.getSkuId().toString())
                            .collect(Collectors.toList());// skuId集合
                    // 4.上架
                    redisTemplate.opsForList().leftPushAll(key, skuIds);
                }
            });
        }
    }