一次線上環(huán)境應(yīng)用使用jedis客戶端分片功能實(shí)踐

背景

線上有一臺(tái)應(yīng)用使用單機(jī)redis作為緩存,在一次架構(gòu)巡檢時(shí)發(fā)現(xiàn)此單機(jī)使用redis在業(yè)務(wù)高峰期,接收請(qǐng)求的qps達(dá)到6+w/s,考慮到后期業(yè)務(wù)的增長(zhǎng),應(yīng)用的穩(wěn)定性,需要調(diào)整架構(gòu)設(shè)計(jì),解決此性能問(wèn)題。

方案

該應(yīng)用業(yè)務(wù)特點(diǎn)為,業(yè)務(wù)高峰期并發(fā)請(qǐng)求高,緩存內(nèi)存占用量較低(大約為5G)。目前應(yīng)用部署在云平臺(tái)上,若申請(qǐng)redis集群最小容量為256G,存在嚴(yán)重資源浪費(fèi)。決定新增一臺(tái)redis,在jedis客戶端使用ShardedJedisPool根據(jù)key做hash分片路由到兩臺(tái)單機(jī)redis上,降低單機(jī)redis的qps的方案。

具體實(shí)現(xiàn)

1.加入maven依賴

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.10.2</version>
</dependency>

2.代碼實(shí)現(xiàn)

public interface ShardedJedisAction<T> {

    T doAction(ShardedJedis paramShardedJedis);

}
public interface ShardedJedisClient {

    <T> T execute(ShardedJedisAction<T> action);

}
public class ShardedJedisClientImpl implements ShardedJedisClient{

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

    private static final ScheduledExecutorService shardedJedisPoolMonitorExecutor =
            Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder()
                    .setNameFormat("ShardedJedisPoolMonitor-%d").setDaemon(false).build());

    private ShardedJedisPool pool;

    public void init(){
        shardedJedisPoolMonitorExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                logger.info("ShardedJedisPoolMonitor enable,NumActive:{},NumIdle:{},NumWaiters:{}",pool.getNumActive(),pool.getNumIdle(),pool.getNumWaiters());
            }
        },60,15, TimeUnit.SECONDS);
    }

    @Override
    public <T> T execute(ShardedJedisAction<T> action) {
        T result = null;
        try {
            result = executeAction(action);
        } catch (Exception e) {
            logger.error("ShardedJedisClient execute error:{}",e);
        }
        return result;
    }

    private <T> T executeAction(ShardedJedisAction<T> action) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = pool.getResource();
            Object object = action.doAction(shardedJedis);
            return (T)object;
        } catch (JedisConnectionException jex) {
            if (shardedJedis != null) {
                try {
                    pool.returnBrokenResource(shardedJedis);
                } catch (Exception ex) {
                    logger.warn("Can not return broken resource.", ex);
                }
                shardedJedis = null;
            }
            throw jex;
        }
        finally{
            if (shardedJedis != null) {
                try {
                    pool.returnResource(shardedJedis);
                } catch (Exception ex) {
                    logger.warn("Can not return resource.", ex);
                }
            }
        }
    }

    public ShardedJedisPool getPool() {
        return pool;
    }

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

    public void destroy() {
        shardedJedisPoolMonitorExecutor.shutdown();
        pool.destroy();
    }
}
public class ShardedJedisTemplate {

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

    private ShardedJedisClient shardedClient;

    private RedisSerializer keySerializer;

    private RedisSerializer valueSerializer;

    /**
     * 設(shè)置單個(gè)值
     * @param key
     * @param value
     * @return
     */
    public void set(final String key,final Object value){
            final byte[] rawKey = rawKey(key);
            final byte[] rawValue = rawValue(value);
            shardedClient.execute(new ShardedJedisAction<String>() {
                @Override
                public String doAction(ShardedJedis shardedJedis) {
                    logger.debug("key:{},value:{},action:set,redis ShardInfo host:{},port{}", key, JSON.toJSONString(value), shardedJedis.getShardInfo(rawKey).getHost(), shardedJedis.getShardInfo(rawKey).getPort());
                    return shardedJedis.set(rawKey, rawValue);
                }
            });
    }

    /**
     * 獲取單個(gè)值
     *
     * @param key
     * @return
     */
    public <T> T get(String key) {
            final byte[] rawKey = rawKey(key);
            byte[] result = shardedClient.execute(new ShardedJedisAction<byte[]>() {
                @Override
                public byte[] doAction(ShardedJedis shardedJedis) {
                    byte[] rawValue = shardedJedis.get(rawKey);
                    logger.debug("key:{},value:{},action:get,redis ShardInfo host:{},port{}",key, JSON.toJSONString(rawValue),shardedJedis.getShardInfo(rawKey).getHost(),shardedJedis.getShardInfo(rawKey).getPort());
                    return rawValue;
                }
            });
            return (T) deserializeValue(result);
    }

    /**
     * key轉(zhuǎn)字節(jié)
     *
     * @param key
     * @return
     */
    private byte[] rawKey(String key) {
        Assert.notNull(key, "non null key required");
        if (keySerializer == null) {
            return key.getBytes();
        }
        return keySerializer.serialize(key);
    }

    /**
     * value轉(zhuǎn)字節(jié)
     *
     * @param value
     * @return
     */
    private byte[] rawValue(Object value) {
        if (valueSerializer == null && value instanceof byte[]) {
            return (byte[]) value;
        }
        return valueSerializer.serialize(value);
    }

    private Object deserializeValue(byte[] value) {
        if (valueSerializer == null) {
            return value;
        }
        return valueSerializer.deserialize(value);
    }

    public ShardedJedisClient getShardedClient() {
        return shardedClient;
    }

    public void setShardedClient(ShardedJedisClient shardedClient) {
        this.shardedClient = shardedClient;
    }

    public RedisSerializer getKeySerializer() {
        return keySerializer;
    }

    public void setKeySerializer(RedisSerializer keySerializer) {
        this.keySerializer = keySerializer;
    }

    public RedisSerializer getValueSerializer() {
        return valueSerializer;
    }

    public void setValueSerializer(RedisSerializer valueSerializer) {
        this.valueSerializer = valueSerializer;
    }
}
@Slf4j
@Configuration
public class ShardedJedisConfiguration {

    @Value("#{'${redis.uri.list}'.split(',')}")
    private List<String> uriList ;

    @Value("{redis.pool.minIdle")
    private int minIdle;

    @Value("${redis.pool.maxIdle}")
    private  int maxIdle;

    @Value("${redis.pool.maxTotal}")
    private  int maxTotal;

    @Value("${redis.pool.maxWaitMillis}")
    private  int maxWaitMillis;

    @Value("${redis.pool.testOnBorrow}")
    private Boolean testOnBorrow;

    @Value("${redis.pool.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${redis.pool.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;

    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxTotal(maxTotal);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setTestOnBorrow(testOnBorrow);
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        return jedisPoolConfig;
    }

    @Bean
    public ShardedJedisPool shardedJedisPool(@Qualifier("jedisPoolConfig") JedisPoolConfig jedisPoolConfig){
        List<JedisShardInfo> jedisShardInfos = uriList.stream().map(uri->new JedisShardInfo(uri)).collect(Collectors.toList());
        return new ShardedJedisPool(jedisPoolConfig, jedisShardInfos);
    }


    @Bean(name = "shardedJedisClient")
    public ShardedJedisClient shardedJedisClient(@Qualifier("shardedJedisPool") ShardedJedisPool shardedJedisPool){
        ShardedJedisClientImpl shardedJedisClient = new ShardedJedisClientImpl();
        shardedJedisClient.setPool(shardedJedisPool);
        return shardedJedisClient;
    }

    @Bean(name = "shardedRedisTemplate")
    public ShardedJedisTemplate shardedRedisTemplate(@Qualifier("shardedJedisClient") ShardedJedisClient shardedJedisClient) {
        ShardedJedisTemplate shardedJedisTemplate = new ShardedJedisTemplate();
        shardedJedisTemplate.setShardedClient(shardedJedisClient);
        shardedJedisTemplate.setKeySerializer(new StringRedisSerializer());
        shardedJedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        return shardedJedisTemplate;
    }

}

踩坑經(jīng)歷

jedis的2.10.2和3.x版本在釋放jedis連接池的寫法不一樣。若maven依賴jedis版本2.10.2,但代碼使用官方3.x的寫法,在高并發(fā)的請(qǐng)求下會(huì)出現(xiàn)連接池不能釋放的問(wèn)題。具體可以參考官方demo:https://github.com/redis/jedis/

實(shí)踐結(jié)果

目前應(yīng)用已穩(wěn)定運(yùn)行半年

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容