一个小功能的改造,结果到处都是redis的身影

前提:接到任务。最近有个三方回调接口超时,这个接口要做的就是对已接入系统的子公司的数据处理,后面的公司会接入更多,接口也会更慢。这个项目本身比较简单,所以就是一个单体项目,也没有引入mq。但也是部署多个节点。

简单的功能优化就分为下面两个:

1:先把每个节点利用起来(之前是接口请求所在节点循环每个公司做数据处理),然后异步执行,不要让回调接口等任务执行完再返回,并且立即返回成功。不太愿意为了这一个功能再去引入mq(申请资源,走流程,运维什么的)

2:开始执行接口和执行结束要收到成功或者失败的消息。

功能1的实现,虽然没有mq,但是有redis。这里就使用了redis做广播消息,每个节点收到消息后,同时去 list 里循环拿数据,放入本地线程池执行。(也可以用redis的 stream 实现)

redis 广播代码比较简单就不解释了。

功能2就麻烦点了。因为多节点,多线程,总体来说得有一个地方记录当前的状态。那就用现成的redis工具 redisson 记录状态呗,于是就有了方式1.

方式1:当时想着,redis记录要有个超时时间,开始执行任务就记录一下 +1 ,退出任务就 -1。是不是想到了弄一个 flag 在redis,然后对它进行操作。但是我当时呢想到懒得自己写,用现成的轮子就行了,就是:RReadWriteLock。redisson 的读写锁。(redisson普通的锁不行,因为只有当前线程可重入)

实现方式就是,开始执行任务时加读锁,因为读锁共享,线程结束先解读锁,然后 尝试加写锁 用 tryLock,由于读写互斥,写锁尝试加锁成功就表示没有读锁了,自然就是所有任务已经结束。

代码我都写了一半,才想通自己犯傻了,如果任务比线程多,正在执行的任务同时结束,还没来得及从队列取下一个任务,这时读锁还是全部解锁了,会误判全部任务结束。比如下面的模拟代码,把线程池改小,会出现多次 下面的这句输出语句:

写锁是否加锁成功: true
@Test
    public void testLock() throws InterruptedException {
        //这里修改线程数
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("test_lock");
        RLock rlock = rwLock.readLock();
        RLock wlock = rwLock.writeLock();
        System.out.println("开始");

        for (int i = 0; i < 6; i++) {
            executorService.execute(() -> {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                rlock.lock(10, TimeUnit.SECONDS);
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                rlock.unlock();

                boolean b = wlock.tryLock();
                if (b) {
                    wlock.unlock();
                }

                System.out.println("写锁是否加锁成功: " + b);
            });
        }

        LockSupport.park();
    }

上面的问题,就需要多一个步骤,就是再判断一下线程池的队列和redis的list 是不是还有任务。有的话就不算结束!

但是感觉这样写太麻烦了,所以放弃,下有一个思路

方式2:redis中记录一个falag,广播前就先把flag设置为任务总数。然后每个线程执行完后,flag 减一。不能直接l用下面的两行代码,非原子性(否则加分布式锁)

  int coun = redis.get("key"); 

 redis.set(count - 1);

直接使用 redis 的 decr 命令。当flag <= 0 就可以发送消息了。

方式3:服务宕机怎么办?用上面方法的话,一直都不会等于0,导致一直收不到消息。

所以这时我想着是做一个心跳,后台线程定时发送心跳或者每完成一个任务就发送一次心跳。

然后在redis中记录每个节点的 ip 和 端口,以及每个节点完成的任务情况,当前节点全部任务结束后就去检查下是否每个节点都完成,比如设置等待30秒超时,节点完成任务就每5秒循环一次看下是不是所有节点都完成了任务,直到所有节点已完成或者已下线或者到了超时时间,就再用redis做个标记,记录当前节点已发送消息,其他节点发送前判断一下就没必要发消息了。任务重新开始时清理这个标记,最好也设置一个长一点的超时时间,双保险。每个在线的节点都需要做前面描述的判断,这样除非全部下线,,,否者总有一个能有机会发出这个消息。

还需要小心一点的就是,尽量不要用一个json的字符串去保存节点的信息,否则就又出现原子性问题(可以加锁解锁),我想着直接存一个hash,每个节点的每个线程各自更新数据,

所以用 redis 的 hsh 做成大概下面这种数据结。这样每个线程只更新自己的数据,就可以避免并发导致的数据混。

最后发送消息内容展示为:xx数据处理完成,成功xx个,失败xx个,失败原因。。。其中xx节点下线导致xx卫执行。

其他细节,接口和任务的幂等,防重,redis中的数据及时清理等就不写了。

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值