redis实现延时任务

484 阅读3分钟

在做游戏开发的时候遇到过这样的需求:

  • 设计一个家园功能。家园包含了建筑。
  • 建筑可以产出和升级(都是玩家手动触发,会消耗一定的道具),随着建筑等级的提高。物品产出的质量越稀有。产出低端物品的时间会缩短
  • 在产出过程中可以花费钻石进行加速
  • 升级过程中不可产出。

分析需要完成的主要功能:

  • 玩家点击升级和产出过后更新完成的时间戳(即当前时间+产出/升级时间),同时给标记为升级/产出状态,并入库。
  • 服务器端定时扫描在线玩家的家园信息。选出符合状态的建筑
  • 用建筑升级/产出完成的时间戳和当前时间戳进行比较。如果小于当前时间戳,更新建筑状态。推送给客户端。

技术选型

** 1、基于DelayQueue或定时任务,存在内存中,数据不可靠。实现简单 2、基于定时任务加mysql的形式,数据可靠。实现简单,但是实现任务的延迟很高。 定时任务每隔一段时间就去扫描表里面满足状态的任务,然后拿出来执行。 因为是对线上的数据量很大,并且只执行查询,所以一般是操作从库。 每个定时任务都会扫描整张表,效率低下。 主从同步存在延迟。 因此对于游戏来说。不满足效率 3、基于定时任务结合redis的方式,数据较可靠,高吞吐量高效率。 ** 生产者代码

package com.hoolai.timer.task.delay.tasks;

import com.hoolai.timer.task.delay.core.RedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;

import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description:
 * @author: Ksssss(chenlin @ hoolai.com)
 * @time: 2020-03-31 11:47
 */

@Component
public class DelayTaskProducer {
    @Autowired
    private JedisProvider client;
    @Autowired
    private RedisConfig config;


    @PostConstruct
    public void start() {
        ExecutorService executor = Executors.newFixedThreadPool(6);
        for (int i = 0; i < 6; i++) {
            executor.execute(new TaskProducer());
        }
    }

    class TaskProducer implements Runnable {
        private final Jedis jedis;
        private final String[] keys;
        private final long timeStamp = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(111);
        private final AtomicInteger i = new AtomicInteger(0);

        public TaskProducer() {
            this.jedis = client.provider();
            keys = config.keys();
        }

        @Override
        public void run() {
            int value = i.get();
            try {
                while (value < 3000) {
                    int pos = value % 2;
                    String key = keys[pos];
                    jedis.zadd(key, System.currentTimeMillis() + value, "" + i);
                    value = i.incrementAndGet();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                jedis.close();
            }
        }
    }
}

消费者代码

package com.hoolai.timer.task.delay.tasks;

import com.hoolai.timer.task.delay.core.ConsumerThreadPoolExecutor;
import com.hoolai.timer.task.delay.core.RedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Jedis;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description:
 * @author: Ksssss(chenlin @ hoolai.com)
 * @time: 2020-03-28 17:57
 */

@Component
public class DelayTaskConsumer {

    @Autowired
    private JedisProvider provider;
    @Autowired
    private RedisConfig config;

    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    private String[] keys;
    private ConsumerThreadPoolExecutor executorEvent;
    private ConsumerThreadPoolExecutor executor;
    private AtomicInteger pos = new AtomicInteger(-1);

    @PostConstruct
    public void start() {

        this.keys = config.keys();
        executorEvent = new ConsumerThreadPoolExecutor("handler", keys.length);
        executor = new ConsumerThreadPoolExecutor("consumer", keys.length);
        scheduledExecutorService.scheduleWithFixedDelay(new DelayTaskTrigger(), 1, 1, TimeUnit.SECONDS);
    }

    class DelayTaskTrigger implements Runnable {

        private final Jedis client;

        public DelayTaskTrigger() {
            client = provider.provider();
        }

        @Override
        public void run() {
            CountDownLatch countDownLatch = new CountDownLatch(keys.length);
            for (int i = 0; i < keys.length; i++) {
                executorEvent.submit(new DelayTaskEvent(keys[i], countDownLatch));
            }

            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class DelayTaskEvent implements Runnable {
        private final String key;
        private final Jedis client;
        private final CountDownLatch countDownLatch;

        public DelayTaskEvent(String key, CountDownLatch countDownLatch) {
            this.key = key;
            client = provider.provider();
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            int retry = 0;
            Set<String> ids;
            while (retry < 3) {
                try {
                    while (true) {
                        ids = client.zrangeByScore(key, 0, System.currentTimeMillis(), 0, 100);
                        if (CollectionUtils.isEmpty(ids)) {
                            break;
                        }
                        client.zrem(key, ids.toArray(new String[ids.size()]));
                        executor.submit(new DelayTaskHandler(ids));
                    }
                    break;
                } catch (Exception e) {
                    e.printStackTrace();
                    retry++;
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException subE) {
                        subE.printStackTrace();
                    }
                }
            }
            client.close();
            if (client.isConnected()) {
                client.disconnect();
            }
            countDownLatch.countDown();
        }
    }

    class DelayTaskHandler implements Runnable {
        private final Set<String> udids;

        public DelayTaskHandler(Set<String> ids) {
            this.udids = ids;
        }

        @Override
        public void run() {
            for (String id : udids) {

                //todo
                System.out.println(id + "升级完成");
            }
        }
    }

    private String next() {
        if (pos.incrementAndGet() == keys.length) {
            pos.set(0);
        }
        return keys[pos.get()];
    }
}

git地址