《redis设计与实现》
《redis开发与运维》

redis

redis应用场景redis常用命令

1. redis内存模型

redis内存模型

1.1 redis内存统计

info memory 查看内存使用情况

1.2 redis内存划分

1、 数据
作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中。
Redis使用键值对存储数据,其中的值(对象)包括5种类型,即字符串、哈希、列表、集合、有序集合。
这5种类型是Redis对外提供的,实际上,在Redis内部,每种类型可能有2种或更多的内部编码实现;
此外,Redis在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如redisObject、SDS等;

1.3 redis数据存储细节

1、 概述

redis数据存储,涉及到内存分配器(如jemalloc)、简单动态字符串(SDS)、5种对象类型及内部编码、redisObject。
下面是执行set hello world, 所涉及到的数据模型。
(1) DictEntry: redis是key-value数据库,对每个键值对都会有一个DictEntry, 里面存储了指向key和value的指针;next指向下一个dictEntry,与本key-value无关。
(2) key: key(“hello”)并不是直接以字符串存储,而是存储在SDS结构中。
(3) redisObject: value(“world”) 也不是像Key一样直接存储在SDS中,而是存储在redisObject中。
实际上,不论Value是5种类型的哪一种,都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。
不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。
(4) jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。
以DictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。

2、jemalloc

3、redisObject

4、SDS

redis没有直接使用C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。
(1) SDS结构

struct sdshdr {
    int len;
    int free;
    char buf[];
};

其中,buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度。

2. redis的对象类型与内部编码

redis支持5种对象类型,而每种结构都有至少两种编码;这样做的好处有:一方面接口与实现分离,当需要增加或改变内部编码时,用户使用不受影响;另一方面可以根据不同的应用场景切换内部编码,提高效率。
redis各种对象类型支持的内部编码如下:

关于redis内部编码的转换,都符合以下规律:编码转换在redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换。

2.1 字符串

(1) 概况
字符串是最基础的类型,因为所有的键都是字符串类型,且字符串之外的其他几种复杂类型的元素也是字符串。
字符串长度不能超过512MB。
(2) 内部编码
字符串类型的内部编码有3种,它们的应用场景如下:

int: 8个字节的长整型。字符串值是整型时,这个值使用long整型表示。
embstr: <=39字节的字符串。embstr与raw都使用redisObject和sds保存数据,区别在于,embstr的使用只分配一次内存空间(因此redisObject和sds是连续的),而raw需要分配两次内存空间(分别为redisObject和sds分配空间)。因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。
raw:大于39个字节的字符串

embstr和raw进行区分的长度,是39;是因为redisObject的长度是16字节,sds的长度是9+字符串长度;因此当字符串长度是39时,embstr的长度正好是16+9+39=64,jemalloc正好可以分配64字节的内存单元。
(3) 编码转换
当int数据不再是整数,或大小超过了long的范围时,自动转化为raw。

而对于embstr,由于其实现是只读的,因此在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了39个字节。

127.0.0.1:6379> set key1 hello
OK
127.0.0.1:6379> object encoding key1
"embstr"
127.0.0.1:6379> append key1 11
(integer) 7
127.0.0.1:6379> object encoding key1
"raw"

2.2 列表

(1) 概述
列表(list)用来存储多个有序的字符串,每个字符串称为元素;一个列表可以存储2^32-1个元素。Redis中的列表支持两端插入和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。
(2) 内部编码
列表的内部编码可以是压缩列表(ziplist)或双端链表(linkedlist)。
压缩列表:压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块(而不是像双端链表一样每个节点是指针)组成的顺序型数据结构;具体结构相对比较复杂,略。与双端链表相比,压缩列表可以节省内存空间,但是进行修改或增删操作时,复杂度较高;因此当节点数量较少时,可以使用压缩列表;但是节点数量多时,还是使用双端链表划算。
压缩列表不仅用于实现列表,也用于实现哈希、有序列表;使用非常广泛。
(3) 编码转换
只有同时满足下面两个条件时,才会使用压缩列表:列表中元素数量小于512个;列表中所有字符串对象都不足64字节。如果有一个条件不满足,则使用双端列表;且编码只可能由压缩列表转化为双端链表,反方向则不可能。
其中,单个字符串不能超过64字节,是为了便于统一分配每个节点的长度;这里的64字节是指字符串的长度,不包括SDS结构,因为压缩列表使用连续、定长内存块存储字符串,不需要SDS结构指明长度。后面提到压缩列表,也会强调长度不超过64字节,原理与这里类似。

2.3 哈希

(1) 概况
(2) 内部编码
内层的哈希使用的内部编码可以是压缩列表(ziplist)和哈希表(hashtable)两种;Redis的外层的哈希则只使用了hashtable。

压缩列表前面已介绍。与哈希表相比,压缩列表用于元素个数少、元素长度小的场景;其优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(1)变为了O(n),但由于哈希中元素数量较少,因此操作的时间并没有明显劣势。
hashtable:一个hashtable由1个dict结构、2个dictht结构、1个dictEntry指针数组(称为bucket)和多个dictEntry结构组成。

(3) 编码转换
如前所述,Redis中内层的哈希既可能使用哈希表,也可能使用压缩列表。

只有同时满足下面两个条件时,才会使用压缩列表:哈希中元素数量小于512个;哈希中所有键值对的键和值字符串长度都小于64字节。如果有一个条件不满足,则使用哈希表;且编码只可能由压缩列表转化为哈希表,反方向则不可能。

2.4 集合

(1) 概况
集合(set)与列表类似,都是用来保存多个字符串,但集合与列表有两点不同:集合中的元素是无序的,因此不能通过索引来操作元素;集合中的元素不能有重复。

一个集合中最多可以存储2^32-1个元素;除了支持常规的增删改查,Redis还支持多个集合取交集、并集、差集。
(2) 内部编码
集合的内部编码可以是整数集合(intset)或哈希表(hashtable)。

哈希表前面已经讲过,这里略过不提;需要注意的是,集合在使用哈希表时,值全部被置为null。

(3) 编码转换
只有同时满足下面两个条件时,集合才会使用整数集合:集合中元素数量小于512个;集合中所有元素都是整数值。如果有一个条件不满足,则使用哈希表;且编码只可能由整数集合转化为哈希表,反方向则不可能。

2.5 有序集合

(1) 概况
有序集合与集合一样,元素都不能重复;但与集合不同的是,有序集合中的元素是有顺序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。
(2) 内部编码
有序集合的内部编码可以是压缩列表(ziplist)或跳跃表(skiplist)。ziplist在列表和哈希中都有使用,前面已经讲过,这里略过不提。

跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。除了跳跃表,实现有序数据结构的另一种典型实现是平衡树;大多数情况下,跳跃表的效率可以和平衡树媲美,且跳跃表实现比平衡树简单很多,因此redis中选用跳跃表代替平衡树。跳跃表支持平均O(logN)、最坏O(N)的复杂点进行节点查找,并支持顺序操作。Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成:前者用于保存跳跃表信息(如头结点、尾节点、长度等),后者用于表示跳跃表节点。具体结构相对比较复杂,略。

(3) 编码转换
只有同时满足下面两个条件时,才会使用压缩列表:有序集合中元素数量小于128个;有序集合中所有成员长度都不足64字节。如果有一个条件不满足,则使用跳跃表;且编码只可能由压缩列表转化为跳跃表,反方向则不可能。


3. redis 知识

redis-cli -h host -p port -a password

3.1 redis 特性

  1. 性能极高,能到 100000 次/s 读写速度
  2. 支持数据的持久化,对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上
  3. 丰富的数据类型,String(字符串)、List(列表)、Hash(字典)、Set(集合)、Sorted Set(有序集合)
  4. 原子性:Redis 的所有操作都是原子性的,多个操作通过 MULTI 和 EXEC 指令支持事务
  5. 丰富的特性:key 过期、publish/subscribe、notify
  6. 支持数据的备份,快速的主从复制
  7. 节点集群,很容易将数据分布到多个Redis实例中

3.2 redis与memcached的区别

  1. 数据结构:Redis 支持 5 种数据结构;Memcached 只支持字符串
  2. 性能对比:单核小数据量存储 Redis 比 Memcached 快;大数据存储 Redis 稍逊
  3. 持久化:Redis 支持持久化;Memecached 数据都在内存之中
  4. 线程模型:Redis 使用单线程模型,基于非阻塞的 IO 多路复用机制,无线程切换;Memecached 使用多线程模型,一个 master 线程,多个 worker 线程
  5. 灾难恢复:Redis 数据丢失后可以通过 aof 恢复;Memecached 挂掉后数据不可恢复
  6. 集群模式:Redis 原生支持cluster模式;Memcached 没有原生的集群模式

3.3 redis 的回收策略

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中,淘汰最近最少使用的数据
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中,淘汰最早会过期的数据
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中,随机淘汰数据
  4. allkeys-lru:从数据集(server.db[i].dict)中,淘汰最近最少使用的数据
  5. allkeys-random:从数据集(server.db[i].dict)中,随机淘汰数据
  6. noenviction:Redis 的默认策略,不回收数据,当达到最大内存时,新增数据返回 error

3.4