Почему jedis не является поточно-ориентированным, хотя используется `synchronized`? - PullRequest
0 голосов
/ 06 мая 2019

У меня есть три потока: A, B и C.

A выполняет фрагмент кода

synchronized (redisClient.getJedis()) {
    List<String> values = redisClient.getJedis().mget(keys.toArray(new String[keys.size()]));
}

B выполняет фрагмент кода

synchronized (redisClient.getJedis()) {
    List<String> values = redisClient.getJedis().mget(keys.toArray(new String[keys.size()]));
}

C выполняет фрагмент кода

synchronized (redisClient.getJedis()) {
    String result = redisClient.getJedis().get("FEATUREID#TEST");
}

redisClient.getJedis() возвращает экземпляр jedis.

Иногда я обнаруживаю, что A получает результат C, а B получает результат A, а C получает результат B, когдатри используют один и тот же экземпляр jedis.

Но, как вы видите, я использовал synchronized, почему это должно происходить?

RedisClient получается из RedisClientFactory с использованием RedisClient redisClient = RedisClientFactory.obtainRedisClient(REDIS_GROUP); REDIS_GROUPявляется именем группы redis.

Класс RedisClient выглядит следующим образом:

import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class RedisClient {

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

    private Jedis jedis;

    private JedisPoolProxy jedisPoolProxy;

    public RedisClient(Jedis jedis, JedisPoolProxy jedisPoolProxy) {
        this.jedis = jedis;
        this.jedisPoolProxy = jedisPoolProxy;
    }

    public void returnResource() {
        try {
            if (jedis != null && jedisPoolProxy != null) {
                jedisPoolProxy.getPool().returnResource(jedis);
            }
        } catch (Exception e) {
            logger.error("", e);

            if (jedis != null && jedisPoolProxy != null) {
                jedisPoolProxy.getPool().returnBrokenResource(jedis);
            }
        } finally {
            jedis = null;
            jedisPoolProxy = null;
        }
    }

    public Jedis getJedis() {
        return jedis;
    }
}

class JedisPoolProxy {

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

    private static final long BLOCKED_TIME = 10 * 1000;

    String server;

    JedisPool pool;

    AtomicLong blockedTime = new AtomicLong(-1);

    public JedisPoolProxy(String server, JedisPool pool) {
        this.server = server;
        this.pool = pool;
    }

    public boolean isBlocked() {
        if (blockedTime.get() < 0) {
            return false;
        }

        if (System.currentTimeMillis() - blockedTime.get() > BLOCKED_TIME) {
            logger.info("server " + server + " recovers");
            blockedTime.set(-1);
            return false;
        }

        return true;
    }

    public String getServer() {
        return server;
    }

    public void setServer(String server) {
        this.server = server;
    }

    public JedisPool getPool() {
        return pool;
    }

    public void setPool(JedisPool pool) {
        this.pool = pool;
    }

    public long getBlockedTime() {
        return blockedTime.get();
    }

    public void setBlockedTime(long blockedTime) {
        logger.info("server " + server + " is blocked");
        this.blockedTime.set(blockedTime);
    }
}

Класс RedisClientFactory выглядит следующим образом:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.configuration2.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@SuppressWarnings("Duplicates")
public class RedisClientFactory {

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

    private static final int MAX_TOTAL = 30;
    private static final int MAX_IDLE = 10;
    private static final int MAX_WAIT_MILLIS = 1000;
    private static final boolean TEST_ON_BORROW = false;
    private static final boolean TEST_WHILE_IDLE = false;
    private static final int TIMEOUT = 5000;

    private static final String CONFIG_FILE = "redis.properties";

    private static volatile boolean init = false;

    private static Map<String, List<JedisPoolProxy>> groupedJedisPools = new HashMap<String, List<JedisPoolProxy>>();

    public static RedisClient obtainRedisClient(String group) {
        if (!init) {
            init();
        }

        if (groupedJedisPools.get(group) == null) {
            return null;
        }

        return obtainRedisClient(groupedJedisPools.get(group));
    }

    private static RedisClient obtainRedisClient(List<JedisPoolProxy> jedisPools) {
        int idx = (int)(Math.random() * jedisPools.size());

        for (int index = idx; index < jedisPools.size(); index++) {
            if (!jedisPools.get(index).isBlocked()) {
                try {
                    Jedis x = jedisPools.get(index).getPool().getResource();

                    if (x != null) {
                        return new RedisClient(x, jedisPools.get(index));
                    }
                } catch (Throwable e) {
                    logger.error("", e);
                    jedisPools.get(index).setBlockedTime(System.currentTimeMillis());
                }
            }
        }

        for (int index = 0; index < idx; index++) {
            if (!jedisPools.get(index).isBlocked()) {
                try {
                    Jedis x = jedisPools.get(index).getPool().getResource();

                    if (x != null) {
                        return new RedisClient(x, jedisPools.get(index));
                    }
                } catch (Throwable e) {
                    logger.error("", e);
                    jedisPools.get(index).setBlockedTime(System.currentTimeMillis());
                }
            }
        }

        return null;
    }

    private static void init() {
        if (!init) {
            synchronized (RedisClientFactory.class) {
                if (!init) {
                    try {
                        Configuration configuration = ConfigLoadAssist.propConfig(CONFIG_FILE);

                        String groups = configuration.getString("groups");
                        if (StringAssist.isBlank(groups)) {
                            throw new RuntimeException("no groups");
                        }

                        String[] groupArray = groups.split(",");
                        for (String group : groupArray) {
                            logger.info("generate jedispools for group: " + group);
                            Configuration subConfiguration = configuration.subset(group);
                            groupedJedisPools.put(group, generateJedisPools(subConfiguration));
                        }
                    } catch (Exception e) {
                        logger.error("", e);
                        throw new RuntimeException(e);
                    } finally {
                        init = true;
                    }
                }
            }
        }
    }

    private static List<JedisPoolProxy> generateJedisPools(Configuration configuration) {
        List<JedisPoolProxy> jedisPools = new ArrayList<JedisPoolProxy>();

        if (configuration == null) {
            return jedisPools;
        }

        String servers = configuration.getString("redis.servers");
        if (StringAssist.isBlank(servers)) {
            logger.error("no redis servers");
            return jedisPools;
        }

        String[] serverArray = servers.split(",");

        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxActive(MAX_TOTAL);
        config.setMaxIdle(MAX_IDLE);
        config.setMaxWait(MAX_WAIT_MILLIS);
        config.setTestOnBorrow(TEST_ON_BORROW);
        config.setTestWhileIdle(TEST_WHILE_IDLE);

        for (String server : serverArray) {
            try {
                String host = server.split(":")[0];
                int port = Integer.parseInt(server.split(":")[1]);

                JedisPoolProxy proxy = new JedisPoolProxy(server, new JedisPool(config, host, port, TIMEOUT));

                jedisPools.add(proxy);
            } catch (Exception e) {
                logger.error(server, e);
            }
        }

        logger.info("jedisPools size : " + jedisPools.size());

        return jedisPools;
    }
}
...