基于redis實現(xiàn)定時任務(wù)的方法詳解
前言
業(yè)務(wù)中碰到的需求(抽象描述一下):針對不同的用戶能夠?qū)崿F(xiàn)不同時間的間隔循環(huán)任務(wù)。比如在用戶注冊成功24小時后給用戶推送相關(guān)短信等類似需求。
使用crontab?太重,且基本不現(xiàn)實,不可能給每一個用戶在服務(wù)器上生成一個定時任務(wù)。
定時輪詢?IO頻繁且效率太低
想到經(jīng)常的使用的redis可以設(shè)置緩存時間,應(yīng)該會有過期的事件通知吧,查了一下文檔,果然有相關(guān)配置,叫做“鍵空間事件通知”。具體說明可參考官方文檔。
技術(shù)棧
redis / nodeJs / koa
技術(shù)重難點
- 開啟redis的鍵空間通知功能(2.8.0及以上的版本才有此功能)
- 盡量使用單獨的redis db來實現(xiàn)
- 使用基于redis的分布式鎖來實現(xiàn)相關(guān)事件不會被重復(fù)消費
- 需要二次使用的信息需要體現(xiàn)在redis緩存的key中
- redis cache key使用業(yè)務(wù)前綴,避免重名覆蓋
- 防止業(yè)務(wù)服務(wù)重啟導(dǎo)致nodejs層面的監(jiān)聽失效
"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 = '&&__&&' // 內(nèi)部使用的分布式鎖的key的特征值 const innerDistributedLockKeyReg = new RegExp(`^${innerDistributedLockKey}`) saveClient.on('ready', async () => { saveClient.config('SET', 'notify-keyspace-events', 'Ex') // 存儲實例設(shè)置為推送鍵過期事件 console.log('redis init success') }) subClient.on('ready', () => { // 服務(wù)重啟后依舊可以初始化所有processor subClient.subscribe(`__keyevent@${config.redis.sub.db}__:expired`) // 訂閱實例負責(zé)訂閱消息 subClient.on('message', async (cahnnel, expiredKey) => { // 分布式鎖的key不做監(jiān)聽處理 if (expiredKey.match(innerDistributedLockKeyReg)) return // 簡易分布式鎖,拿到鎖的實例消費event const cackeKey = `${innerDistributedLockKey}-${expiredKey}` const lock = await saveClient.set(cackeKey, 2, 'ex', 5, 'nx') // 這里的用法可以實現(xiàn)簡易的分布式鎖 if (lock === 'OK') { await saveClient.del(cackeKey) for (let key in processor) { processor[key](expiredKey) // processor對應(yīng)的是接收到相關(guān)鍵過期通知后執(zhí)行的業(yè)務(wù)邏輯,比如推送短信,然后在相關(guān)processor中再次set一個定時過期的key } } }) console.log('subClient init success') })
servide/task (processor) exports.sendMessage = async function sendMessage(expiredKey, subClient) { // 只處理相關(guān)業(yè)務(wù)的過期事件 if (expiredKey.match(/^send_message/)) { const [prefix, userId, type] = expiredKey.split('-') let user = getUser(userId) if (user.phone) { push(message) // 偽代碼 resetRedisKey(expiredKey, ttl) // 重新把key設(shè)置為一段時間后過期,過期后會再次觸發(fā)本邏輯 } } }
總結(jié)
- 此功能利用了redis的鍵空間通知功能實現(xiàn)了簡單了基于用戶或者基于不同業(yè)務(wù)場景的定時任務(wù)功能。由于鍵空間事件通知功能是一個較消耗CPU的操作,所以建議使用單獨的DB來處理。
- 這里展示出來的是基本用法,未考慮定時任務(wù)的持久化功能,如果使用過程中redis故障重啟,則會導(dǎo)致所有定時任務(wù)丟失。如果在redis發(fā)布鍵失效通知時,訂閱服務(wù)出故障未在線,或者網(wǎng)絡(luò)問題沒有被消費方收到,也會導(dǎo)致此次事件丟失。
- redis的expired事件并不是在key過期的時候觸發(fā),而是在key被刪除的時候觸發(fā)。redis會定期清理過期的key,或者當(dāng)訪問key的時候檢查是否過期,只有這時過期的key才會觸發(fā)刪除操作,因此會有一些小的時間差距(個人的實際使用中并沒有影響用戶體驗)。
因此需要權(quán)衡使用redis的過期機制實現(xiàn)的定時任務(wù)的使用場景。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
Spring Boot整合Redis實現(xiàn)訂單超時處理問題
這篇文章主要介紹了Spring Boot整合Redis實現(xiàn)訂單超時處理,通過這個基本的示例,你可以了解如何使用Spring Boot和Redis來處理訂單超時問題,并根據(jù)需要進行擴展和定制,需要的朋友可以參考下2023-11-11關(guān)于redis可視化工具讀取數(shù)據(jù)亂碼問題
大家來聊一聊在日常操作redis時用的是什么工具,redis提供的一些命令你都了解了嗎,今天通過本文給大家介紹redis可視化工具讀取數(shù)據(jù)亂碼問題,感興趣的朋友跟隨小編一起看看吧2021-07-07