redis pub sub with jedis , sub crashes with error

baba.kabira picture baba.kabira · Aug 25, 2012 · Viewed 10.3k times · Source

All

I have installed latest Redis 2.4.16 and trying to use its Pub/Sub system with java. I am putting a message to a channel every second. There is no issue with Publisher but Subscriber crashes with the message

Exception:

redis.clients.jedis.exceptions.JedisDataException: ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
at redis.clients.jedis.Protocol.processError(Protocol.java:59)
at redis.clients.jedis.Protocol.process(Protocol.java:66)
at redis.clients.jedis.Protocol.read(Protocol.java:131)
at redis.clients.jedis.Connection.getObjectMultiBulkReply(Connection.java:206)
at redis.clients.jedis.JedisPubSub.process(JedisPubSub.java:88)
at redis.clients.jedis.JedisPubSub.proceed(JedisPubSub.java:83)
at redis.clients.jedis.Jedis.subscribe(Jedis.java:1971)
at com.jedis.test.JedisSub$1.run(JedisSub.java:22)
at java.lang.Thread.run(Thread.java:680)

Here is my Code:

Publisher:

        final Jedis jedis = new Jedis("localhost");

        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
        newFixedThreadPool.submit(new Runnable() {

            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    jedis.publish("CC", new Date().toString());
                }

            }
        });

Subscriber:

        JedisPool jedisPool = new JedisPool(poolConfig,"localhost", 6379, 100);
        final Jedis subscriberJedis = jedisPool.getResource();


        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    subscriberJedis.subscribe(new JedisPubSub() …..,"CC");
                } catch (Exception e) {
                   e.printStackTrace();
                }
            }
        }).start();

        jedisPool.returnResource(subscriberJedis);

Pool Configuration:

    JedisPoolConfig poolConfig = new JedisPoolConfig();
    poolConfig.maxActive = 10;
    poolConfig.maxIdle = 5;
    poolConfig.minIdle = 1;
    poolConfig.testOnBorrow = true;
    poolConfig.numTestsPerEvictionRun = 10;
    poolConfig.timeBetweenEvictionRunsMillis = 60000;
    poolConfig.maxWait = 3000;
    poolConfig.whenExhaustedAction = org.apache.commons.pool.impl.GenericObjectPool.WHEN_EXHAUSTED_FAIL;

For installing Redis, I simply used command

make PREFIX=/Users/ggg/dev/dist/redis/ install

After this I did not use ./install_server.sh

Jedis version is 2.1.0, platform is Mac OS X.

Note: What I have noticed is subscriber crashes near about 30 seconds after start.

Answer

Didier Spezia picture Didier Spezia · Aug 25, 2012

Both the code of the publisher and the subscriber are wrong in their own way.

The error comes from the fact a Redis connection cannot be shared between publishers and subscribers. Actually you need a connection (or a pool of connections) for publishers, and just one dedicated connection for the subscriber thread. Running a single subscriber thread per process is usually enough.

Here, you return the subscriberJedis connection to the pool too early, before the subscriber thread is complete, so the connection is shared.

In the publisher:

Since you have a pool of 10 threads, you should not share a unique connection across these threads. This is the perfect place to use the connection pool, and the connections have to be grabbed and released in each thread.

    // This should be a global singleton
    JedisPool jedisPool = new JedisPool(poolConfig,"localhost", 6379, 100);

    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
    newFixedThreadPool.submit(new Runnable() {

        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Jedis jedis = jedisPool.getResource();
                try {
                   jedis.publish("CC", new Date().toString());
                } catch (Exception e) {
                   e.printStackTrace();
                } finally {
                   jedisPool.returnResource(jedis);
                }
            }

        }
    });

In the subscriber:

In the subscriber, you need a dedicated connection.

    new Thread(new Runnable() {
        @Override
        public void run() {
            Jedis subscriberJedis = new Jedis("localhost");
            try {
                subscriberJedis.subscribe(new JedisPubSub() …..,"CC");
            } catch (Exception e) {
               e.printStackTrace();
            }
        }
    }).start();

If you need to subscribe to different channels or patterns, it is better to set the other subscriptions in the same thread and for the same connection.