A detailed explanation of the timing task based on redis
- 2020-06-23 02:14:56
- OfStack
preface
Requirements encountered in the business (under abstract Description 1) : The ability to loop tasks at different intervals for different users. For example, push relevant SMS messages to users 24 hours after successful registration.
Using crontab & # 63; It is too heavy, and almost unrealistic, to generate one timed task for every user on the server.
Regular polling ? IO is frequent and inefficient
Considering that redis, which is often used, can set the cache time, there should be expired event notification, I checked the documentation and found the relevant configuration, called "key-space event notification". For details, refer to the official documentation.
Technology stack
redis / nodeJs / koa
Key technical difficulties
"talk is cheap, show me the code 🤖"
The core code
The core code
const { saveClient, subClient } = require('./db/redis') // The storage and subscription instances need to be two different instances
const processor = require('./service/task')
const config = require('./config/index')
const innerDistributedLockKey = '&&__&&' // Distributed locks used internally key The eigenvalues of the
const innerDistributedLockKeyReg = new RegExp(`^${innerDistributedLockKey}`)
saveClient.on('ready', async () => {
saveClient.config('SET', 'notify-keyspace-events', 'Ex') // The storage instance is set to push key expiration events
console.log('redis init success')
})
subClient.on('ready', () => { // All can still be initialized after the service is restarted processor
subClient.subscribe(`__keyevent@${config.redis.sub.db}__:expired`) // The subscription instance is responsible for subscribing messages
subClient.on('message', async (cahnnel, expiredKey) => {
// Distributed locked key Do not listen on processing
if (expiredKey.match(innerDistributedLockKeyReg)) return
// Simple distributed lock, get lock instance consumption event
const cackeKey = `${innerDistributedLockKey}-${expiredKey}`
const lock = await saveClient.set(cackeKey, 2, 'ex', 5, 'nx') // The usage here allows for a simple distributed lock
if (lock === 'OK') {
await saveClient.del(cackeKey)
for (let key in processor) {
processor[key](expiredKey) // processor This corresponds to the business logic executed after receiving the phase-critical expiration notification, such as pushing an SMS message, and then relating it processor Again in set1 It expires at a certain time key
}
}
})
console.log('subClient init success')
})
servide/task (processor)
exports.sendMessage = async function sendMessage(expiredKey, subClient) {
// Only the expiration event for the relevant business is handled
if (expiredKey.match(/^send_message/)) {
const [prefix, userId, type] = expiredKey.split('-')
let user = getUser(userId)
if (user.phone) {
push(message) // Pseudo code
resetRedisKey(expiredKey, ttl) // To put the key Set to 1 After a period of time expired, expired will trigger the logic again
}
}
}
conclusion
Therefore, you need to balance the usage scenarios of timing tasks implemented using redis's expiration mechanism.