前言
業務中碰到的需求(抽象描述一下):針對不同的用戶能夠實現不同時間的間隔循環任務。比如在用戶注冊成功24小時后給用戶推送相關短信等類似需求。
使用crontab?太重,且基本不現實,不可能給每一個用戶在服務器上生成一個定時任務。
定時輪詢?IO頻繁且效率太低
想到經常的使用的redis可以設置緩存時間,應該會有過期的事件通知吧,查了一下文檔,果然有相關配置,叫做“鍵空間事件通知”。具體說明可參考官方文檔。
技術棧
redis / nodeJs / koa
技術重難點
"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的過期機制實現的定時任務的使用場景。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對武林網的支持。
新聞熱點
疑難解答