使用jedisPool构建redisUtils

最近做的项目有用到redis,主要是用于登录保存token,保存微信的accessToken,使用链接池来优化redis,在每个操作后自动释放资源,无需每个 都使用finally{jedis.close();},由于redis 再项目中使用的情况比较平凡,因此使用 单例模式中的 饿汉模式来初始化.

1.1 所需jar

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
	<version>2.9.0</version>
</dependency>

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

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.30</version>
</dependency>

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.7.30</version>
</dependency>

1.2 配置文件

定义了一些基础的配置 比如:redis 的host:port, 最大链接数等等

# 操作超时时间,默认2秒
timeout=3000

# redis url接口以";"分割多个地址
urls=127.0.0.1:6379

# jedis池最大连接数总数,默认8
maxTotal=8

# jedis池最大空闲连接数,默认8
maxIdle=8

#jedis池最少空闲连接数
minIdle=3

# jedis池没有对象返回时,最大等待时间单位为毫秒
maxWaitTime=60000

# 在borrow一个jedis实例时,是否提前进行validate操作
testOnBorrow=true

1.3 定义一个方法实现的接口,方便统一释放资源

import redis.clients.jedis.Jedis;

/**
 * @program: lw_weaver
 * @description:
 * @author: zhouQ
 * @create: 2021-02-08 09:56
 **/
// redis具体逻辑接口
public interface HashRedisExecutor<T> {
    T execute(Jedis jedis);
}

1.4 创建HashRedisUtil工具类

采用分布式存储,即有多个redis提供服务,配置文件中的urls 可以有多个,定义多个urls 的分隔符,定义一些初始的参数如,JedisPool[]等

    // 多个url 间用; 来分割
    private static final String DEFAULT_REDIS_SEPARATOR = ";";

    // 端口号分隔符
    private static final String HOST_PORT_SEPARATOR = ":";

    // jedis线程池 默认是0 等到 初始化的时候赋值
    private JedisPool[] jedisPools = new JedisPool[0];

    // 工具类示例话 final 修饰 唯一 不可变, static 此处处保证在类加载的时候 初始化 HashRedisUtil,执行无参构造, 单例模式 饿汉
    private static final HashRedisUtil INSTANCE = new HashRedisUtil();

当类在被加载的时候,static 修饰的 DEFAULT_REDIS_SEPARATOR、HOST_PORT_SEPARATOR创建,执行new HashRedisUtil()方法,实例化HashRedisUtil

1.4.1 HashRedisUtil 的无参构造方法

    // 无参构造
    private HashRedisUtil() {
        initPool();
    }

    // 初始化 jedis 池
    private void initPool() {
        // 操作超时时间,默认2秒
        int timeout = NumberUtils.toInt(Prop.getPropValue("hashRedis", "timeout"), 2000);
        // jedis池最大连接数总数,默认8
        int maxTotal = NumberUtils.toInt(Prop.getPropValue("hashRedis", "maxtotal"), 8);
        // jedis池最大空闲连接数,默认8
        int maxIdle = NumberUtils.toInt(Prop.getPropValue("hashRedis", "maxIdle"), 8);
        // jedis池最少空闲连接数
        int minIdle = NumberUtils.toInt(Prop.getPropValue("hashRedis", "minIdle"), 0);
        // jedis池没有对象返回时,最大等待时间单位为毫秒
        long maxWaitMillis = NumberUtils.toLong(Prop.getPropValue("hashRedis", "maxWaitTime"), -1);
        // 在borrow一个jedis实例时,是否提前进行validate操作
        boolean testOnBorrow = Boolean.parseBoolean(Prop.getPropValue("hashRedis", "testOnBorrow"));
        // 设置jedis连接池配置
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxWaitMillis(maxWaitMillis);
        poolConfig.setTestOnBorrow(testOnBorrow);

        // 取得redis的url
        String redisUrls = Prop.getPropValue("hashRedis", "urls");
        if (StringUtils.isBlank(redisUrls)) {
            throw new IllegalStateException("the urls of redis is not configured");
        }
        // 生成连接池
        List<JedisPool> jedisPoolList = new ArrayList<JedisPool>();
        // 根据 ; 切割所有的 redis 服务 并遍历 获取每一个url:port
        for (String redisUrl : redisUrls.split(DEFAULT_REDIS_SEPARATOR)) {
            // 根据 :  切割 host 和 port
            String[] redisUrlInfo = redisUrl.split(HOST_PORT_SEPARATOR);
            
            // 为每一个 redis 服务创建一个 jedis池 
            jedisPoolList.add(new JedisPool(poolConfig, redisUrlInfo[0], Integer.parseInt(redisUrlInfo[1]), timeout));
        }

        jedisPools = jedisPoolList.toArray(jedisPools);
    }

使用的单例模式 ,因此构造方法为 privite修饰 ,提供一个对外界唯一的访问方法getInstance()

    /***
     * 提供外界访问的方法
     * @return
     */
    public static HashRedisUtil getInstance() {
        return INSTANCE;
    }

1.4.2 定义一个公共的方法execute

    /**
     * 实现jedis连接的获取和释放,具体的redis业务逻辑由executor实现
     *
     * @param executor RedisExecutor接口的实现类
     * @return
     */
    public <T> T execute(String key, HashRedisExecutor<T> executor) {
        // 根据存放的key 的hashcode 算出应该存放在那台redis 服务上,获取对应的jedis
        Jedis jedis = jedisPools[(0x7FFFFFFF & key.hashCode()) % jedisPools.length].getResource();
        T result = null;
        try {
			// 实现 HashRedisExecutor 接口的 executor 方法,对于具体 redis 的操作方法 只需要执行此 execute方法,并根据业务具体重写 executor.execute(jedis) 就行, 而且执行完成后 最后执行finally 中的 close 方法,无需再每个方法中都去释放资源
            result = executor.execute(jedis);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return result;
    }

关于为什么使用(0x7FFFFFFF & key.hashCode()) % jedisPools.length
使用(0x7FFFFFFF & key.hashCode()) % jedisPools.length 使得存放在每一台redis服务上的数据均衡分布,不会形成数据倾斜

        // (0x7FFFFFFF & key.hashCode()
        // 0x7FFFFFFF 是用十六进制表示的一个数, 是int 的最大值,表示01111 1111 1111 1111 1111 1111 1111 1111
        // key.hashCode() 取到的是key 的hashCode 是一个int      0000  0000 0000 1011 1101 0010 1110 1001  两数&运算 同真为真  一假为假
        //                                                    0000  0000 0000 1011 1101 0010 1110 1001
        // 可以发现 与 0x7FFFFFFF & 运算后 得到的还是 key.hashCode() 的hashCode 那为什么还要与 0x7FFFFFFF 进行&运算呢,
        // 可以发现 0x7FFFFFFF 的最高位 符号位 为 0 ,如果 key.hashCode() 的 是int 的最小值 -2^31次
        // 01111 1111 1111 1111 1111 1111 1111 1111
        // & 运算
        // 11111 1111 1111 1111 1111 1111 1111 1111
        // 此时就能保证得到的数 是一个整数 01111 1111 1111 1111 1111 1111 1111 1111
        // 至于为什么不用 Math.abs() 取正数呢;   int 的取值范围是 -2^31 ~ 2^31 - 1 当取到的负数  -2^31 取到正数 是 2^31 则超出了 int 正数的最大范围
        // 01111 1111 1111 1111 1111 1111 1111 1111
        // 11111 1111 1111 1111 1111 1111 1111 1111 得到的还是正数 的 01111 1111 1111 1111 1111 1111 1111 1111 不会溢出

使用测试数据如下图所示均衡分布
在这里插入图片描述

1.4.3具体方法实现 每个方法都调用 execute()


    /***
     * set String
     * @param key
     * @param value
     * @return
     */
    public String set(final String key, final String value) {
        return execute(key, new HashRedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.set(key, value);
            }
        });
    }

    /***
     *
     * @param key
     * @param value
     * @param nxxx  nx key不存在时设置value,成功返回OK,失败返回(nil), xx key存在时设置value,成功返回OK,失败返回(nil)
     * @param expx ex 设置失效时长,单位秒 , px 设置失效时长,单位毫秒
     * @param time 失效时间
     * @return
     */
    public String set(final String key, final String value, final String nxxx, final String expx, final long time) {
        return execute(key, new HashRedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                SetParams params = new SetParams();
                if (ObjectUtils.equals(nxxx, "xx")) {
                    params.xx();
                } else {
                    params.nx();
                }

                if (ObjectUtils.equals(expx, "ex")) {
                    params.ex((int) time);
                } else {
                    params.px(time);
                }
                return jedis.set(key, value, params);
            }
        });
    }

    /***
     * 根据key 取值
     * @param key
     * @return
     */
    public String get(final String key) {
        return execute(key, new HashRedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.get(key);
            }
        });
    }

    /***
     * 根据key 判断是否存在
     * @param key
     * @return
     */
    public Boolean exists(final String key) {
        return execute(key, new HashRedisExecutor<Boolean>() {
            @Override
            public Boolean execute(Jedis jedis) {
                return jedis.exists(key);
            }
        });
    }

    /***
     * key不存在时设置value,成功返回OK,失败返回(nil)
     * @param key
     * @param value
     * @return
     */
    public Long setnx(final String key, final String value) {
        return execute(key, new HashRedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.setnx(key, value);
            }
        });
    }

    /***
     * set值并且设置失效时间 单位s
     * @param key
     * @param seconds  设置失效时间 单位 秒
     * @param value
     * @return
     */
    public String setex(final String key, final int seconds, final String value) {
        return execute(key, new HashRedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.setex(key, seconds, value);
            }
        });
    }

    /***
     * key已经存在,设置key生存时间,当key过期时,它会被自动删除。
     * @param key
     * @param seconds
     * @return
     */
    public Long expire(final String key, final int seconds) {
        return execute(key, new HashRedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.expire(key, seconds);
            }
        });
    }

    /***
     * 将key 对应得value +1, 如果不存在 先 初始化0 再执行 incr 操作
     * @param key
     * @return
     */
    public Long incr(final String key) {
        return execute(key, new HashRedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.incr(key);
            }
        });
    }

    /***
     * 将key 对应得value -1, 如果不存在 先 初始化0 再执行 decr 操作
     * @param key
     * @return
     */
    public Long decr(final String key) {
        return execute(key, new HashRedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.decr(key);
            }
        });
    }

    /***
     * 为哈希表中的字段赋值
     * @param key
     * @param field
     * @param value
     * @return
     */
    public Long hset(final String key, final String field, final String value) {
        return execute(key, new HashRedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.hset(key, field, value);
            }
        });
    }

    /***
     *  返回哈希表中指定字段的值
     * @param key
     * @param field
     * @return
     */
    public String hget(final String key, final String field) {
        return execute(key, new HashRedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.hget(key, field);
            }
        });
    }

    /***
     * 同时将多个 field-value (字段-值)对设置到哈希表中。此命令会覆盖哈希表中已存在的字段。
     * @param key
     * @param hash
     * @return
     */
    public String hmset(final String key, final Map<String, String> hash) {
        return execute(key, new HashRedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.hmset(key, hash);
            }
        });
    }

    /***
     * 返回哈希表中指定字段的值。
     * @param key
     * @param fields
     * @return
     */
    public List<String> hmget(final String key, final String... fields) {
        return execute(key, new HashRedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.hmget(key, fields);
            }
        });
    }

    /***
     * 根据 key 删除指定的值
     * @param key
     * @return
     */
    public Long del(final String key) {
        return execute(key, new HashRedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.del(key);
            }
        });
    }

    /***
     * 返回哈希表中,所有的字段和值。
     * @param key
     * @return
     */
    public Map<String, String> hgetAll(final String key) {
        return execute(key, new HashRedisExecutor<Map<String, String>>() {
            @Override
            public Map<String, String> execute(Jedis jedis) {
                return jedis.hgetAll(key);
            }
        });
    }

    /***
     * 关闭指定 jedis 连接池 释放资源
     */
    public void destroy() {
        for (int i = 0; i < jedisPools.length; i++) {
            jedisPools[i].close();
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值