com.janrain.redis.Redis.java Source code

Java tutorial

Introduction

Here is the source code for com.janrain.redis.Redis.java

Source

/*
 * Copyright 2012 Janrain, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.janrain.redis;

import com.janrain.commons.util.Pair;
import com.janrain.utils.BackplaneSystemProps;
import com.netflix.curator.framework.CuratorFramework;
import com.netflix.curator.framework.recipes.cache.ChildData;
import com.netflix.curator.framework.recipes.cache.PathChildrenCache;
import com.netflix.curator.framework.recipes.cache.PathChildrenCacheEvent;
import com.netflix.curator.framework.recipes.cache.PathChildrenCacheListener;
import com.netflix.curator.framework.recipes.locks.InterProcessMutex;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Gauge;
import com.yammer.metrics.core.MetricName;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import redis.clients.jedis.Protocol;

/**
 * @author Tom Raney
 */
public class Redis implements PathChildrenCacheListener {

    public static Redis getInstance() {
        return instance;
    }

    /**
     * Be sure to return to pool!
     * @return
     */

    public Jedis getReadJedis() {
        return getJedisFromPool(getReadPool());
    }

    public Jedis getWriteJedis() {
        return getJedisFromPool(getWritePool());
    }

    public void releaseToPool(Jedis jedis) {
        releaseToPool(jedis, false);
    }

    public void releaseBrokenResourceToPool(Jedis jedis) {
        releaseToPool(jedis, true);
    }

    public void releaseToPool(Jedis jedis, boolean isBroken) {
        if (jedis == null)
            return;
        logger.debug("returning jedis: " + jedis.toString() + " to pool -> isBroken: " + isBroken);
        JedisPool pool = checkedOutJedises.get(jedis);
        if (pool != null) {
            synchronized (jedis) {
                if (isBroken) {
                    pool.returnBrokenResource(jedis);
                } else {
                    pool.returnResource(jedis);
                }
                checkedOutJedises.remove(jedis);
            }
        } else {
            logger.warn("attempted to return a broken jedis: " + jedis.toString() + " that wasn't checked out");
        }

    }

    public void set(byte[] key, byte[] value) {
        Jedis jedis = getWriteJedis();

        try {
            jedis.set(key, value);
        } finally {
            releaseToPool(jedis);
        }
    }

    public void del(byte[] key) {
        Jedis jedis = getWriteJedis();
        try {
            jedis.del(key);
        } finally {
            releaseToPool(jedis);
        }
    }

    public void set(byte[] key, byte[] value, int seconds) {
        Jedis jedis = getWriteJedis();
        try {
            jedis.setex(key, seconds, value);
        } finally {
            releaseToPool(jedis);
        }
    }

    public void set(String key, String value) {
        set(key, value, null);
    }

    public void set(String key, String value, @Nullable Integer seconds) {
        Jedis jedis = getWriteJedis();
        try {
            if (seconds == null) {
                jedis.set(key, value);
            } else {
                jedis.setex(key, seconds, value);
            }
        } finally {
            releaseToPool(jedis);
        }
    }

    public void append(byte[] key, byte[] value) {
        Jedis jedis = getWriteJedis();
        try {
            jedis.append(key, value);
        } finally {
            releaseToPool(jedis);
        }
    }

    public Long rpush(final byte[] key, final byte[] string) {
        Jedis jedis = getWriteJedis();
        try {
            return jedis.rpush(key, string);
        } finally {
            releaseToPool(jedis);
        }
    }

    public long llen(byte[] key) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.llen(key);
        } finally {
            releaseToPool(jedis);
        }
    }

    public byte[] get(byte[] key) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.get(key);
        } finally {
            releaseToPool(jedis);
        }
    }

    public String get(String key) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.get(key);
        } finally {
            releaseToPool(jedis);
        }
    }

    public List<byte[]> mget(byte[]... keys) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.mget(keys);
        } finally {
            releaseToPool(jedis);
        }
    }

    public byte[] lpop(byte[] key) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.lpop(key);
        } finally {
            releaseToPool(jedis);
        }
    }

    public List<byte[]> lrange(final byte[] key, final int start, final int end) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.lrange(key, start, end);
        } finally {
            releaseToPool(jedis);
        }
    }

    public Set<byte[]> zrangebyscore(final byte[] key, double min, double max) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.zrangeByScore(key, min, max);
        } finally {
            releaseToPool(jedis);
        }
    }

    public long zcard(final byte[] key) {
        Jedis jedis = getReadJedis();
        try {
            return jedis.zcard(key);
        } finally {
            getReadPool().getRight().returnResource(jedis);
        }
    }

    public void setActiveRedisInstance(CuratorFramework client) {
        this.curatorFramework = client;
        InterProcessMutex lock = null;

        try {
            lock = new InterProcessMutex(client, REDIS_LOCK);
            lock.acquire();

            PathChildrenCache pathChildrenCache = new PathChildrenCache(client, REDIS, true);
            pathChildrenCache.getListenable().addListener(this);
            pathChildrenCache.start(true);

            ChildData childData = pathChildrenCache.getCurrentData(REDIS);

            String redisServer = null;
            if (childData != null) {
                byte[] bytes = childData.getData();
                redisServer = new String(bytes);
            }

            if (redisServer == null) {
                // set the node to the default redis server
                setRedisServer(BackplaneSystemProps.REDIS_SERVER_PRIMARY);
            } else {
                // accept the cluster wide redis server
                currentRedisServerForWrites = redisServer;
            }

        } catch (Exception e) {
            logger.error(e);
        } finally {
            if (lock != null) {
                try {
                    lock.release();
                } catch (Exception e) {
                    logger.error("could not release lock " + e);
                }
            }
        }

    }

    @Override
    public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent)
            throws Exception {
        try {
            if (pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.CHILD_UPDATED
                    || pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED) {

                ChildData childData = pathChildrenCacheEvent.getData();
                if (childData != null && childData.getData() != null) {
                    String newValue = new String(childData.getData());
                    currentRedisServerForWrites = newValue;
                    logger.info("redis server changed: " + newValue);
                }
            }
        } catch (Exception e) {
            logger.warn("failure with childEvent");
            throw e;
        }

    }

    public synchronized void setRedisServer(String server) {

        if (curatorFramework != null) {
            try {
                if (this.curatorFramework.checkExists().forPath(REDIS_SERVER) == null) {
                    this.curatorFramework.create().forPath(REDIS_SERVER);
                }
                this.curatorFramework.setData().forPath(REDIS_SERVER, server.getBytes());
            } catch (Exception e) {
                logger.error(e);
            }
        }
        logger.info("redis server set to " + server);
    }

    public void ping() {

        Jedis jedisWrite = null;
        Jedis jedisRead = null;

        String replyJedisWrite = "ERROR";
        String replyJedisRead = "ERROR";

        try {
            jedisWrite = this.getWriteJedis();
            replyJedisWrite = jedisWrite.ping();
        } catch (Exception e) {
            // something bad
            logger.warn("error during ping");
        } finally {
            logger.info("PING " + System.getProperty(BackplaneSystemProps.REDIS_SERVER_PRIMARY) + " ("
                    + BackplaneSystemProps.REDIS_SERVER_PRIMARY + ") -> " + replyJedisWrite);
            releaseToPool(jedisWrite);
        }

        try {
            jedisRead = this.getReadJedis();
            replyJedisRead = jedisRead.ping();
        } catch (Exception e) {
            // something bad
            logger.warn("error during ping");
        } finally {
            logger.info("PING " + System.getProperty(BackplaneSystemProps.REDIS_SERVER_READS) + " ("
                    + BackplaneSystemProps.REDIS_SERVER_READS + ") -> " + replyJedisRead);
            releaseToPool(jedisRead);
        }

    }

    // PRIVATE

    private static final Logger logger = Logger.getLogger(Redis.class);

    //private String[] currentRedisServerForReads;
    private String currentRedisServerForWrites;

    private final Pair<String, JedisPool> poolForWrites;
    private final ArrayList<Pair<String, JedisPool>> poolForReads = new ArrayList<Pair<String, JedisPool>>();

    private static Redis instance = new Redis();
    private final String REDIS_LOCK = "/redislock";
    private final String REDIS = "/redis";
    private final String REDIS_SERVER = "/redis/server";

    private static final long REDIS_MAX_WAIT_SECONDS = 2l;

    private CuratorFramework curatorFramework;

    private Redis() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxActive(50);
        jedisPoolConfig.setTestWhileIdle(true);
        jedisPoolConfig.setTestOnReturn(true);
        jedisPoolConfig.setMaxWait(REDIS_MAX_WAIT_SECONDS * 1000l);
        jedisPoolConfig.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
        jedisPoolConfig.setMaxIdle(-1);
        jedisPoolConfig.setMinIdle(50);

        String redisServerConfig = System.getProperty(BackplaneSystemProps.REDIS_SERVER_PRIMARY);
        String redisServerPass = System.getProperty(BackplaneSystemProps.REDIS_SERVER_PRIMARY_PASSWORD);
        if (StringUtils.isEmpty(redisServerConfig)) {
            logger.error("cannot find configuration entry for " + BackplaneSystemProps.REDIS_SERVER_PRIMARY);
            System.exit(1);
        }
        String[] args = redisServerConfig.split(":");
        int port = 6379;
        if (args.length == 2) {
            try {
                port = Integer.parseInt(args[1]);
            } catch (NumberFormatException e) {
                logger.error("port for Redis server is malformed: " + redisServerConfig);
            }
        }

        poolForWrites = new Pair<String, JedisPool>(args[0] + ":" + port,
                new JedisPool(jedisPoolConfig, args[0], port, Protocol.DEFAULT_TIMEOUT, redisServerPass));
        redisServerConfig = System.getProperty(BackplaneSystemProps.REDIS_SERVER_READS);
        redisServerPass = System.getProperty(BackplaneSystemProps.REDIS_SERVER_READS_PASSWORD);

        if (StringUtils.isEmpty(redisServerConfig)) {
            logger.error("cannot find configuration entry for " + BackplaneSystemProps.REDIS_SERVER_READS);
            System.exit(1);
        }

        String[] readServers = redisServerConfig.split(",");
        for (int i = 0; i < readServers.length; i++) {
            args = readServers[i].split(":");
            port = 6379;
            if (args.length == 2) {
                try {
                    port = Integer.parseInt(args[1]);
                    //currentRedisServerForReads[i] = args[0];
                    poolForReads
                            .add(new Pair<String, JedisPool>(args[0] + ":" + port, new JedisPool(jedisPoolConfig,
                                    args[0], port, Protocol.DEFAULT_TIMEOUT, redisServerPass)));
                } catch (NumberFormatException e) {
                    logger.error("invalid Redis server configuration: " + redisServerConfig);
                    System.exit(1);
                }
            }
        }

    }

    private Pair<String, JedisPool> getWritePool() {
        return poolForWrites;
    }

    private Pair<String, JedisPool> getReadPool() {
        Random random = new Random();
        return poolForReads.get(random.nextInt(poolForReads.size()));
    }

    private Jedis getJedisFromPool(Pair<String, JedisPool> poolInfo) {
        try {
            logger.debug("attempting to get resource from pool");
            Jedis jedis = poolInfo.getRight().getResource();
            synchronized (jedis) {
                checkedOutJedises.put(jedis, poolInfo.getRight());
                logger.debug(
                        "jedis " + jedis.getClient().getHost() + " checked out from pool " + poolInfo.getLeft());
            }
            return jedis;
        } catch (RuntimeException e) {
            logger.warn("error retrieving connection to redis pool " + poolInfo.getLeft() + " : " + e.getMessage());
            throw e;
        }
    }

    private ConcurrentHashMap<Jedis, JedisPool> checkedOutJedises = new ConcurrentHashMap<Jedis, JedisPool>();
    private final Gauge checkedOutJedisesCounterGauge = Metrics.newGauge(
            new MetricName("redis", this.getClass().getName().replace(".", "_"), "map_db_connections"),
            new Gauge<Integer>() {
                @Override
                public Integer value() {
                    return checkedOutJedises.size();
                }
            });

}