Java实战——使用单例及管道优化Redis连接资源过多问题

Java实战——使用单例及管道优化Redis连接资源过多问题

前言:

不知不觉,距离自己高考已经过去了小8个年头啦,时光飞逝呀!祝大家都可以活成自己在当年想活成的样子,不忘初心!ok!进入正题,在开始正文前我们先简单介绍一下单例模式和Redis管道操作

在Java设计模式中,单例模式是一个比较简单且常用的一种软件设计模式,它的定义也很简单且直接:单例对象的类只能允许一个实例存在。

 

单例的实现主要是通过以下两个步骤:

  1. 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
  2. 在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

 

Redis管道操作:

很多人一直以来对 Redis 管道其实有一个误解,以为这是 Redis 服务器提供的一种加速 Redis的存取效率的能力。但是实际上 Redis 管道 (Pipeline) 本身并不是 Redis 服务器直接提供的技术,这个技术本质上是由客户端提供的,跟服务器没有什么直接的关系。

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  1. 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
  2. 服务端处理命令,并将结果返回给客户端。

比如我们完成3条命令操作:

客户端要经历了写-读 写-读 写-读 3个操作 3次IO 才完整地执行了多条指令。

Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。这样就可以大幅节省 IO 时间。管道中指令越多,效果越好。

 

需求背景:

最近开发的一个需求,有这大量频繁的Redis操作,当操作频繁且数据量每次写入的数据量较大的时候为了避免读写超时的大KEY 我采用了将大量数据切割分批读写然后在内存中进行拼接 然后就开始频繁的报错redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool 连接数太多,导致无法从池中获取资源;

 

解决方案:

1、增加连接最大超时时间:timeout = 2000;

2、之前的redis操作是架构提供的一个工具类,有一个扩展的方法,可以创建一个jedispool连接池,但是用起来发现并不是单例模式,依然会报连接太多等异常,所以需要自己写一个简约型的单例模式;

    private JedisPool jedisPool = null;

public synchronized Jedis initJedis() {
        if (null == jedisPool) {
            jedisPool = redisConnectionCustom.getJedisPool();
        }
        log.info("当前Redis连接池资源:空闲连接数:[{}] | 阻塞连接数:[{}] | 使用连接数:[{}]", jedisPool.getNumIdle(), jedisPool.getNumWaiters(), jedisPool.getNumActive());
        log.info("当前Redis连接池资源: 获取连接平均等待时间:[{}]ms | 获取连接最大等待时间:[{}]ms", jedisPool.getMeanBorrowWaitTimeMillis(), jedisPool.getMaxBorrowWaitTimeMillis());
        return jedisPool.getResource();
    }

3、大数据量操作,采用分割操作,使用管道进行批量读写:

/**
     * Hash 批量异步Set
     *
     * @param key
     * @param value
     * @param expireTime 过期时间
     */
    public void hmset(String key, Map<String, String> value, int expireTime) {
        Pipeline pipelined = null;
        Jedis jedis = null;
        try {
            jedis = initJedis();
            pipelined = jedis.pipelined();
            if (value.size() < 5000) {
                pipelined.hmset(key, value);
            } else {
                List<Map<String, String>> maps = splitMap(value, 5000);
                for (Map<String, String> map : maps) {
                    pipelined.hmset(key, map);
                }
            }
        } catch (Exception e) {
            log.error("hmset is Fail!", e);
        } finally {
            if (pipelined != null) {
                if (expireTime > 0) {
                    pipelined.expire(key, expireTime);
                }
                pipelined.sync();
            }
            if (jedis != null) {
                jedis.close();
            }

        }
    }

分割集合:

/**
     * 分割 map
     *
     * @param map 数据集
     * @param pageSize 分割后大小
     * @param <K>
     * @param <V>
     * @return
     */
    private static <K, V> List<Map<K, V>> splitMap(Map<K, V> map, int pageSize) {
        if (map == null || map.isEmpty()) {
            return Collections.emptyList();
        }
        pageSize = pageSize == 0 ? 5000 : pageSize;
        List<Map<K, V>> newList = new ArrayList<>();
        int j = 0;
        for (Map.Entry<K, V> entry : map.entrySet()) {
            if (j % pageSize == 0) {
                newList.add(new HashMap<>());
            }
            newList.get(newList.size() - 1).put(entry.getKey(), entry.getValue());
            j++;
        }
        return newList;
    }

之后查看Redis的连接数

Java实战——使用单例及管道优化Redis连接资源过多问题 至此问题解决,测试中也再也没有报过 连接数太多的异常

ok!文章到此就结束了,希望可以对大家有帮助,有不对的地方希望大家可以提出来的,共同成长;

 

最后

给大家分享一份曾经带过我的阿里大佬前辈整理的对标阿里 P7 的Java高级架构师的一套大神学习笔记。

大家可以去看一下:吹爆!!946页!阿里大佬 整理的 Java高级架构师 大神级 面试题 学习笔记。GitHub标星9K!

如果有想要完整版的,可以直接点击此处获取:耗时198天,共三个模块,946页66万字,Java开发核心知识笔记!

有需要的朋友可以点击上方或者直接通过下方方式免费获取。祝大家万事胜意!
 

上一篇:1-web应用之LAMP源码环境搭建


下一篇:Redis进阶