基于redis實(shí)現(xiàn)定時(shí)任務(wù)的方法詳解
前言
業(yè)務(wù)中碰到的需求(抽象描述一下):針對(duì)不同的用戶能夠?qū)崿F(xiàn)不同時(shí)間的間隔循環(huán)任務(wù)。比如在用戶注冊(cè)成功24小時(shí)后給用戶推送相關(guān)短信等類似需求。
使用crontab?太重,且基本不現(xiàn)實(shí),不可能給每一個(gè)用戶在服務(wù)器上生成一個(gè)定時(shí)任務(wù)。
定時(shí)輪詢?IO頻繁且效率太低
想到經(jīng)常的使用的redis可以設(shè)置緩存時(shí)間,應(yīng)該會(huì)有過(guò)期的事件通知吧,查了一下文檔,果然有相關(guān)配置,叫做“鍵空間事件通知”。具體說(shuō)明可參考官方文檔。
技術(shù)棧
redis / nodeJs / koa
技術(shù)重難點(diǎn)
- 開(kāi)啟redis的鍵空間通知功能(2.8.0及以上的版本才有此功能)
- 盡量使用單獨(dú)的redis db來(lái)實(shí)現(xiàn)
- 使用基于redis的分布式鎖來(lái)實(shí)現(xiàn)相關(guān)事件不會(huì)被重復(fù)消費(fèi)
- 需要二次使用的信息需要體現(xiàn)在redis緩存的key中
- redis cache key使用業(yè)務(wù)前綴,避免重名覆蓋
- 防止業(yè)務(wù)服務(wù)重啟導(dǎo)致nodejs層面的監(jiān)聽(tīng)失效
"talk is cheap, show me the code 🤖"
核心代碼
核心代碼
const { saveClient, subClient } = require('./db/redis') // 存儲(chǔ)實(shí)例和訂閱實(shí)例需要為兩個(gè)不同的實(shí)例
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') // 存儲(chǔ)實(shí)例設(shè)置為推送鍵過(guò)期事件
console.log('redis init success')
})
subClient.on('ready', () => { // 服務(wù)重啟后依舊可以初始化所有processor
subClient.subscribe(`__keyevent@${config.redis.sub.db}__:expired`) // 訂閱實(shí)例負(fù)責(zé)訂閱消息
subClient.on('message', async (cahnnel, expiredKey) => {
// 分布式鎖的key不做監(jiān)聽(tīng)處理
if (expiredKey.match(innerDistributedLockKeyReg)) return
// 簡(jiǎn)易分布式鎖,拿到鎖的實(shí)例消費(fèi)event
const cackeKey = `${innerDistributedLockKey}-${expiredKey}`
const lock = await saveClient.set(cackeKey, 2, 'ex', 5, 'nx') // 這里的用法可以實(shí)現(xiàn)簡(jiǎn)易的分布式鎖
if (lock === 'OK') {
await saveClient.del(cackeKey)
for (let key in processor) {
processor[key](expiredKey) // processor對(duì)應(yīng)的是接收到相關(guān)鍵過(guò)期通知后執(zhí)行的業(yè)務(wù)邏輯,比如推送短信,然后在相關(guān)processor中再次set一個(gè)定時(shí)過(guò)期的key
}
}
})
console.log('subClient init success')
})
servide/task (processor)
exports.sendMessage = async function sendMessage(expiredKey, subClient) {
// 只處理相關(guān)業(yè)務(wù)的過(guò)期事件
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è)置為一段時(shí)間后過(guò)期,過(guò)期后會(huì)再次觸發(fā)本邏輯
}
}
}
總結(jié)
- 此功能利用了redis的鍵空間通知功能實(shí)現(xiàn)了簡(jiǎn)單了基于用戶或者基于不同業(yè)務(wù)場(chǎng)景的定時(shí)任務(wù)功能。由于鍵空間事件通知功能是一個(gè)較消耗CPU的操作,所以建議使用單獨(dú)的DB來(lái)處理。
- 這里展示出來(lái)的是基本用法,未考慮定時(shí)任務(wù)的持久化功能,如果使用過(guò)程中redis故障重啟,則會(huì)導(dǎo)致所有定時(shí)任務(wù)丟失。如果在redis發(fā)布鍵失效通知時(shí),訂閱服務(wù)出故障未在線,或者網(wǎng)絡(luò)問(wèn)題沒(méi)有被消費(fèi)方收到,也會(huì)導(dǎo)致此次事件丟失。
- redis的expired事件并不是在key過(guò)期的時(shí)候觸發(fā),而是在key被刪除的時(shí)候觸發(fā)。redis會(huì)定期清理過(guò)期的key,或者當(dāng)訪問(wèn)key的時(shí)候檢查是否過(guò)期,只有這時(shí)過(guò)期的key才會(huì)觸發(fā)刪除操作,因此會(huì)有一些小的時(shí)間差距(個(gè)人的實(shí)際使用中并沒(méi)有影響用戶體驗(yàn))。
因此需要權(quán)衡使用redis的過(guò)期機(jī)制實(shí)現(xiàn)的定時(shí)任務(wù)的使用場(chǎng)景。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- Redis定時(shí)監(jiān)控與數(shù)據(jù)處理的實(shí)踐指南
- Redis拓展之定時(shí)消息通知實(shí)現(xiàn)詳解
- Redis?延時(shí)任務(wù)實(shí)現(xiàn)及與定時(shí)任務(wù)區(qū)別詳解
- Spring boot詳解緩存redis實(shí)現(xiàn)定時(shí)過(guò)期方法
- Redis定時(shí)任務(wù)原理的實(shí)現(xiàn)
- Python定時(shí)從Mysql提取數(shù)據(jù)存入Redis的實(shí)現(xiàn)
- Spring Boot監(jiān)聽(tīng)Redis Key失效事件實(shí)現(xiàn)定時(shí)任務(wù)的示例
- Springboot使用Redis實(shí)現(xiàn)定時(shí)任務(wù)的三種方式
相關(guān)文章
redis簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了redis簡(jiǎn)介,Redis是一個(gè)開(kāi)源的,先進(jìn)的 key-value 存儲(chǔ)可用于構(gòu)建高性能,可擴(kuò)展的 Web 應(yīng)用程序的解決方案,有興趣的可以了解一下2017-08-08
Redis的六種底層數(shù)據(jù)結(jié)構(gòu)(小結(jié))
本文主要介紹了Redis的六種底層數(shù)據(jù)結(jié)構(gòu),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Redis服務(wù)器的啟動(dòng)過(guò)程分析
這篇文章主要介紹了Redis服務(wù)器的啟動(dòng)過(guò)程分析,本文講解了初始化Redis服務(wù)器全局配置、加載配置文件、初始化服務(wù)器、加載數(shù)據(jù)、開(kāi)始網(wǎng)絡(luò)監(jiān)聽(tīng)等內(nèi)容,需要的朋友可以參考下2015-04-04
redis+mysql+quartz 一種紅包發(fā)送功能的實(shí)現(xiàn)
這篇文章主要介紹了redis+mysql+quartz 一種紅包發(fā)送功能的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-01-01
RedisDesktopManager?連接redis的方法
這篇文章主要介紹了RedisDesktopManager?連接redis,需要的朋友可以參考下2023-08-08
Redis數(shù)據(jù)結(jié)構(gòu)之鏈表詳解
大家好,本篇文章主要講的是Redis數(shù)據(jù)結(jié)構(gòu)之鏈表詳解,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
利用redisson快速實(shí)現(xiàn)自定義限流注解(接口防刷)
利用redis的有序集合即Sorted?Set數(shù)據(jù)結(jié)構(gòu),構(gòu)造一個(gè)令牌桶來(lái)實(shí)施限流,而redisson已經(jīng)幫我們封裝成了RRateLimiter,通過(guò)redisson,即可快速實(shí)現(xiàn)我們的目標(biāo),這篇文章主要介紹了利用redisson快速實(shí)現(xiàn)自定義限流注解,需要的朋友可以參考下2024-07-07

