前言 Redis作为一款强大的key-value型数据库,其应用是十分广泛的。在Java语言中,常用来与Redis数据库建立连接用到的是Jedis Pool连接池。
今天我们来简单了解下它们然后实现一个可移植的操作Redis的API。
正文 知识准备 我们知道Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。并提供了一系列的命令操作这些数据类型。
Jedis相当于对这些操作进行了代码封装,及提供了一些其它常用操作。
我们先来了解下Jedis的连接池配置参数。
commons-pool2 有一个配置类GenericObjectPoolConfig里面的通用参数设置如下:
参数 说明 默认值 备注 maxTotal 说明一个pool最多可以有多少个Jedis实例 8 -1表示不限制 maxIdle 一个pool最多可以有多少个空闲的Jedis实例 8 minIdle 一个pool最少有多少个空闲的Jedis实例 0
可以看到它继承BaseObjectPoolConfig。我们可以看到BaseObjectPoolConfig的参数如下。
部分参数意义如下:
参数 说明 默认值 备注 lifo pool中的idle列表是双端队列,设定是否last in first out true maxWaitMillis 当active数量为max时,等待的时长 -1L(代表一直等) 配合blockWhenExhausted使用 blockWhenExhausted 当active数量为max时,是否阻塞等待一段时间 true testOnCreate 创建实例时有效性检测 false testOnReturn 归还实例时有效性检测 false testOnBorrow 借出实例时有效性检测 false
思路分析 首先Redis连接池属性我们应当放置在配置文件里,解析并获得,连接池最好设计成单例的,每次不用在初始化过多连接资源。同时Redis有单机模式和集群模式区分,这两种模式我们也应该区分开来。单机模式下,可以选择多个database,集群模式下只能选择database0.集群模式下,如果redis地址过多,我们如何分开呢?
我们可以考虑如下样式: address =127.0.0.1:6379;127.0.0.1:6380
每个redis地址用分号分隔,解析配置时把每个解析到并建立连接。
当然,最后完成JedisPool的创建后,我们应该编写工具类对一些常用操作方法进行封装,便于我们使用。
代码 我们根据上述思路,构造了如下图所示的小项目。
其中:
RedisException 是用来统一处理程序过程中的异常的类。
JedisFactory 可以认为是一个JedisPool工厂,用来提供单机模式的连接池或者集群模式的连接池。
RedisConfiguration 是与配置文件对应的配置类,用于存放配置的数据。
RedisConstants 用来放置一些项目中用到的常量。
RedisUtil 工具类接口,提供了多种操作Redis的方法。
RedisSingleUtil 工具接口的单机模式实现。
RedisClusterUtil 工具接口的集群模式实现。
redis-config.properties Redis的配置文件存放
JedisFactory和RedisUtill为主要类。我们看下他们的具体实现。
JedisFactory的主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 private volatile JedisPool jedisPool; private volatile JedisCluster jedisCluster; private RedisConfiguration redisConfig; private Pattern addRessPattern = Pattern.compile("^.+[:]\\d{1,5}\\s*(;.+[:]\\d{1,5}\\s*)*[;]?\\s*$" ); public JedisFactory (final RedisConfiguration redisConfiguration) { this .redisConfig=redisConfiguration; } public JedisPool getJedisPool () { if (jedisPool==null ){ synchronized (JedisFactory.class){ if (jedisPool==null ){ init(); } } } return jedisPool; } public JedisCluster getJedisCluster () { if (jedisCluster==null ){ synchronized (JedisFactory.class){ if (jedisCluster==null ){ init(); } } } return jedisCluster; } public void init () { logger.info("JedisFactory init start..." ); try { if (StringUtils.isNotBlank(redisConfig.getLocalPropertiesPath())){ fillData(); } logger.info("redis config is: {}." , redisConfig.toString()); Set<HostAndPort> hostAndPortSet = this .parseHostAndPort(redisConfig.getAddress()); GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig(); genericObjectPoolConfig.setMaxWaitMillis(redisConfig.getMaxWaitMillis()); genericObjectPoolConfig.setMaxTotal(redisConfig.getMaxTotal()); genericObjectPoolConfig.setMinIdle(redisConfig.getMinIdle()); genericObjectPoolConfig.setMaxIdle(redisConfig.getMaxIdle()); if (redisConfig.getMode()== RedisConstants.REDIS_MODE_SINGLE){ HostAndPort hostAndPort=(HostAndPort)hostAndPortSet.toArray()[0 ]; jedisPool=new JedisPool(genericObjectPoolConfig, hostAndPort.getHost(), hostAndPort.getPort(), redisConfig.getTimeout(), null ,redisConfig.getDatabase()); logger.info("jedisPool init is finished" ); }else { if (redisConfig.getDatabase()!=0 ){ logger.warn("当前配置的database为:" +redisConfig.getDatabase()+",集群模式下不能选择database,只能使用database0" ); } jedisCluster = new JedisCluster(hostAndPortSet, redisConfig.getTimeout(), redisConfig.getMaxRedirections(), genericObjectPoolConfig); logger.info("jedisCluster init is finished" ); } }catch (Exception ex){ throw new RedisException(ex); } } private void fillData () throws Exception { Properties localProperties = PropertiesUtils.loadLocalProperties(redisConfig.getLocalPropertiesPath()); String address=localProperties.getProperty("address" , "" ); if (StringUtils.isBlank(address)) { throw new RedisException("error:redis config address is blank!" ); } long maxWaitMillis=Long.parseLong(localProperties.getProperty("maxWaitMillis" , String.valueOf(GenericObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS))); int maxTotal=Integer.parseInt(localProperties.getProperty("maxTotal" , String.valueOf(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL))); int minIdle=Integer.parseInt(localProperties.getProperty("minIdle" , String.valueOf(GenericObjectPoolConfig.DEFAULT_MIN_IDLE))); int maxIdle=Integer.parseInt(localProperties.getProperty("maxIdle" , String.valueOf(GenericObjectPoolConfig.DEFAULT_MAX_IDLE))); int timeout=Integer.parseInt((localProperties.getProperty("timeout" , "2000" ))); int maxRedirections=Integer.parseInt((localProperties.getProperty("maxRedirections" , "6" ))); int database=Integer.parseInt((localProperties.getProperty("database" , "0" ))); int mode=Integer.parseInt((localProperties.getProperty("mode" , String.valueOf(RedisConstants.REDIS_MODE_SINGLE)))); redisConfig.setAddress(address); redisConfig.setMaxWaitMillis(maxWaitMillis); redisConfig.setMaxTotal(maxTotal); redisConfig.setMinIdle(minIdle); redisConfig.setMaxIdle(maxIdle); redisConfig.setTimeout(timeout); redisConfig.setMaxRedirections(maxRedirections); redisConfig.setDatabase(database); redisConfig.setMode(mode); }
对于RedisUtil接口,应有两个实现,单机和集群的,这里为了简化代码,只简单列举了一个方法。
1 2 3 4 public interface RedisUtil { String setString (String key, String value) ; }
单机模式的实现:
1 2 3 4 5 6 7 8 9 10 11 12 public class RedisSingleUtil implements RedisUtil { @Override public String setString (String key, String value) { Jedis jedis = this .getResource(); try { return jedis.set(key, value); }finally { this .closeResource(jedis); } } }
集群模式的实现:
1 2 3 4 5 6 7 8 public class RedisClusterUtil implements RedisUtil { @Override public String setString (String key, String value) { JedisCluster cluster=getResource(); return getResource().set(key, value); } }
其他方法及实现不在赘述,有兴趣的可以在
https://github.com/javazwt/framework-base 上查看相关代码。
测试 1 2 3 4 5 6 7 public class RedisTest { private static RedisSingleUtil redisSingleUtil=new RedisSingleUtil(); public static void main (String[] args) { redisSingleUtil.setString("str" ,"123" ); redisSingleUtil.getString("str" ); } }
可以检测我们的正确性。
结束语 经过封装后,我们可以把该工具类使用在任何项目上,提高开发效率,降低项目耦合性,同时对Redis有了更深入的认知。