關於JedisCluster不支持Pipeline操作的解決方案

原文鏈接:https://blog.csdn.net/EndTheme_Xin/article/details/84623063

版權聲明:本文爲博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/EndTheme_Xin/article/details/84623063
一、背景
業務需要,把redis單結點改爲集羣,在對代碼進行測試的時候發現了,原本使用jedis的批量操作pipeline,到了集羣的時候不可用了。報了org.springframework.data.redis.connection.jedis.JedisClusterConnection的錯誤。

即pipeline是不支持在集羣模式下使用的。

於是乎,只能技能PipelineBase接口,重寫這個方法了。

二、代碼

JedisCluster.java(獲取jedis集羣實體類)
 

@Bean
    public JedisCluster getJedisCluster() {
        String[] serverArray = clusterNodes.split(",");//獲取服務器數組
        Set<HostAndPort> nodes = new HashSet<>();
 
        for (String ipPort : serverArray) {
            String[] ipPortPair = ipPort.split(":");
            nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
        }
 
        return new JedisCluster(nodes, poolConfig());
    }
JedisClusterPipeline.java(重寫的pipeline類)
package com.hqjy.msg.util;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.*;
 
/**
 * @program: message
 * @description: JedisClusterPipeLine
 * @author: Irving Wei
 * @create: 2018-11-27 10:22
 **/
 
public class JedisClusterPipeline extends PipelineBase implements Closeable {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(JedisClusterPipeline.class);
 
    // 部分字段沒有對應的獲取方法,只能採用反射來做
    // 你也可以去繼承JedisCluster和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");
    }
 
    private JedisSlotBasedConnectionHandler connectionHandler;
    private JedisClusterInfoCache clusterInfoCache;
    private Queue<Client> clients = new LinkedList<Client>();   // 根據順序存儲每個命令對應的Client
    private Map<JedisPool, Jedis> jedisMap = new HashMap<>();   // 用於緩存連接
    private boolean hasDataInBuf = false;   // 是否有數據在緩存區
 
    /**
     * 根據jedisCluster實例生成對應的JedisClusterPipeline
     * @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<Object>();
 
        innerSync(responseList);
 
        return responseList;
    }
 
    private void innerSync(List<Object> formatted) {
        HashSet<Client> clientSet = new HashSet<Client>();
 
        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();
        }
    }
 
    @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;
    }
 
    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) {
            LOGGER.error("get value fail", e);
 
            throw new RuntimeException(e);
        }
    }
 
}

XXXService.java(使用)

@Autowired
    private JedisCluster jc;
 
@RedisIdxConnection
    public synchronized void pipeline(String key, List list) {
        // 如果要操作的list沒有值,則直接返回
        if (!(list.size() > 0)) {
            return;
        }
        JedisClusterPipeline jcp = JedisClusterPipeline.pipelined(jc);
        jcp.refreshCluster();
        List<Object> batchResult = null;
        try {
            for (int i = 0; i < list.size(); i++) {
                // for循環的內容根據自己的業務進行修改,核心就是用jcp加到數組再一起同步到redis實現批量操作
                Map map = (Map) list.get(i);
                String msg = (String) map.get("value");
                long time = (long) map.get("score");
                String zkey = (String) map.get(Constant.REDIS_KEY);
                jcp.zadd(zkey, time, msg);
            }
            jcp.sync();
            batchResult = jcp.syncAndReturnAll();
        } finally {
            jcp.close();
        }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章