Redis
作为目前主流的缓存数据库,提供了丰富的数据结构,这次做的项目里我们大量使用了Redis
作为数据缓存,甚至在某些场景下直接作为了数据库使用(当然这是建立在公司内部基础组件足够可靠的前提下),由于项目是从零开始,在使用Redis的过程中,遇到了一些设计上的挑战和问题,因此特别在博客里记录下来。
最基础的肯定就是Java对象的存储,使用SQL型数据库的时候,可以借助ORM框架直接进行把Java对象映射到表中的一行,但是Redis
作为K-V型数据库,显然是无法直接这样做的。
我们知道Java对象可以序列化成二进制数据,基于此点,我们可以直接把Java
对象存储到Redis
的String
结构中。
这里选择使用protostuff
作为序列化工具,maven项目中添加如下依赖:
<dependency>
<groupId>io.protostuffgroupId>
<artifactId>protostuff-coreartifactId>
<version>1.6.2version>
dependency>
<dependency>
<groupId>io.protostuffgroupId>
<artifactId>protostuff-runtimeartifactId>
<version>1.6.2version>
dependency>
编写代码,SerializationUtil
工具类使用protostuff
序列化/反序列化对象。
@Slf4j
public class SerializationUtil {
// 避免protostuff每次序列化都重新申请Buffer空间
private static LinkedBuffer buffer = LinkedBuffer.allocate();
/**
* 序列化对象到字节数组
*
* @param obj 待序列化对象
* @param clazz 待序列化对象类型
* @return 序列化得到字节数组
*/
public static <T> byte[] serialize(T obj, Class<T> clazz) {
Schema<T> schema = RuntimeSchema.getSchema(clazz);
byte[] data;
try {
data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} finally {
buffer.clear();
}
return data;
}
/**
* 但序列化字节数组到指定类型对象
*
* @param data 字节数组
* @param clazz 指定类型
* @return 反序列化得到对象
*/
public static <T> T deserialize(byte[] data, Class<T> clazz) {
Schema<T> schema = RuntimeSchema.getSchema(clazz);
T obj = schema.newMessage();
ProtostuffIOUtil.mergeFrom(data, obj, schema);
return obj;
}
}
这里我们定义一个Java类TestBean
,作为测试类(这里注解使用Lombok
)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestBean {
private String aString;
private Integer aInteger;
private Double aDouble;
private List<String> stringList;
}
这里我使用了Jedis
作为Redis
连接驱动,当然你也可以使用Spring-Data-Redis
或者Lettuce
,这里测试一下代码:
@Slf4j
public class JedisTest {
public static void main(String[] args) throws UnsupportedEncodingException {
// 建立redis连接到本地
Jedis jedis = new Jedis("localhost");
log.debug("jedis测试连接:{}", jedis.ping());
// 新建一个测试对象
TestBean testBean = new TestBean(null, 1, 0.1, Arrays.asList("1", "2", "3"));
byte[] redisKey = "test:string:1".getBytes("UTF8");
byte[] redisValue = SerializationUtil.serialize(testBean, TestBean.class);
log.debug("jedis返回状态:{}", jedis.set(redisKey, redisValue));
}
}
运行以后,可以看到已经成功写入Redis
了。
我们在Redis-Cli里看一下我们写入的键,然而这里看到的全是十六进制,这是为什么呢?
答案很简单,因为Protostuff
是按照自己定义的规则序列化了对象,得到的是二进制数组,这里甚至可以打印一下序列化得到的二进制数组:
log.debug("序列化得到的二进制数据:{}", redisValue);
/**
* 2019-12-16 20:40:59 DEBUG com.cringkong.mytest.service.JedisTest:21 -
* 序列化得到的二进制数据:
* [16, 1, 25, -102, -103, -103,
* -103, -103, -103, -71, 63, 34, 1,
* 49, 34, 1, 50, 34, 1, 51]
*/
也就是说,序列化以后的对象,我们是不可读的,只有Protostuff
按照一定的规则反序列化后,才能得到一个对象,这里我们反序列化:
public static void main(String[] args) throws UnsupportedEncodingException {
// 建立redis连接到本地
Jedis jedis = new Jedis("localhost");
log.debug("jedis测试连接:{}", jedis.ping());
// 新建一个测试对象
TestBean testBean = new TestBean(null, 1, 0.1, Arrays.asList("1", "2", "3"));
byte[] redisKey = "test:string:1".getBytes("UTF8");
byte[] redisValue = SerializationUtil.serialize(testBean, TestBean.class);
log.debug("序列化得到的二进制数据:{}", redisValue);
log.debug("jedis返回状态:{}", jedis.set(redisKey, redisValue));
// 从redis中取数据
byte[] getValue = jedis.get(redisKey);
// 反序列化数据
log.debug(SerializationUtil.deserialize(getValue, TestBean.class).toString());
}
Java
对象可以序列化成二进制数组,当然也可以序列化成Json
字符串,这里我们可以直接将Json
字符串存储进Redis
中。
这里选择jackson
作为Json
序列化工具,当然使用FastJson
或者Gson
也是完全没有问题的。
public class SerializationUtil {
private static ObjectMapper jacksonMapper = new ObjectMapper();
/**
* 序列化对象成Json字符串
*
* @param o 对象
* @return Json字符串
*/
public static String ObjectToJson(Object o) {
try {
return jacksonMapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
log.error("序列化生成Json出现", e);
}
return null;
}
/**
* 反序列化Json成置顶类型对象
*
* @param json Json字符串
* @param clazz 泛型类型
* @return 指定类型对象
*/
public static <T> T jsonToObject(String json, Class<T> clazz) {
try {
return jacksonMapper.readValue(json, clazz);
} catch (JsonProcessingException e) {
log.error("Json反序列化出现问题", e);
}
return null;
}
}
测试代码如下:
public static void main(String[] args) throws UnsupportedEncodingException {
// 建立redis连接到本地
Jedis jedis = new Jedis("localhost");
log.debug("jedis测试连接:{}", jedis.ping());
// 新建一个测试对象
TestBean testBean = new TestBean("亦可赛艇", 123, 0.1123, Arrays.asList("13", "42", "53"));
log.debug("jedis返回状态:{}", jedis.set("test:string:2", SerializationUtil.ObjectToJson(testBean)));
log.debug("redis直接取值:{}",jedis.get("test:string:2"));
}
这里可以看到,redis
中存储的值就是一个Json
字符串,同理我们直接在Redis-cli中查看:
这里可以看到数据是可读的,当然这里面的astring
是十六进制,这是因为汉字在UTF8编码下占用24位二进制的原因。
优劣对比 | 序列化成二进制 | 序列化成JSON |
---|---|---|
序列化速度 | 快 | 相对慢 |
序列后大小 | 小 | 相对大 |
序列化后可读性 | 完全不可读 | 可读 |
Redis中数据类型 | String | String |
其实序列化成Json最大的优势就在于可读性,便于我们在项目开发中更快的发现、定位代码中可能存在的问题,这里序列化成二进制特指使用Protostuff
,如果使用Java
自带的序列化方式,可能速度会比序列化成Json
还要慢。
另外需要说明一点的是Jedis
存Json
字符串的时候,还会进行一次编码:
也就是说如果直接存储二进制数组的话,Jedis
代码层面上的性能开销也会更小,总之序列化成二进制的有点就是性能要比序列化成Json
好很多。
因此大家在开发环境下选择哪种方式,还是要自行判断场景进行定夺。