Redis 批量操做之 pipeline

业务场景

最近项目中场景须要get一批key的value,由于redis的get操做(不仅仅是get命令)是阻塞的,若是循环取值的话,就算是内网,耗时也是巨大的。因此想到了redis的pipeline命令。java

pipeline简介

非pipeline:client一个请求,redis server一个响应,期间client阻塞redis

Pipeline:redis的管道命令,容许client将多个请求依次发给服务器(redis的客户端,如jedisCluster,lettuce等都实现了对pipeline的封装),过程当中而不须要等待请求的回复,在最后再一并读取结果便可。缓存

单机版

单机版比较简单,直接上代码服务器

//换成真实的redis实例
Jedis jedis = new Jedis();
//获取管道
Pipeline p = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
    p.get(i + "");
}
//获取结果
List<Object> results = p.syncAndReturnAll();

复制代码

集群版

由于 JedisCluster 自己不支持 pipeline ,因此咱们须要对 JedisCluster 进行一些封装。ide

仍是同样,直接上代码ui

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisMovedDataException;
import redis.clients.jedis.exceptions.JedisRedirectionException;
import redis.clients.util.JedisClusterCRC16;
import redis.clients.util.SafeEncoder;

import java.io.Closeable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BiConsumer;



@Slf4j
public class JedisClusterPipeline extends PipelineBase implements Closeable {


    /** * 用于获取 JedisClusterInfoCache */
    private JedisSlotBasedConnectionHandler connectionHandler;
    /** * 根据hash值获取链接 */
    private JedisClusterInfoCache clusterInfoCache;

    /** * 也能够去继承JedisCluster和JedisSlotBasedConnectionHandler来提供访问接口 * JedisCluster继承于BinaryJedisCluster * 在BinaryJedisCluster,connectionHandler属性protected修饰的,因此须要反射 * * * 而 JedisClusterInfoCache 属性在JedisClusterConnectionHandler中,可是这个类是抽象类, * 但它有一个实现类JedisSlotBasedConnectionHandler */
    private static final Field FIELD_CONNECTION_HANDLER;
    private static final Field FIELD_CACHE;
    static {
        FIELD_CONNECTION_HANDLER = getField(BinaryJedisCluster.class, "connectionHandler");
        FIELD_CACHE = getField(JedisClusterConnectionHandler.class, "cache");
    }


    /** * 根据顺序存储每一个命令对应的Client */
    private Queue<Client> clients = new LinkedList<>();
    /** * 用于缓存链接 * 一次pipeline过程当中使用到的jedis缓存 */
    private Map<JedisPool, Jedis> jedisMap = new HashMap<>();
    /** * 是否有数据在缓存区 */
    private boolean hasDataInBuf = false;

    /** * 根据jedisCluster实例生成对应的JedisClusterPipeline * 经过此方式获取pipeline进行操做的话必须调用close()关闭管道 * 调用本类里pipelineXX方法则不用close(),但建议最好仍是在finally里调用一下close() * @param * @return */
    public static JedisClusterPipeline pipelined(JedisCluster jedisCluster) {
        JedisClusterPipeline pipeline = new JedisClusterPipeline();
        pipeline.setJedisCluster(jedisCluster);
        return pipeline;
    }


    public JedisClusterPipeline() {
    }

    public void setJedisCluster(JedisCluster jedis) {
        connectionHandler = getValue(jedis, FIELD_CONNECTION_HANDLER);
        clusterInfoCache = getValue(connectionHandler, FIELD_CACHE);
    }

    /** * 刷新集群信息,当集群信息发生变动时调用 * @param * @return */
    public void refreshCluster() {
        connectionHandler.renewSlotCache();
    }

    /** * 同步读取全部数据. 与syncAndReturnAll()相比,sync()只是没有对数据作反序列化 */
    public void sync() {
        innerSync(null);
    }

    /** * 同步读取全部数据 并按命令顺序返回一个列表 * * @return 按照命令的顺序返回全部的数据 */
    public List<Object> syncAndReturnAll() {
        List<Object> responseList = new ArrayList<>();

        innerSync(responseList);

        return responseList;
    }


    @Override
    public void close() {
        clean();

        clients.clear();

        for (Jedis jedis : jedisMap.values()) {
            if (hasDataInBuf) {
                flushCachedData(jedis);
            }

            jedis.close();
        }

        jedisMap.clear();

        hasDataInBuf = false;
    }

    private void flushCachedData(Jedis jedis) {
        try {
            jedis.getClient().getAll();
        } catch (RuntimeException ex) {
        }
    }

    @Override
    protected Client getClient(String key) {
        byte[] bKey = SafeEncoder.encode(key);

        return getClient(bKey);
    }

    @Override
    protected Client getClient(byte[] key) {
        Jedis jedis = getJedis(JedisClusterCRC16.getSlot(key));

        Client client = jedis.getClient();
        clients.add(client);

        return client;
    }

    private Jedis getJedis(int slot) {
        JedisPool pool = clusterInfoCache.getSlotPool(slot);

        // 根据pool从缓存中获取Jedis
        Jedis jedis = jedisMap.get(pool);
        if (null == jedis) {
            jedis = pool.getResource();
            jedisMap.put(pool, jedis);
        }

        hasDataInBuf = true;
        return jedis;
    }



    public static void pipelineSetEx(String[] keys, String[] values, int[] exps,JedisCluster jedisCluster) {
        operate(new Command() {
            @Override
            public List execute() {
                JedisClusterPipeline p = pipelined(jedisCluster);
                for (int i = 0, len = keys.length; i < len; i++) {
                    p.setex(keys[i], exps[i], values[i]);
                }
                return p.syncAndReturnAll();
            }
        });
    }

    public static List<Map<String, String>> pipelineHgetAll(String[] keys,JedisCluster jedisCluster) {
        return operate(new Command() {
            @Override
            public List execute() {
                JedisClusterPipeline p = pipelined(jedisCluster);
                for (int i = 0, len = keys.length; i < len; i++) {
                    p.hgetAll(keys[i]);
                }
                return p.syncAndReturnAll();
            }
        });
    }



    public static List<Boolean> pipelineSismember(String[] keys, String members,JedisCluster jedisCluster) {
        return operate(new Command() {
            @Override
            public List execute() {
                JedisClusterPipeline p = pipelined(jedisCluster);
                for (int i = 0, len = keys.length; i < len; i++) {
                    p.sismember(keys[i], members);
                }
                return p.syncAndReturnAll();
            }
        });
    }



    public static <O> List pipeline(BiConsumer<O, JedisClusterPipeline> function, O obj,JedisCluster jedisCluster) {
        return operate(new Command() {
            @Override
            public List execute() {
                JedisClusterPipeline jcp = JedisClusterPipeline.pipelined(jedisCluster);
                function.accept(obj, jcp);
                return jcp.syncAndReturnAll();
            }
        });
    }



    private void innerSync(List<Object> formatted) {
        HashSet<Client> clientSet = new HashSet<>();

        try {
            for (Client client : clients) {
                // 在sync()调用时实际上是不须要解析结果数据的,可是若是不调用get方法,发生了JedisMovedDataException这样的错误应用是不知道的,所以须要调用get()来触发错误。
                // 其实若是Response的data属性能够直接获取,能够省掉解析数据的时间,然而它并无提供对应方法,要获取data属性就得用反射,不想再反射了,因此就这样了
                Object data = generateResponse(client.getOne()).get();
                if (null != formatted) {
                    formatted.add(data);
                }

                // size相同说明全部的client都已经添加,就不用再调用add方法了
                if (clientSet.size() != jedisMap.size()) {
                    clientSet.add(client);
                }
            }
        } catch (JedisRedirectionException jre) {
            if (jre instanceof JedisMovedDataException) {
                // if MOVED redirection occurred, rebuilds cluster's slot cache,
                // recommended by Redis cluster specification
                refreshCluster();
            }

            throw jre;
        } finally {
            if (clientSet.size() != jedisMap.size()) {
                // 全部尚未执行过的client要保证执行(flush),防止放回链接池后后面的命令被污染
                for (Jedis jedis : jedisMap.values()) {
                    if (clientSet.contains(jedis.getClient())) {
                        continue;
                    }

                    flushCachedData(jedis);
                }
            }

            hasDataInBuf = false;
            close();
        }
    }


    private static Field getField(Class<?> cls, String fieldName) {
        try {
            Field field = cls.getDeclaredField(fieldName);
            field.setAccessible(true);

            return field;
        } catch (NoSuchFieldException | SecurityException e) {
            throw new RuntimeException("cannot find or access field '" + fieldName + "' from " + cls.getName(), e);
        }
    }

    @SuppressWarnings({"unchecked" })
    private static <T> T getValue(Object obj, Field field) {
        try {
            return (T)field.get(obj);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            log.error("get value fail", e);

            throw new RuntimeException(e);
        }
    }

    private static <T> T operate(Command command) {
        try  {
            return command.execute();
        } catch (Exception e) {
            log.error("redis operate error");
            throw new RuntimeException(e);
        }
    }

    interface Command {
        /** * 具体执行命令 * * @param <T> * @return */
        <T> T execute();
    }

}


复制代码

使用demothis

public Object testPipelineOperate() {
        // String[] keys = {"dylan1","dylan2"};
        // String[] values = {"dylan1-v1","dylan2-v2"};
        // int[] exps = {100,200};
        // JedisClusterPipeline.pipelineSetEx(keys, values, exps, jedisCluster);
        long start = System.currentTimeMillis();

        List<String> keyList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            keyList.add(i + "");
        }
        // List<String> pipeline = JedisClusterPipeline.pipeline(this::getValue, keyList, jedisCluster);
        // List<String> pipeline = JedisClusterPipeline.pipeline(this::getHashValue, keyList, jedisCluster);
        String[] keys = {"dylan-test1", "dylan-test2"};

        List<Map<String, String>> all = JedisClusterPipeline.pipelineHgetAll(keys, jedisCluster);
        long end = System.currentTimeMillis();
        System.out.println("testPipelineOperate cost:" + (end-start));

        return Response.success(all);
    }

复制代码
相关文章
相关标签/搜索