基于redis实现定时任务

业务中碰到的需求(抽象描述一下):针对不一样的用户可以实现不一样时间的间隔循环任务。好比在用户注册成功24小时后给用户推送相关短信等相似需求。
使用crontab?过重,且基本不现实,不可能给每个用户在服务器上生成一个定时任务。
定时轮询?IO频繁且效率过低
想到常常的使用的redis能够设置缓存时间,应该会有过时的事件通知吧,查了一下文档,果真有相关配置,叫作“键空间事件通知”。具体说明可参考官方文档javascript

技术栈

redis / nodeJs / koajava

技术重难点

  • 开启redis的键空间通知功能(2.8.0及以上的版本才有此功能)
  • 尽可能使用单独的redis db来实现
  • 使用基于redis的分布式锁来实现相关事件不会被重复消费
  • 须要二次使用的信息须要体如今redis缓存的key中
  • redis cache key使用业务前缀,避免重名覆盖
  • 防止业务服务重启致使nodejs层面的监听失效

"talk is cheap, show me the code 🤖"

核心代码
const { saveClient, subClient } = require('./db/redis') // 存储实例和订阅实例须要为两个不一样的实例
const processor = require('./service/task')
const config = require('./config/index')
const innerDistributedLockKey = '&&__&&' // 内部使用的分布式锁的key的特征值
const innerDistributedLockKeyReg = new RegExp(`^${innerDistributedLockKey}`)

saveClient.on('ready', async () => {
  saveClient.config('SET', 'notify-keyspace-events', 'Ex') // 存储实例设置为推送键过时事件
  console.log('redis init success')
})

subClient.on('ready', () => { // 服务重启后依旧能够初始化全部processor
  subClient.subscribe(`__keyevent@${config.redis.sub.db}__:expired`) // 订阅实例负责订阅消息
  subClient.on('message', async (cahnnel, expiredKey) => {
    // 分布式锁的key不作监听处理
    if (expiredKey.match(innerDistributedLockKeyReg)) return
    // 简易分布式锁,拿到锁的实例消费event
    const cackeKey = `${innerDistributedLockKey}-${expiredKey}`
    const lock = await saveClient.set(cackeKey, 2, 'ex', 5, 'nx') // 这里的用法能够实现简易的分布式锁
    if (lock === 'OK') {
      await saveClient.del(cackeKey)
      for (let key in processor) {
        processor[key](expiredKey) // processor对应的是接收到相关键过时通知后执行的业务逻辑,好比推送短信,而后在相关processor中再次set一个定时过时的key
      }
    }
  })
  console.log('subClient init success')
})
servide/task (processor)
exports.sendMessage = async function sendMessage(expiredKey, subClient) {
  // 只处理相关业务的过时事件
  if (expiredKey.match(/^send_message/)) {
    const [prefix, userId, type] = expiredKey.split('-')
    let user = getUser(userId)
    if (user.phone) {
      push(message) // 伪代码
      resetRedisKey(expiredKey, ttl) // 从新把key设置为一段时间后过时,过时后会再次触发本逻辑
    }
  }
}

总结

  • 此功能利用了redis的键空间通知功能实现了简单了基于用户或者基于不一样业务场景的定时任务功能。因为键空间事件通知功能是一个较消耗CPU的操做,因此建议使用单独的DB来处理。
  • 这里展现出来的是基本用法,未考虑定时任务的持久化功能,若是使用过程当中redis故障重启,则会致使全部定时任务丢失。若是在redis发布键失效通知时,订阅服务出故障未在线,或者网络问题没有被消费方收到,也会致使这次事件丢失
  • redis的expired事件并非在key过时的时候触发,而是在key被删除的时候触发。redis会按期清理过时的key,或者当访问key的时候检查是否过时,只有这时过时的key才会触发删除操做,所以会有一些小的时间差距(我的的实际使用中并无影响用户体验)。

所以须要权衡使用redis的过时机制实现的定时任务的使用场景。node

感谢阅读,转载请注明出处。
喜欢的朋友能够关注个人公众号:雨茗良记,每周会按期更新文章哦,包括但不限于技术。
我是雨茗良记,一个爱作饭的程序猿😜
雨茗良记redis

相关文章
相关标签/搜索