Redis 的 hash,及其序列化问题
- 介绍
- 常用命令
- 应用场景
- hash的序列化存储方式
介绍
一次应用中,需要将MySQL中的表在redis中缓存一份,防止查询次数太多,对数据库造成压力。用到了hash这种结构,对此进行一个理解。Redis hash数据结构 是一个键值对(key-value)集合,它是一个 string 类型的 field 和 value 的映射表(它的key取名为field,为了和redis的key做区分),redis 本身就是一个 key-value 型数据库,因此 hash 数据结构相当于在 value 中又套了一层 key-value 型数据。所以 redis 中 hash 数据结构特别适合存储关系型对象。结构如下表示:
- key : redis的key
- field: hash的key
- value: hash的value
结构虽然不是很难,用编程语言可以理解为Map<key, Map<field,value>>。但是它的作用可是很大。
常用命令
- 单次操作命令:
hset “key” “field” value [field value]: 存放指定key的field和value值。
hget “key” “field” 获取存储在哈希表中指定字段的值。
hdel “key” “field1” “field2” 删除一个或多个哈希表字段
hexists “key” “field” 查看哈希表key中,指定的字段是否存在
- 批量操作命令:
hgetall key 获取在哈希表中指定key的所有字段和值
hkeys key 获取所有哈希表中的字段(field)
hmget key field1 field2 获取所有给定字段的值
hmset key field1 value1 field2 value2 同时将多个field-value(域-值)对设置到哈希表key中
hvals key获取哈希表中所有的值(value)
因为是对关系型对象的存储,我们最常用的就是批量的操作,因为这样连接只用获取一次吗。同时,在JedisCluster下,有很多便于操作的方法,最常用的像hmset(可以直接填入一个map作为hash)、hgetAll(返回一个Map<String,String>类型)。。。。引用如下:
@Test
public void testHash() {
Map<String,String> map = new HashMap<String,String>();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
jedis.hmset("hash",map);
jedis.hset("hash", "key5", "value5");
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));//return Map<String,String>
System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>
System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 6));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 3));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
System.out.println("判断hash中是否存在key2:"+jedis.hexists("hash","key2"));
System.out.println("判断hash中是否存在key3:"+jedis.hexists("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key4"));
}
应用场景
上面我们讨论了hash可以存储关系型对象,MySQL的关系型数据缓存。接下来分析一下它的具体设计:
通常情况下,我们用redis的key来指定一种类型的对象,比如用户。
key可以设计为(redis_user),value就是user对象的映射了,但是这样相当于把整个用户都放到了一个节点key上,如果redis发生崩溃,造成的影响可不小。
换一种方式,我们把key设计为(redis_userId),这样虽然能占用多的key节点,假如用户是成千上万的,导致了每个用户占用一个key,这样使用hash的意义也就不大了。
所以我们使用hash结构,既要利用其特点,也要考虑redis集群状态下的节点分布问题。所以对key的设计是比较重要的,例如在key上加一个id对某个数取余的标志。让key分散开来,例如(redis_userId%10),因为放入的对象还是带着自己的id。操作也不会造成影响。
hash的序列化存储方式
通常情况,存入hash的对象需要对其序列化存储。因为普通的JSONString类型占用空间较大,而且序列化后方便了跨平台的传输和使用。常用的序列化方式有:
JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer、==Kryo-Serializers ==这些序列化方式。我们此次用到的是Kryo方式。先看一下特点:
Kryo 是一个快速序列化/反序列化工具,其使用了字节码生成机制(底层依赖了 ASM 库),因此具有比较好的运行速度。
Kryo 序列化出来的结果,是其自定义的、独有的一种格式,不再是 JSON 或者其他现有的通用格式;而且,其序列化出来的结果是二进制的(即 byte[];而 JSON 本质上是字符串 String);二进制数据显然体积更小,序列化、反序列化时的速度也更快。
Kryo 一般只用来进行序列化(然后作为缓存,或者落地到存储设备之中)、反序列化,而不用于在多个系统、甚至多种语言间进行数据交换 —— 目前 kryo 也只有 java 实现。
像 Redis 这样的存储工具,是可以安全地存储二进制数据的,所以可以直接把 Kryo 序列化出来的数据存进去。
具体的使用我们参考一下大佬写的:kryo使用