You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Jedis实现Redis内部通信时publish命令报错的问题排查

关于Jedis发布订阅报错及跨实例消息过滤的问题

一、报错原因

Redis的连接一旦进入订阅模式(调用subscribe/psubscribe后),就会进入特殊的协议状态:这个连接只能执行和订阅相关的命令,比如SUBSCRIBE/UNSUBSCRIBE/PING/QUIT,任何其他命令(比如PUBLISH、GET、SET等)都会被Redis拒绝,这就是你看到ERR Can't execute 'publish'错误的原因。

你用了同一个Jedis实例既做订阅监听,又调用publish发消息,相当于在一个已经处于订阅状态的连接上执行非订阅命令,直接触发了Redis的协议限制。

二、解决发布报错的方法

核心是拆分连接:给订阅和发布分别创建独立的Jedis实例,两个连接互不干扰:

// 1. 订阅专用连接,单独开线程运行监听
Jedis subscribeJedis = new Jedis("localhost", 6379);
new Thread(() -> subscribeJedis.subscribe(new CustomPubSub(), "user_update")).start();

// 2. 发布专用连接,专门用来发消息
Jedis publishJedis = new Jedis("localhost", 6379);
// 发布消息时用这个实例
publishJedis.publish("user_update", "user_123_updated");

// 自定义订阅处理类
class CustomPubSub extends JedisPubSub {
    @Override
    public void onMessage(String channel, String message) {
        System.out.println(channel + " -> " + message);
        // 这里添加清除缓存的逻辑
    }
}

三、实现只接收其他服务器的消息

要过滤掉自己发送的消息,关键是给每个后端实例分配唯一标识,发布消息时带上这个标识,订阅端收到消息后判断发送者是否是自己,是则忽略:

  1. 给每个实例生成唯一ID(比如启动时生成UUID,或者从配置读取实例编号)
  2. 发布消息时,把业务内容和自身ID打包发送
  3. 订阅端解析消息后,对比发送者ID和自身ID,不一致才处理

示例代码:

// 每个实例的唯一标识,启动时生成一次
private static final String INSTANCE_ID = UUID.randomUUID().toString();

// 发布端:打包消息内容和发送者ID
public void publishUserUpdate(String userId) {
    // 用JSON格式打包,实际项目建议用Jackson/Gson等工具类
    String message = String.format("{\"userId\":\"%s\",\"senderId\":\"%s\"}", userId, INSTANCE_ID);
    publishJedis.publish("user_update", message);
}

// 订阅端:解析并过滤消息
class CustomPubSub extends JedisPubSub {
    private final String ownInstanceId;

    public CustomPubSub(String ownInstanceId) {
        this.ownInstanceId = ownInstanceId;
    }

    @Override
    public void onMessage(String channel, String message) {
        // 解析JSON消息
        JSONObject msgJson = new JSONObject(message);
        String senderId = msgJson.getString("senderId");
        String userId = msgJson.getString("userId");

        // 只处理其他实例发送的消息
        if (!ownInstanceId.equals(senderId)) {
            System.out.println("收到其他实例的通知,清除用户" + userId + "的缓存");
            // 执行清除缓存的逻辑
        }
    }
}

// 初始化订阅时传入自身ID
new Thread(() -> subscribeJedis.subscribe(new CustomPubSub(INSTANCE_ID), "user_update")).start();

内容的提问来源于stack exchange,提问作者Lvkas_

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 05:07:16