Redis是一个开源的Key-Value存储引擎,它支持string、hash、list、set和sorted set等多种值类型。由于其卓越的性能表现、丰富的数据类型及稳定性,广泛用于各种需要k/v存储的场景。甚至在一些分布式缓存系统中,也用它作为底层存储引擎。本文对redis最常用的数据类型进行剖析,从而让使用者在各自场景下选择合适的数据类型,从而发挥其最好的优势。
1、String
String是最常用的一种数据类型,普通的k/v存储都可以归为此类,内部由sds.h定义。在类型的设计上,除了包含字符数组buf,还会额外存储实际字符串的长度以及buf的剩余空间,从而提供更灵活的管理方式。特别需要提到的是,redis自己管理内存,从而自身可以掌握更多关于内存的信息,提高内存分配的性能以及作为其它功能的依据。此外,该结构还提供了一些字符串相关的操作函数,功能丰富,实现透明,使用方便。
2、Hash
Hash是一个string类型的field和value的映射表,由dict.h定义。一个key可对应多个field,一个field对应一个value。将一个对象存储为hash类型,较于每个字段都存储成string类型更能节省内存,并且可以更方便的存取整个对象。hash内部存储的value为一个hashMap,并提供了直接存取这个Map成员的接口。
举个例子:我们要存储一个用户详细信息,就对每个用户设置一个userID作为key,而存储的value包含了包括姓名name,年龄age,生日birth,住址addr等信息。如果用普通的k/v结构存储,一般就是userID与value中的每一项组合作为key,而对应的内容作为value。如通过命令
mset user1_name ”张三” user1_age 18 user1_addr “杭州”
这种模式,在数据量大的时候,存在明显的内存浪费。而redis提供的hash结构就很好的解决了这个问题。对于上面的例子,通过执行命令hmset user1 name ”张三” age 18 addr “杭州”
user1作为key,而name、age、birth等作为属性。如此,便通过key+field的方式,避免了数据的重复存储。
提到Hash结构,不得不说的是key的冲突解决办法和哈希表的扩容方案。在Redis中,在冲突发生时,采取的是链式冲突解决办法。另外,redis采用了双哈希表结构(ht[2])。简单来说,就是初始k/v保存在ht[0]中,当冲突严重时,将ht[1]中桶的大小设置为ht[0]的两倍,并逐步将ht[0]中的元素迁移到ht[1]。等到所有元素都迁移完成后,再将ht[0]与ht[1]交换地址。
3、List
List实现为一个双向链表,支持了反向的插入、查找和遍历,由adlist.h定义。我们可以通过push,pop操作从链表的头部或者尾部添加删除元素。在需要队列这样的数据结构时,List能提供非常丰富的接口,广泛用于缓冲队列,消息队列等应用场景。学过数据结构的朋友,对此肯定不会陌生。
4、Set
Set集合类型提供的是一个列表的功能,而且是可以自动去重的。它的内部实现其实是一个value值为null的HashMap,也正是因此来使得数据去重以及判断某个成员是否在集合内等其它一些重要接口变的简单。当某些应用场景需要存储一个列表,且不想要重复的数据时,就可以选择set这个结构来处理。
5、Sort Set
Sort Set的功能与Set非常相似,只不过它是可以通过用户提供一个优先级参数来实现自动排序的,而Set结构不会做自动排序。Sort set内部使用HashMap和SkipList来实现数据的有序存储,保证查询的效率以及元素有序性。在某些应用场景,比如需要为某个班级的学生根据成绩来排序,就可以将优先级参数设置为成绩分数,这样在插入到这个结构时,就可以实现自动的排序。