Package org.hibernate.cache.redis.jedis

Source Code of org.hibernate.cache.redis.jedis.JedisClient

/*
* Copyright 2011-2013 the original author or authors.
*
* 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 org.hibernate.cache.redis.jedis;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.cache.redis.serializer.RedisSerializer;
import org.hibernate.cache.redis.serializer.SerializationTool;
import org.hibernate.cache.redis.serializer.SnappyRedisSerializer;
import org.hibernate.cache.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Transaction;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* RedisClient implements using Jedis library
* <p/>
* 참고 : https://github.com/xetorthio/org.hibernate.cache.redis.jedis/wiki/AdvancedUsage
*
* @author 배성혁 ( sunghyouk.bae@gmail.com )
* @since 13. 4. 9 오후 10:20
*/
@Slf4j
public class JedisClient {

    public static final int DEFAULT_EXPIRY_IN_SECONDS = 120;
    public static final String DEFAULT_REGION_NAME = "hibernate";

    @Getter
    private final JedisPool jedisPool;

    @Getter
    @Setter
    private int expiryInSeconds;

    private final StringRedisSerializer regionSerializer = new StringRedisSerializer();
    private final StringRedisSerializer keySerializer = new StringRedisSerializer();
    private final RedisSerializer<Object> valueSerializer = new SnappyRedisSerializer<Object>();

    public JedisClient() {
        this(new JedisPool("localhost"), DEFAULT_EXPIRY_IN_SECONDS);
    }

    public JedisClient(JedisPool jedisPool) {
        this(jedisPool, DEFAULT_EXPIRY_IN_SECONDS);
    }

    /**
     * initialize JedisClient instance
     *
     * @param jedisPool       JedisPool instance
     * @param expiryInSeconds expiration in seconds
     */
    public JedisClient(JedisPool jedisPool, int expiryInSeconds) {
        log.debug("JedisClient created. jedisPool=[{}], expiryInSeconds=[{}]", jedisPool, expiryInSeconds);

        this.jedisPool = jedisPool;
        this.expiryInSeconds = expiryInSeconds;
    }

    /**
     * ping test for server alive
     */
    public String ping() {
        return run(new JedisCallback<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.ping();
            }
        });
    }

    /**
     * get Redis db size
     */
    public Long dbSize() {
        return run(new JedisCallback<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.dbSize();
            }
        });
    }

    /**
     * confirm the specified cache item in specfied region
     *
     * @param region region name
     * @param key    cache key
     */
    public boolean exists(final String region, final Object key) {
        final byte[] rawRegion = rawRegion(region);
        final byte[] rawKey = rawKey(key);

        return run(new JedisCallback<Boolean>() {
            @Override
            public Boolean execute(Jedis jedis) {
                return jedis.hexists(rawRegion, rawKey);
            }
        });
    }

    /**
     * Get cache
     *
     * @param region region name
     * @param key    cache key
     * @return return cached entity, if not exists return null.
     */
    public Object get(final String region, final Object key) {
        return get(region, key, 0);
    }

    /**
     * Get cache
     *
     * @param region              region name
     * @param key                 cache key
     * @param expirationInSeconds expiration timeout in seconds
     * @return return cached entity, if not exists return null.
     */
    public Object get(final String region, final Object key, final int expirationInSeconds) {
        final byte[] rawRegion = rawRegion(region);
        final byte[] rawKey = rawKey(key);

        // NOTE: expire 된 캐시 정보라면 삭제하고, null 값을 반환합니다.
        if (expirationInSeconds > 0 && isExpired(region, key)) {
            runWithPipeline(new JedisPipelinedCallback() {
                @Override
                public void execute(Pipeline pipeline) {
                    final byte[] rawZkey = rawZkey(region);
                    pipeline.zrem(rawZkey, rawKey);
                    pipeline.hdel(rawRegion, rawKey);
                }
            });
            return null;
        }

        byte[] rawValue = run(new JedisCallback<byte[]>() {
            @Override
            public byte[] execute(Jedis jedis) {
                return jedis.hget(rawRegion, rawKey);
            }
        });

        // after get, update expiration time
        if (rawValue != null && rawValue.length > 0) {
            if (expirationInSeconds > 0 && !region.contains("UpdateTimestampsCache")) {
                run(new JedisCallback<Object>() {
                    @Override
                    public Object execute(Jedis jedis) {
                        final byte[] rawZkey = rawZkey(region);
                        final long score = System.currentTimeMillis() + expirationInSeconds * 1000L;
                        return jedis.zadd(rawZkey, score, rawKey);
                    }
                });
            }
        }

        return deserializeValue(rawValue);
    }

    private Boolean isExpired(final String region, final Object key) {
        final byte[] rawZkey = rawZkey(region);
        final byte[] rawKey = rawKey(key);

        Double timestamp = run(new JedisCallback<Double>() {
            @Override
            public Double execute(Jedis jedis) {
                return jedis.zscore(rawZkey, rawKey);
            }
        });
        return timestamp != null && System.currentTimeMillis() > timestamp.longValue();
    }

    /**
     * retrieve all cached items in specified region
     *
     * @param region region
     * @return collection of cached items
     */
    public Set<Object> keysInRegion(String region) {
        try {
            final byte[] rawRegion = rawRegion(region);
            Set<byte[]> rawKeys = run(new JedisCallback<Set<byte[]>>() {
                @Override
                public Set<byte[]> execute(Jedis jedis) {
                    return jedis.hkeys(rawRegion);
                }
            });

            if (rawKeys != null)
                return deserializeKeys(rawKeys);
        } catch (Exception ignored) { }
        return new HashSet<Object>();
    }

    /**
     * get cache count in region
     *
     * @param region region
     * @return cache item count in region
     */

    public Long keySizeInRegion(final String region) {
        final byte[] rawRegion = rawRegion(region);
        return run(new JedisCallback<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.hlen(rawRegion);
            }
        });
    }

    /**
     * get all cached items in specified region
     *
     * @param region region name
     * @return map of keys and all cached items in specified region
     */
    public Map<Object, Object> hgetAll(String region) {
        final byte[] rawRegion = rawRegion(region);
        Map<byte[], byte[]> rawMap = run(new JedisCallback<Map<byte[], byte[]>>() {
            @Override
            public Map<byte[], byte[]> execute(Jedis jedis) {
                return jedis.hgetAll(rawRegion);
            }
        });

        Map<Object, Object> map = new HashMap<Object, Object>();
        for (Map.Entry<byte[], byte[]> entry : rawMap.entrySet()) {
            Object key = deserializeKey(entry.getKey());
            Object value = deserializeValue(entry.getValue());
            map.put(key, value);
        }
        return map;
    }

    /**
     * multiple get cache items in specified region
     *
     * @param region region name
     * @param keys   cache key collection to retrieve
     * @return cache items
     */
    public List<Object> mget(final String region, final Collection<?> keys) {
        final byte[] rawRegion = rawRegion(region);
        final byte[][] rawKeys = rawKeys(keys);

        List<byte[]> rawValues = run(new JedisCallback<List<byte[]>>() {
            @Override
            public List<byte[]> execute(Jedis jedis) {
                return jedis.hmget(rawRegion, rawKeys);
            }
        });
        return deserializeValues(rawValues);
    }

    /**
     * save cache
     *
     * @param region region name
     * @param key    cache key to save
     * @param value  cache value to save
     */
    public void set(String region, Object key, Object value) {
        set(region, key, value, expiryInSeconds, TimeUnit.SECONDS);
    }

    /**
     * save cache item
     *
     * @param region           region name
     * @param key              cache key to save
     * @param value            cache value to save
     * @param timeoutInSeconds expire timeout in seconds
     */
    public void set(String region, Object key, Object value, long timeoutInSeconds) {
        set(region, key, value, timeoutInSeconds, TimeUnit.SECONDS);
    }

    /**
     * save cache item
     *
     * @param region  region name
     * @param key     cache key to save
     * @param value   cache value to save
     * @param timeout expire timeout
     * @param unit    expire timeout unit
     */
    public void set(final String region, final Object key, final Object value, long timeout, TimeUnit unit) {
        final byte[] rawRegion = rawRegion(region);
        final byte[] rawKey = rawKey(key);
        final byte[] rawValue = rawValue(value);
        final int seconds = (int) unit.toSeconds(timeout);

        runWithTx(new JedisTransactionalCallback() {
            @Override
            public void execute(Transaction tx) {
                tx.hset(rawRegion, rawKey, rawValue);
                if (seconds > 0 && !region.contains("UpdateTimestampsCache")) {
                    final byte[] rawZkey = rawZkey(region);
                    final long score = System.currentTimeMillis() + seconds * 1000L;
                    tx.zadd(rawZkey, score, rawKey);
                }
            }
        });
    }

    /**
     * delete cache item which is expired in region
     *
     * @param region region name
     */
    public void expire(final String region) {

        try {
            final byte[] rawZkey = rawZkey(region);
            final byte[] rawRegion = rawRegion(region);
            final long score = System.currentTimeMillis();

            // get key which score is less than current time
            final Set<byte[]> rawKeys = run(new JedisCallback<Set<byte[]>>() {
                @Override
                public Set<byte[]> execute(Jedis jedis) {
                    return jedis.zrangeByScore(rawZkey, 0, score);
                }
            });

            if (rawKeys != null && rawKeys.size() > 0) {
                log.debug("delete expired cache item in region[{}] expire time=[{}]", region, score);

                runWithPipeline(new JedisPipelinedCallback() {
                    @Override
                    public void execute(Pipeline pipeline) {
                        // delete cache item
                        for (final byte[] rawKey : rawKeys) {
                            pipeline.hdel(rawRegion, rawKey);
                        }
                        pipeline.zremrangeByScore(rawZkey, 0, score);
                    }
                });

            }
        } catch (Exception ignored) {
            log.warn("Error in Cache Expiration Method.", ignored);
        }
    }

    /**
     * delete cache item in specified region.
     *
     * @param region region name
     * @param key    cache key to delete
     * @return count of deleted key
     */
    public Long del(final String region, final Object key) {
        final byte[] rawRegion = rawRegion(region);
        final byte[] rawKey = rawKey(key);
        final byte[] rawZkey = rawZkey(region);

        runWithTx(new JedisTransactionalCallback() {
            @Override
            public void execute(Transaction tx) {
                tx.hdel(rawRegion, rawKey);
                tx.zrem(rawZkey, rawKey);
            }
        });

        return 1L;
    }

    /**
     * multiplu delete cache items in specified region
     *
     * @param keys key collection to delete
     */
    public void mdel(final String region, final Collection<?> keys) {

        final byte[] rawRegion = rawRegion(region);
        final byte[] rawZkey = rawZkey(region);
        final byte[][] rawKeys = rawKeys(keys);

        runWithTx(new JedisTransactionalCallback() {
            @Override
            public void execute(Transaction tx) {
                for (byte[] rawKey : rawKeys) {
                    tx.hdel(rawRegion, rawKey);
                    tx.zrem(rawZkey, rawKey);
                }
            }
        });
    }

    /**
     * delete region
     *
     * @param region region name to delete
     */
    public void deleteRegion(final String region) throws JedisCacheException {
        log.debug("delete region region=[{}]", region);

        final byte[] rawRegion = rawRegion(region);
        final byte[] rawZkey = rawZkey(region);

        runWithTx(new JedisTransactionalCallback() {
            @Override
            public void execute(Transaction tx) {
                tx.del(rawRegion);
                tx.del(rawZkey);
            }
        });
    }

    /**
     * flush db
     */
    public String flushDb() {
        log.info("Flush DB...");

        return run(new JedisCallback<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.flushDB();
            }
        });
    }

    /**
     * serialize cache key
     */
    private byte[] rawKey(final Object key) {
        return keySerializer.serialize(key.toString());
    }

    @SuppressWarnings("unchecked")
    private byte[][] rawKeys(final Collection<?> keys) {
        byte[][] rawKeys = new byte[keys.size()][];
        int i = 0;
        for (Object key : keys) {
            rawKeys[i++] = rawKey(key);
        }
        return rawKeys;
    }

    /**
     * Serialize expiration region name
     */
    private byte[] rawZkey(final String region) {
        return rawRegion("z:" + region);
    }

    /**
     * serializer region name
     */
    private byte[] rawRegion(final String region) {
        return regionSerializer.serialize(region);
    }

    /**
     * deserialize key
     */
    private Object deserializeKey(final byte[] rawKey) {
        return keySerializer.deserialize(rawKey);
    }

    /**
     * serializer cache value
     */
    private byte[] rawValue(final Object value) {
        try {
            return valueSerializer.serialize(value);
        } catch (Exception e) {
            log.warn("value를 직렬화하는데 실패했습니다. value=" + value, e);
            return null;
        }
    }

    /**
     * deserialize raw value
     */
    private Object deserializeValue(final byte[] rawValue) {
        return valueSerializer.deserialize(rawValue);
    }

    /**
     * execute the specified callback
     */
    private <T> T run(final JedisCallback<T> callback) {

        Jedis jedis = jedisPool.getResource();
        try {
            return callback.execute(jedis);
        } finally {
            jedisPool.returnResource(jedis);
        }
    }

    /**
     * execute the specified callback under transaction
     * HINT: https://github.com/xetorthio/org.hibernate.cache.redis.jedis/wiki/AdvancedUsage
     *
     * @param callback executable instance under transaction
     */
    private List<Object> runWithTx(final JedisTransactionalCallback callback) {

        Jedis jedis = jedisPool.getResource();
        try {
            Transaction tx = jedis.multi();
            callback.execute(tx);
            return tx.exec();
        } finally {
            jedisPool.returnResource(jedis);
        }
    }

    /**
     * execute the specified callback under Redis Pipeline
     * HINT: https://github.com/xetorthio/org.hibernate.cache.redis.jedis/wiki/AdvancedUsage
     *
     * @param callback executable instance unider Pipeline
     */
    private void runWithPipeline(final JedisPipelinedCallback callback) {
        final Jedis jedis = jedisPool.getResource();
        try {
            final Pipeline pipeline = jedis.pipelined();
            callback.execute(pipeline);
            // use #sync(), not #exec()
            pipeline.sync();
        } finally {
            jedisPool.returnResource(jedis);
        }
    }

    /**
     * deserize the specified raw key set.
     *
     * @return original key set.
     */
    private Set<Object> deserializeKeys(final Set<byte[]> rawKeys) {
        Set<Object> keys = new HashSet<Object>();
        for (byte[] rawKey : rawKeys) {
            keys.add(deserializeKey(rawKey));
        }
        return keys;
    }

    /**
     * deseriaize the specified raw value collection
     *
     * @return collection of original value
     */
    private List<Object> deserializeValues(final List<byte[]> rawValues) {
        return SerializationTool.deserialize(rawValues, valueSerializer);
    }
}
TOP

Related Classes of org.hibernate.cache.redis.jedis.JedisClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.