一次线上环境应用使用jedis客户端分片功能实践

背景

线上有一台应用使用单机redis作为缓存,在一次架构巡检时发现此单机使用redis在业务高峰期,接收请求的qps达到6+w/s,考虑到后期业务的增长,应用的稳定性,需要调整架构设计,解决此性能问题。

方案

该应用业务特点为,业务高峰期并发请求高,缓存内存占用量较低(大约为5G)。目前应用部署在云平台上,若申请redis集群最小容量为256G,存在严重资源浪费。决定新增一台redis,在jedis客户端使用ShardedJedisPool根据key做hash分片路由到两台单机redis上,降低单机redis的qps的方案。

具体实现

1.加入maven依赖

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

2.代码实现

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;

    /**
     * 设置单个值
     * @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);
                }
            });
    }

    /**
     * 获取单个值
     *
     * @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转字节
     *
     * @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转字节
     *
     * @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;
    }

}

踩坑经历

jedis的2.10.2和3.x版本在释放jedis连接池的写法不一样。若maven依赖jedis版本2.10.2,但代码使用官方3.x的写法,在高并发的请求下会出现连接池不能释放的问题。具体可以参考官方demo:https://github.com/redis/jedis/

实践结果

目前应用已稳定运行半年

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容