springboot2 + docker 线上运行时 redis setnx 两个java进程都获取到了锁?

作者站长头像
站长
· 阅读数 10

发生企业级灾难代码执行描述:1、用户的余额是通过后台,在每日0点开始进行入账(归入余额)。2、使用@Scheduled作为定时脚本3、定时任务需要获取到 redis setnx 锁的权限,才能继续执行下面的代码

@Component      // 1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class OrderMoneyToMerchantMoney {
    @Resource
    private JedisPoolUtil jedisPoolUtil;

    @Scheduled(cron = "${scheduled.timedTaskScheduled:*/5 * * * * ?}")
    public void configureTasks() {
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
        selectDb();
    }

    @Transactional  // 事务操作
    public void selectDb() {
      ……
      for (Orderlist temp : allList) {
      // 获取锁权限
      boolean setNx = JedisPoolUtil.setNx("D1MoneyToMerchantD0Money:" + temp.getOrderid(), 60 * 10); // 锁10分钟
      if (!setNx) {
          continue;
      }
      ……
    }
}

使用的util工具是

@Component
public class JedisPoolUtil {
    
  private RedisTemplate<String, Object> redisTemplate;

  public RedisTemplate<String, Object> getRedisTemplate() {
      return redisTemplate;
  }

  public boolean setNx(String key, long time) {
    try {
      Boolean lock = getRedisTemplate().opsForValue().setIfAbsent(key, 1, time, TimeUnit.SECONDS);
      if (lock) {
        // 加锁成功
        return true;
      } else {
        // 没有分布式锁,等待,进入自旋
        return false;
      }
      } catch (Exception e) {
        e.printStackTrace();
        return false;
      }
    }
}

redis执行逻辑:在程序初始化时:创建0~15库的redis连接,并放到map中使用时,取出map中的一个值,加载到工具类的redisTemplate当中

JedisPoolUtil           redisUtil    = jedisPoolUtil.setRedisTemplate(jedisPoolUtilConfig.getRedisTemplateByDb(10));

服务器:一台 centos7.9docker:docker19.03.13 + docker composerjava:运行在2个docker容器中(使用docker composer编排)redis:运行在docker容器中 redis_6.2.6版本 使用docker官方镜像 redis:6.2.6

灾难描述:1、用户余额在昨日,突然2个在docker中的java进程均获取到了 redis setnx 锁-->导致用户余额重复增加2、商户余额已经提现,平台金额亏损。

发现原因:1、猜测redis使用的spingboot2 自动注入的类

@Resource
private JedisPoolUtil jedisPoolUtil;

而不是

JedisPoolUtil redisUtil = jedisPoolUtil.setRedisTemplate(jedisPoolUtilConfig.getRedisTemplateByDb(10));

导致多个java进程(可能)连接到不同的redis库,所以不同redis库在setnx时,可以获取到锁。2、猜测是否跟使用docker redis+docker java 有关-->本地启动2个jar+本地redis测试结果:只能1个java进程获取到redis setnx锁,另一个获取不到-->本地启动1个jar+线上启动2个docker java+本地连接线上redis测试结果:只能1个java进程获取到redis setnx锁,另一个获取不到测试结果:均正常

3、查看docker redis状态执行:docker ps | grep redis结果:redis运行时长均在3week以上,没有提示docker redis中断重启

4、查看宿主机器上docker redis日志在日志中查看到并无错误日志,日志为空

5、查看宿主机器上message内核日志没有错误

解决办法:1、猜不到是什么原因,将使用mysql for update互斥锁,进行锁表更新,如果未更新则回滚。上线后,运行正常 测试正常。2、怀疑redis连接库的原因 使用JedisPoolUtil redisUtil = jedisPoolUtil.setRedisTemplate(jedisPoolUtilConfig.getRedisTemplateByDb(10));代替

回复
1个回答
avatar
test
2024-07-05

answer image 你们是一个库一个连接?能确保两个应用抢占同一个key的时候,是同一个库连接吗?

回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容