Jedis API 再封装(单机和集群统一接口)
前言
Redis的效用就不用多说,Jedis作为java版的Redis客户端接口也表现的非常不错。
但是也有其缺陷,比如对外统一接口的数据存储面向的是字符串值,这对我们平时面
向对象开发还是多有不便;spring-redis-data针对Jedis也做了一层封装,但是其
由于固有的风格限制,接口设计过于细致,针对不同实体需要定义不同的template实
例。以上是笔者的粗略见解,所以在大致研究了spring-redis-data和jedis源码的
上才有了大胆的对其进行再封装的想法。
目标
1、数据交互面向对象,通过泛型实现
2、对象与字节间的转化自定义多样化,默认JDK的序列化,后期支持JSON格式
3、单机和集群对外统一接口,只是配置不同而已,集群配置优先级更高
目标1与目标2是共通的,解决对象与字节间的转换也就解决了使接口面向对象从
而更方便我们操作,而对象的序列化则是java原生就支持的,但是考虑到序列化
数据量大有时会有效率方面的问题(摘抄自网上说法),所以JSON格式或许是个
好的解决方案,所以后期会考虑支持。
至于目标3,Jedis API中Jedis 与JedisCluster就是实现了统一接口JedisCommands
笔者最初有这个再封装的想法根源即在此,虽然JedisCommands相较于其子类实现少
了很多特性,但是基本以及复杂数据操作已全部囊括,其余一些运维类命令,
通过连接服务器敲命令行解决笔者到觉得更好。
实现
1、接口定义
在spring-redis-data中根据redis提供的命令操作的数据类型进行接口的分类定义,
所以笔者也延续的这个风格
(篇幅有限仅列举部分接口定义)
RedisStringCommands
public interface RedisStringCommands {
<T> T get(String key, Class<T> type);
<T> String set(String key, T value);
<T> Boolean setNX(String key, T value);
Long incr(String key);
....
}
RedisListCommands
public interface RedisListCommands {
<T> Long rPush(String key, T... values);
<T> List<T> lRange(String key, long begin, long end, Class<T> type);
....
}
RedisCommands 对外统一接口
public interface RedisCommands extends RedisStringCommands, RedisListCommands {
}
2、数据转化
定义一个数据转化接口,专用于对象与字符串间的转化
public interface ValueConverter {
/**
* 将redis存储的字符串值转化为对象
*
* @param data
* @param type
* @param <T>
* @return 当转化出现异常返回null
*/
<T> T toValue(String data, Class<T> type);
/**
* 将对象转化为字符串值便于redis存储
*
* @param value
* @param <T>
* @return 当转化出现异常返回null
*/
<T> String toString(T value);
}
通过接口定义可知,toValue方法将存储于redis的字节数据转为具体对象,toString
方法用于将具体对象转化为字符串从而与Jedis接口交互,朋友们可能已经发现,当前
接口定义只适用于普通对象互转,对于复杂的数据,例如List, Set这类支持泛型类数据
无法进行互转,受限于实力,目前想到的解决方案对整体框架而言显得格格不入,甚是暴力。
所以希望广大读者朋友能提供宝贵建议。
通过java序列化实现该接口
public class SerializableValueConverter implements ValueConverter {
private static final Logger logger = LoggerFactory.getLogger(SerializableValueConverter.class);
public static final String DEFT_ENCODING = "ISO-8859-1";
@Override
public <T> T toValue(String data, Class<T> type) {
ObjectInputStream ois = null;
ByteArrayInputStream bais = null;
try {
if (StringUtils.isNotBlank(data)) {
bais = new ByteArrayInputStream(data.getBytes(DEFT_ENCODING));
ois = new ObjectInputStream(bais);
return (T) ois.readObject();
}
} catch (Exception e) {
logger.error("convert string to value occur an ERROR", e);
} finally {
closeInStream(ois, bais);
}
return null;
}
@Override
public <T> String toString(T value) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
if (value != null) {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(value);
return new String(baos.toByteArray(), DEFT_ENCODING);
}
} catch (Exception e) {
logger.error("convert value to string occur an ERROR", e);
} finally {
closeOutStream(oos, baos);
}
return null;
}
private void closeOutStream(ObjectOutputStream oos, ByteArrayOutputStream baos) {
try {
oos.close();
baos.close();
} catch (Exception e) {
// ignore
}
}
private void closeInStream(ObjectInputStream ois, ByteArrayInputStream bais) {
try {
ois.close();
bais.close();
} catch (Exception e) {
// ignore
}
}
}
3、骨架实现
同大多数资源连接操作处理类似,redis也需要打开连接,执行命令,关闭连接。流程固定,
针对此类逻辑代码我们需要提供模板方法从而固定流程,所以在骨架实现中笔者引入了一个
RedisExecutor的抽象类进行该流程的处理,具体定义如下
public abstract class RedisExecutor<T> {
// Jedis统一接口
private final JedisCommands connection;
public RedisExecutor(JedisCommands connection) {
this.connection = connection;
}
// 抽象方法,具体命令执行在此方法中实现
public abstract T execute(JedisCommands connection, String key);
// 运行整个流程
public T run(String key) {
try {
return execute(connection, key);
} catch (Exception e) {
return null;
} finally {
// 释放连接
releaseConnection(connection);
}
}
// 连接释放处理,比较粗暴
private void releaseConnection(JedisCommands connection) {
if (connection instanceof Jedis) {
((Jedis) connection).close();
} else if (connection instanceof JedisCluster) {
try {
((JedisCluster) connection).close();
} catch (Exception e) {
}
}
}
}
RedisExecutor正如其名,处理整个操作流程的执行器,run方法固定流程,
execute方法抽象交由子类实现,调用具体的redis命令
接口的骨架实现类AbstractJedisConnection
public abstract class AbstractJedisConnection implements RedisCommands {
protected ValueConverter valueConverter;
public abstract JedisCommands getConnection();
public AbstractJedisConnection(ValueConverter valueConverter) {
this.valueConverter = valueConverter;
}
@Override
public <T> T get(String key, final Class<T> type) {
return new RedisExecutor<T>(getConnection()) {
@Override
public T execute(JedisCommands connection, String key) {
String data = connection.get(key);
return valueConverter.toValue(data, type);
}
}.run(key);
}
@Override
public <T> String set(String key, final T value) {
return new RedisExecutor<String>(getConnection()) {
@Override
public String execute(JedisCommands connection, String key) {
System.out.println("set key:" + key);
return connection.set(key, valueConverter.toString(value));
}
}.run(key);
}
}
AbstractJedisConnection首先需要ValueConverter实例来作为数据转化器,
其次定义了一个获取Jedis统一接口的方法,该方法应该由具体子类覆盖并返回
具体的Jedis连接对象是单机Jedis或集群JedisCluster。代码示例中给出了最
为常见的方法实现,get和set。在get方法中new了一个RedisExecutor对象,
覆盖了其execute方法,在其中使用JedisCommands调用get接口,再用
ValueConvert将其返回值转化为具体的对象。set方法类似,只不过调用接口
不同和转化数据方向相反而已。
4、单机Jedis和集群JedisCluser实现
这里的实现就比较简单了,Pool对象由Jedis原生支持,拿来用非常方便
public class JRedisConnection extends AbstractJedisConnection {
private final Pool<Jedis> pool;
public JRedisConnection(Pool<Jedis> pool, ValueConverter valueConverter) {
super(valueConverter);
this.pool = pool;
}
@Override
public JedisCommands getConnection() {
if (pool != null) {
return pool.getResource();
} else {
throw new IllegalStateException();
}
}
}
public class JRedisClusterConnection extends AbstractJedisConnection {
private final JedisCluster jedisCluster;
public JRedisClusterConnection(JedisCluster jedisCluster, ValueConverter valueConverter) {
super(valueConverter);
this.jedisCluster = jedisCluster;
}
@Override
public JedisCommands getConnection() {
return jedisCluster;
}
}
至此,笔者对于Jedis API的简单再封装就结束了,能力有限,简单封装,
仅作为自嗨,用于生产仍需打磨。还有关于键值唯一性保证
两种模式:
业务模式,app:module:bizcode:data;
表格模式,app:module:table:primarykey:[colnum])
,以及RedisCommands对象实例的获取(定义对象工厂,通过配置生成具体
对象并返回,方便集成spring)都没有说明,这些比较简单就不做说明。
最后欢迎大家进群讨论(Java博客党 313145288)