Node.js 定时任务Demo

在实际项目开发中很多时候都会使用到定时任务的需求,举个最简单的例子,一个用户推荐了另一个用户,我们定一个二十四小时之后的任务,看看被推荐的用户有没有来注册,如果没注册就给他搞一条短信过去。

我们需要借助Redis的键空间消息Redis Keyspace Notifications,它配合 2.0.0 版本之后的 SUBSCRIBE 就能完成这个定时任务的操作了,不过定时的单位是秒。

配置

即使你的 Redis 版本达标了,但是 Redis 默认是关闭这个功能的,你需要修改配置文件来打开它,或者直接在 CLI 里面通过指令修改。这里就说说配置文件的修改吧。

首先打开 Redis 的配置文件,在不同的系统和安装方式下文件位置可能不同,比如通过brew安装的 MacOS 下可能是在/usr/local/etc/redis.conf下面,通过 apt-get安装的 Ubuntu 下可能是在 /etc/redis/redis.conf下,总之找到配置文件。

然后找到一项叫 notify-keyspace-events的地方,如果找不到则自行添加,其值可以是 Ex、Klg 等等。这些字母的具体含义如下所示:

  • K,表示 keyspace 事件,有这个字母表示会往 keyspace@ 频道推消息。
  • E,表示 keyevent 事件,有这个字母表示会往 keyevent@ 频道推消息。
  • g,表示一些通用指令事件支持,如 DEL、EXPIRE、RENAME 等等。
  • $,表示字符串(String)相关指令的事件支持。
  • l,表示列表(List)相关指令事件支持。
  • s,表示集合(Set)相关指令事件支持。
  • h,哈希(Hash)相关指令事件支持。
  • z,有序集(Sorted Set)相关指令事件支持。
  • x,过期事件,与 g 中的 EXPIRE 不同的是,g 的 EXPIRE 是指执行 EXPIRE -
    key ttl 这条指令的时候顺便触发的事件,而这里是指那个 key 刚好过期的这个时间点触发的事件。
  • e,驱逐事件,一个 key 由于内存上限而被驱逐的时候会触发的事件。
  • A,g$lshzxe 的别名。也就是说 AKE 的意思就代表了所有的事件。

结合上述列表我们就能拼凑出自己所需要的事件支持字符串了,在我的需求中我只需要 Ex 就可以满足了,所以配置项就是这样的:

notify-keyspace-events Ex

然后保存配置文件,启动 Redis 就启用了过期事件的支持了。

找的时候注意这里会有三处notify-keyspace-events,两处是注释的,有一处是没有注释值为'',把这里改为Ex即可。

实践

我们先说任务的创造者吧。由于这里 Redis 的事件只会传键名,并不会传键值,而过期事件触发的时候那个键已经没了,你也无法获取键值,加上我的主系统和任务系统是分布式的,所以就把所有需要的信息往键名塞。

如果是循环触发定时任务的话,订阅与监听的redis分开定义,不要引用同一个,不然会报错

主文件 app.js

/**
 * Created by qijun on 2017/11/29.
 * 简单Demo
 */
const restify = require('restify');
const redis = require('redis'); // 这里重新定义一次 与db.js中分开

const server = restify.createServer({
    name: 'demo',
    version: '1.0.0'
});

server.use(restify.plugins.acceptParser(server.acceptable));
server.use(restify.plugins.queryParser());
server.use(restify.plugins.bodyParser());

server.listen('3000', '0.0.0.0', function () {
    console.log('本地端口监听:','3000');
});

// 定时任务Demo
const subscribeKey1 = '__keyevent@1__:expired'; // 这里要固定写法 写其他会无法触发
const redis_db1 = redis.createClient({
    'host': '127.0.0.1',
    'port': 6379,
    'password': '123456',
    'db': 1
});

redis_db1.on('connect', function() {
    console.log('Redis_1连接成功');
    redis_db1.subscribe(subscribeKey1, function() {
        //... 订阅频道成功
        console.log('Redis_1订阅成功');
    });
});

redis_db1.on('error', function() {
    console.log('Redis_1连接失败');
});

redis_db1.on('message', function(channel, msg) {
    console.log(channel); //__keyevent@1__:expired
    console.log(msg); //Set的Key
});

const routes = require('./routes')(server);

控制器 controller.js

const redis_db1 = require('../common/db').redis_1;

class TestController {
    //实际业务
    test(req, res) {
        redis_db1.multi()
            .set('Test', 'ok') 
    // Test为具体业务逻辑 把定时任务触发需要发生的参数以某种形式写在这里
    // 一个最简单的键名设计就是 任务类型 + ":" + JSON.stringify 化后的参数数组;
            .expire('Test', 10)
            .exec((error) => {
                if(error) {
                    console.log(error);
                    return;
                }
                console.log('订阅成功,10秒后生效');
            });
    }
}

module.exports = new TestController();

数据库

const redis = require('redis');

const client_0 = redis.createClient({
    'host': '127.0.0.1',
    'port': 6379
    'password': '123456',
    'db': 0
});

client_0.on("error", function () {
    console.log('Redis_0连接失败');
});

client_0.on('connect', function () {
    console.log('Redis_0连接成功');
})

const client_1 = redis.createClient({ // 这里如果是需要循环触发的话 需要在定义一次
    'host': '127.0.0.1',
    'port': 6379,
    'password': '123456',
    'db': 1
});

exports.redis_0 = client_0;
exports.redis_1 = client_1;

这样一个简单的定时任务就完成了 当我触发的时候

本地端口监听: 3000
Redis_1连接成功
Redis_0连接成功
Redis_1订阅成功
订阅成功,10秒后生效
__keyevent@1__:expired 即channel
Test 即msg 拿到之后 可实现给目标手机号发送短信功能

你可能感兴趣的:(Node.js 定时任务Demo)