redis发布订阅注意事项
前言
Redis 的发布/订阅(Pub/Sub)是一个消息传递模式,主要有发布端和订阅端两个角色,发送者(pub)发送消息,订阅者(sub)接收消息。这种机制可以用于实时消息推送、事件通知等应用场景。
今天我在用redis的发布/订阅模式,发现订阅端在接收消息时一直报错,所以简单记录下这次错误。
一、报错现场还原
redis配置
// redis_util.js
var redis = require('redis');
var client = redis.createClient(6379,'127.0.0.1');
发布端代码
// publish_clien.js
const redis = require('redis_utils');
priority = 1
await redis.zadd('manager_queue', priority, JSON.stringify(job_data));
redis.publish('taskManagerChannel', 'New task added');
订阅端代码
const redis = require('redis_utils');
function subscribeToChannel(channel) {
// 订阅频道 subscribe channel
redis.subscribe(channel, (err, count) => {
if (err) {
console.error('Job-Manager',Failed to subscribe: ${err}.);
return;
}
console.log('Job-Manager', Subscribed to ${count} channel(s).);
});
// 监听消息 listen message
redis.on('message', async (channel, message) => {
if (message === 'New task added') {
console.log('Job-Manager', Received message from channel ${channel}: ${message});
const jb = await imageQueue.getJobCounts();
const total_Counts = jb.completed + jb.failed + jb.waiting + jb.active + jb.delayed + jb.paused;
const completed_Counts = await imageQueue.getCompletedCount();
const failed_Counts = await imageQueue.getFailedCount();
if (total_Counts === completed_Counts + failed_Counts) {
const result = await redis.zrange(redis.manager_queue, 0, 0);
if (result.length > 0) {
await imageQueue.add(JSON.parse(result[0]));
await redis.zrem('manager_queue', result[0]);
}
}
}
});
}
subscribeToChannel('taskManagerChannel');
上述代码出了问题
2024-08-05 16:08:39 ---> <任务管理调度> : Subscribed to taskManagerChannel channel(s).
2024-08-05 16:08:54 ---> <任务管理调度> : Received message from channel taskManagerChannel: New task added
Redis Error ReplyError: ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context
从上面控制台可以看到,redis抛出了一个ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context
,这是什么意思呢?
Redis 客户端在订阅模式下只能执行订阅相关的命令(如
SUBSCRIBE
、UNSUBSCRIBE
、PING
和QUIT
),不允许执行其他操作(如SET
、GET
)。如果我们在处理订阅消息时使用了相同的客户端实例来执行其他 Redis 命令,这可能会导致该错误。
很明显,我们的订阅端代码在操作redis的有序集合时所用到的redis实例和订阅监听所用到的redis实例是同一个,这就违反了
**Redis 客户端在订阅模式下只能执行订阅相关的命令(如
SUBSCRIBE、
UNSUBSCRIBE、
PING和
QUIT),不允许执行其他操作(如
SET、
GET)**。
,所以我们的代码出现了Redis Error ReplyError: ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context
这个错误
await redis.zrem('manager_queue', result[0]);`
const result = await redis.zrange(redis.manager_queue, 0, 0);
==================================================================
redis.subscribe(channel, (err, count)
redis.on('message', async (channel, message)
二、代码修正
redis配置文件
//redis_utils.js
var redis = require('redis');
var subscriberClient = redis.createClient(6379, '127.0.0.1'); // 订阅客户端
var publisherClient = redis.createClient(6379, '127.0.0.1'); // 发布客户端
subscriberClient.on('ready', function () {
console.log('Redis subscriber client: ready');
});
publisherClient.on('ready', function () {
console.log('Redis publisher client: ready');
});
module.exports = {
getSubscriberClient: () => subscriberClient,
getPublisherClient: () => publisherClient,
// Other methods as needed
};
// 然后,发布代码中使用publisherClient,订阅代码中使用subscriberClient
// 注意 在 redisClient.on('message') 回调中只处理消息,而不进行其他 Redis 操作。如果需要执行 Redis 操作,请使用单独的客户端实例。
订阅端
const redis = require('../database/redis-client');
const publisherClient = redis.getPublisherClient();
const subscriberClient = redis.getSubscriberClient();
function subscribeToChannel(channel) {
subscriberClient.subscribe(channel, (err, count) => { // 这里改成了subscriberClient
if (err) {
console.error('Job-Manager',Failed to subscribe: ${err}.);
return;
}
console.log('Job-Manager', Subscribed to ${count} channel(s).);
});
subscriberClient.on('message', async (channel, message) => { //这里改成了subscriberClient
if (message === 'New task added') {
console.log('Job-Manager', Received message from channel ${channel}: ${message});
const jb = await imageQueue.getJobCounts();
const total_Counts = jb.completed + jb.failed + jb.waiting + jb.active + jb.delayed + jb.paused;
const completed_Counts = await imageQueue.getCompletedCount();
const failed_Counts = await imageQueue.getFailedCount();
if (total_Counts === completed_Counts + failed_Counts) {
const result = await publisherClient.zrange(redis.manager_queue, 0, 0); //这里做了改动
if (result.length > 0) {
var job_opts = {removeOnComplete: 10, removeOnFail: {age: 60*60*24}};
await imageQueue.add(JSON.parse(result[0]), job_opts);
await publisherClient.zrem('manager_queue', result[0]); //这里做了改动
}
}
}
});
}
subscribeToChannel('taskManagerChannel');```
---