Redis如今基本也算是后台开发的基础服务,基本像Mysql同样广泛在应用中使用了。我第一次接触的Nosql是memcache用来解决夸服务session共享问题。后来由于memcache没法持久化问题改成使用Redis。此次主要针对Redis作一个整理。c++
类型 | 特色说明 |
---|---|
String | 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB |
Hash | Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。 |
List | Redis 列表是简单的字符串列表,按照插入顺序排序。你能够添加一个元素到列表的头部(左边)或者尾部(右边) |
Set | Redis 的 Set 是 string 类型的无序集合。集合是经过哈希表实现的,因此添加,删除,查找的复杂度都是 O(1) |
ZSet | 与Set不一样的是每一个元素都会关联一个double类型的分数。redis正是经过分数来为集合中的成员进行从小到大的排序 |
HyperLogLog | 在 2.8.9 版本添加是用来作基数统计的算法,HyperLogLog 的优势是,在输入元素的数量或者体积很是很是大时,计算基数所需的空间老是固定 的、而且是很小的 |
Bitmaps | 可作为布隆过滤器使用 |
GeoHash | Redis 3.2 版本地理空间位置(纬度、经度、名称) |
以set k1 hello为例,由于Redis是KV的数据库,它是经过hashtable实现的(咱们把这个叫作外层的哈希)。因此每一个键值对都会有一个dictEntry(源码位置:dict.h),里面指向了key和value的指针。next指向下一个dictEntry。redis
typedef struct dictEntry { void *key; /* Key关键字定义 */ union { void *val; /* value定义 */ uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next;/*下一个节点*/ } dictEntry;
key是字符串,可是Redis没有直接使用C的字符数组,而是存储在自定义的SDS中。算法
value既不是直接做为字符串存储,也不是直接存储在SDS中,而是存储在redisObject中。实际上五种经常使用的数据类型的任何一种,都是经过redisObject来存储的。sql
redisObject定义在src/server.h文件中数据库
typedef struct redisObject { unsigned type:4; /*对象类型,包括 OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET*/ unsigned encoding:4;/* 具体数据结构*/ unsigned lru:LRU_BITS; /*24位,对象最后一次被命令程序访问的时间,与内存回收有关*/ /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount;/*引用计数。当refcount为0的时候,表示该对象已经不被任何对象引用,则能够进行垃圾回收了*/ void *ptr;/*指向对象实际的数据结构*/ } robj;
127.0.0.1:6379>set num1 1 OK 127.0.0.1:6379>set str1 "aaaadddddddddddddddddddddddddddddddccccccccccccccccccc" OK 127.0.0.1:6379>set str2 beijing OK 127.0.0.1:6379>object encoding num1 "int" 127.0.0.1:6379>object encoding str1 "embstr" 127.0.0.1:6379>object encoding str2 "raw
字符串类型的内部编码有三种:数组
一、int,存储8个字节的长整型(long,2^63-1)。安全
二、embstr,表明embstr格式的SDS(SimpleDynamicString简单动态字符串),存储小于44个字节的字符串。session
三、raw,存储大于44个字节的字符串(3.2版本以前是39字节)。数据结构
/* <object.c> * Create a string object with EMBSTR encoding if it is smaller than * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is * used. * * The current limit of 44 is chosen so that the biggest string object * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */ #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
Redis中字符串的实现。在3.2之后的版本中,SDS又有多种结构(sds.h):sdshdr五、sdshdr八、sdshdr1六、sdshdr3二、sdshdr64,用于存储不一样的长度的字符串,分别表明:
2^5=32byte
2^8=256byte
2^16=65536byte=64KB
2^32byte=4GB。app
C语言自己没有字符串类型(只能用字符数组char[]实现)。
一、使用字符数组必须先给目标变量分配足够的空间,不然可能会溢出。
二、若是要获取字符长度,必须遍历字符数组,时间复杂度是O(n)。
三、C字符串长度的变动会对字符数组作内存重分配。
四、经过从字符串开始到结尾碰到的第一个'\0'来标记字符串的结束,所以不能保存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全。
一、不用担忧内存溢出问题,若是须要会对SDS进行扩容。
二、获取字符串长度时间复杂度为O(1),由于定义了len属性。
三、经过“空间预分配”(sdsMakeRoomFor)和“惰性空间释放”,防止屡次重分配内存。
四、判断是否结束的标志是len属性(它一样以'\0'结尾是由于这样就可使用C语言中函数库操做字符串的函数了),能够包含'\0'。
c字符串 | SDS |
---|---|
embstr | 只读 |
获取字符串长度的复杂度为O(N) | 获取字符串长度的复杂度为O(1) |
API是不安全的,可能会形成缓冲区溢出 | API是安全的,不会早晨个缓冲区溢出 |
修改字符串长度N次必然须要执行N次内存重分配 | 修改字符串长度N次最多须要执行N次内存重分配 |
只能保存文本数据 | 能够保存文本或者二进制数据 |
可使用全部<string.h>库中的函数 | 可使用一部分<string.h>库中的函数 |
embstr的使用只分配一次内存空间(由于RedisObject和SDS是连续的),而raw须要分配两次内存空间(分别为RedisObject和SDS分配空间)。
所以与raw相比,embstr的好处在于建立时少分配一次空间,删除时少释放一次空间,以及对象的全部数据连在一块儿,寻找方便。
而embstr的坏处也很明显,若是字符串的长度增长须要从新分配内存时,整个RedisObject和SDS都须要从新分配空间,所以Redis中的embstr实现为只读。
当int数据再也不是整数,或大小超过了long的范围(2^631=9223372036854775807)时,自动转化为embstr。
127.0.0.1:6379>set s1 1 OK 127.0.0.1:6379>append s1 a (integer)2 127.0.0.1:6379>object encoding s1 "raw"
127.0.0.1:6379>set s2 a OK 127.0.0.1:6379>object encoding s2 "embstr" 127.0.0.1:6379>append s2 b (integer)2 127.0.0.1:6379>object encoding s2 "raw"
对于embstr,因为其实现是只读的,所以在对embstr对象进行修改时,都会先转化为raw再进行修改。
所以,只要是修改embstr对象,修改后的对象必定是raw的,不管是否达到了44个字节。
关于Redis内部编码的转换,都符合如下规律:编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换(可是不包括从新set)
经过封装,能够根据对象的类型动态地选择存储结构和可使用的命令,实现节省空间和优化查询速度。