0%

Redis

准备工作

安装Redis

  1. 在ubuntu安装Redis
    1
    sudo apt-get install redis-server
  2. Redis服务器连接默认没有密码,可通过更改/etc/redis/redis.conf内的# requirepass foobared来指定登录密码,比如去掉注释后更改密码为requirepass 123456,指定了密码为123456
    1
    2
    3
    sudo vim /etc/redis/redis.conf
    #修改密码
    requirepass 123456
  3. 运行远程连接redis,redis默认只允许本地连接,你可以通过注释掉bind 127.0.0.1::1来支持远程登录。

Redis服务器启动、关闭和重启

每次启动虚拟机,Redis服务器和MySQL服务器都不会启动,因此你需要手动启动

1
2
3
4
5
6
7
8
#Redis
/etc/init.d/redis-server start
/etc/init.d/redis-server stop
/etc/init.d/redis-server restart
#Mysql
service mysql start
service mysql stop
service mysql restart

进入数据库

进入数据库说白了就是通过客户端进入,无论时Mysql还是Redis下载server时都会自带本地的客户端

1
2
3
4
5
redis-cli
#由于更改了密码登录,因此需要先密码认证
auth 123456
#ping测试是否连接,ping后会返回pong
ping

配置数据库

当然,上面的密码配置和远程连接也可以在你进入数据库后配置,当然前提时你知道redis的语法。Redis 的配置文件位于 Redis 安装目录下,文件名为 /etc/redis/redis.conf。你可以通过CONFIG命令查看或设置配置项。

  1. CONFIG get *获取当前的配置
    1
    CONFIG get *
  2. CONFIG set命令来修改配置
    1
    CONFIG set requirepass "123456"

其各配置含义见Redis菜鸟教程

Redis的特点

Redis是一个键值(key-value)存储系统,即键值对非关系型数据库。Redis作为一个高性能的键值数据库,不仅在很大程度上弥补了memcached这类键值存储的不足,而且在部分场合下可以对关系数据库如Mysql起到很好的补充作用。Redis提供了Python、Ruby、Erlang、PHP客户端,使用很方便。

Redis支持存储的值(value)类型包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。这些数据类型都支持push/pop、add/remove以及取交集、并集和差集等丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与memcached一样,为了保证效率,Redis中的数据都是缓存在内存中的,它会周期性地把更新的数据写入磁盘,或者把修改操作写入追加的记录文件;此外,Redis还实现了主从(master-slave)同步

Redis 与其他 key - value 缓存产品有以下三个特点

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis 优势

  • 性能极高 。Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型。 Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets数据类型操作。
  • 操作的原子性。 Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性。Redis还支持 publish/subscribe, 通知, key 过期等等特性

Redis与其他key-value存储有什么不同?

  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。

  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

Redis的数据结构

Redis的低层数据结构:简单动态字符串、双向链表、字典、跳跃表、压缩列表等

简单动态字符串

Redis不是直接使用C语言传统的字符串表示,而是构建了一种名为简单动态字符串。说白了就是一结构体,里面提供char[]、len、free:

1
2
3
4
5
6
7
8
struct sdshdr{
//记录buf数组中已经使用的字节数量
int len;
//记录buf数组中未使用的字节数量
int free;
//字节数组,保存字符串
char buf[];
};
依据上面的结构,SDS与C的字符数组相比,有: 因为SDS即使读取到空字符也能通过len识别是不是读到字符串末尾,因此时二进制安全的。对Redis来说,字符串:

  • string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。

  • string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

  • string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

举例:

1
SET trluper "加油努力"

链表

当有一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的额字符串时,Redis就会使用链表作为列表建的底层实现

节点层结构:

1
2
3
4
5
6
7
8
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;

list底层结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct list {
// 表头节点
listNode *head;
// 表尾节点
listNode *tail;
// 链表所包含的节点数量
unsigned long len;
// 节点值复制函数
void *(*dup)(void *ptr);
// 节点值是放过函数
void (*free)(void *ptr);
// 节点值对比函数
int(*match)(void *ptr, void *key);
} list;
特性

  • 链表被广泛用于实现Redis的各种功能,比如列表建、发布与订阅、慢查询、监视器等。
  • 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。
  • 每个链表使用一个list结构表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息。
  • 因为链表表头的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。
  • 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值

字典

Redis hash 是一个string类型的 field(字段)value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储$ 2^{32} - 1 $键值对(40多亿)。

Redis的字典使用哈希表作为低层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典的一个键值对。

字典结构

1
2
3
4
5
6
7
8
9
10
11
typedef struct dict{
//类型特定函数
dictType *type;
//私有数据
void* privdata;
//哈希表
dictht ht[2];
//rehash索引
//当rehash不能存在时,值为-1
int trehashidx;
}dict;

哈希表结构

1
2
3
4
5
6
7
8
9
10
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
usigned long sizemask;
//该哈希表已有节点数量
unsigned long used;
}dictht;

哈希节点结构

1
2
3
4
5
6
7
8
9
10
11
typedef struct dictEntry{
//键
void* key;
union{
void* value;
uint64_ tu64;
int64_ ts64;
}v;
//指向下一个哈希表节点
struct dictEntry *next;
}dictEntry;
通过上面的两个结构定义就可以知道,字典以哈希表作为低层实现,首先通过传入的key值通过哈希计算,计算这个键值对应在table的哪个下标索引位置,之后采用链地址法来解决哈希冲突。

特性

  • 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。
  • Redis中的字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。
  • Redis使用MurmurHash2算法来计算键的哈希值。
  • 哈希表使用链地址法来解决键冲突

跳表

Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合的元素数量较多,或是比较长的字符串时,Redis就会使用跳表来作为有序集合键的底层实现。Redis跳表由zskiplistNode定义节点,zskiplist保存跳跃表节点的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct zskiplistNode {
// 后退指针
struct zskiplistNode *backward;
// 分值 权重
double score;
// 成员对象
robj *obj;
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} leval[];
} zskiplistNode;
zskipList
1
2
3
4
5
6
7
8
typedef struct zskiplist {
// 表头节点和表尾节点
struct zskiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中层数最大的节点的层数
int leval;
} zskiplist;
特性

  • 跳跃表是有序集合的底层实现之一
  • Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点
  • 每个跳跃表节点的层高都是1至32之间的随机数
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。
  • 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。
  • 跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树,且比平衡树的实现。

压缩列表

压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct ziplist {
unsigned char *zl; /* 压缩列表的地址 */
unsigned int zlbytes; /* 压缩列表已使用的字节数 */
unsigned int zltail_offset;/* 尾节点偏移量 */
unsigned int zllen; /* 压缩列表中节点的数量 */
} ziplist;

typedef struct zlentry {
unsigned int prevrawlensize, prevrawlen; //存储前一个节点的长度所需的字节数和长度。
unsigned int lensize, len; //当前节点长度字段所需的字节数
unsigned int headersize; //节点头部的总长度
unsigned char encoding; //编码方式
unsigned char *p; //指向当前节点值的指针
} zlentry;

优点

  • 节省内存:相比与链表作为列表低层数据结构,压缩列表可以在一定程度上减少存储所需要的内存空间(不需要维护钱后驱指针)。【像listNode未存储元素时需要24字节,字典也是24,而压缩列表只需要6字节)
  • 快速访问:压缩列表因为是采用连续内存空间的存储方式,因此可以提供更快的访问速度,并且使用了连续内存指针,减少随机访问的开销
  • 支持存储多种数据类型:压缩列表可以存储不同类型的元素,如字符串、整数等等。

缺点: - 可能存在扩容开销:当压缩列表需要插入更多元素是,由于内存空间连续,在扩容时会导致内存的重新分配和元素的拷贝操作。 - 存储压缩带来计算开销:为节省内存,压缩列表会压缩存储数据,而在读取时为了还原数据需要执行解压缩。 - 不适合数据量大的情况:压缩列表因为内存空间的连续,并且需要对数据进行压缩和解压缩操作,这对数据量大的情况下可能第一没有那么多的连续内存空间存储,第二压缩算法对于较大数据量情况下可能会导致存储和读取的效率变低。

  • 看他的名字就能看出来,是为了节省内存造的列表结构,由连续内存块组成的顺序型数据结构

对象(Redis的数据类型)

在上面,我们提到了Redis的五种数据结构,有简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等。Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含五种数据类型string(字符串)hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。 数据库之所以使用对象,是因为:

  • Redis执行命令前,根据对象的类型判断一个对象是否可以执行给定的命令。
  • 另一个好处是,针对不同的使用场景,为对象设置了多种不同的数据结构实现,从而优化不同场景下的使用效率
  • Redis对象系统还实现了基于引用计数的内存回收和对象共享机制,从而节省内存和及时回收内存。

Redis支持五种数据类型:string(字符串)hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)

对象编码

Redis使用对象来表示数据库中的键和值,每次当在Redis的数据库中新建一个键值对时,我们至少会创建两个对象,一个对象用作键值的键,另一个对象用作键值的值。Redis中每个对象都由一个redisObject结构表示,有三个属性:

1
2
3
4
5
6
7
8
9
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向低层实现数据结构的指针
void* ptr;
...
};

  • type属性:记录对象类型,这个属性的值可为REDIS_STRING\REDIS_LIST\REDIS_HASH\REDIS_SET\REDIS_ZSET中的一个。对于Redis的键值来说,键总是一个REDIS_STRING类型即字符串类型,而值是可为5种中的一种。
    1
    2
    3
    4
    redis>SET msg "hello redis"
    OK
    redis>TYPE msg
    string
  • ptr:指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。encoding属性记录对象所使用的编码,也就是这个对象用什么数据结构作为对象的底层实现。
    1
    2
    3
    Redis>SET msg "hello redis"
    redis>OBJECT ENCODING msg
    "embstr"

通过encoding属性决定对象所使用的编码,而不是关联固定的数据结构作为底层实现,极大提升了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景的效率,比如**在列表对象包含的元素较少时,Redis使用压缩列表作为底层实现

  • 因为压缩列表比双端链表更节省内存,并且在元素较少时,在内存中以连续快的方式保持压缩列表比双端链表可以更快的被载入缓存中
  • 随着列表对象的包含的元素越来越多,使用压缩列表来保存元素的优势消失时,对象会将底层实现从压缩列表转为功能更强,也更适合保存大量元素的双端链表上面。

字符串

底层实现(内部编码)有3种:int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)

常用命令

依据该数据结构,Redis支持字符串对象,Redis中每个对象的命令都有细微差异,因此你使用哪种命令就是使用哪个数据结构对象存储值。下列是字符串对象的命令:

序号 命令及描述
1 SET key value:设置指定 key 的值。
2 GET key获取指定 key 的值。
3 GETRANGE key start end返回 key 中字符串值的子字符
4 GETSET key value将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
5 GETBIT key offset对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
6 MGET key1 [key2..]获取所有(一个或多个)给定 key 的值。
7 SETBIT key offset value对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
8 SETEX key seconds value将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
9 SETNX key value只有在 key 不存在时设置 key 的值。
10 SETRANGE key offset value用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
11 STRLEN key返回 key 所储存的字符串值的长度。
12 MSET key value [key value ...]同时设置一个或多个 key-value 对。
13 MSETNX key value [key value ...]同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
14 PSETEX key milliseconds value这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
15 INCR key将 key 中储存的数字值增一。
16 INCRBY key increment将 key 所储存的值加上给定的增量值(increment) 。
17 INCRBYFLOAT key increment将 key 所储存的值加上给定的浮点增量值(increment) 。
18 DECR key将 key 中储存的数字值减一。
19 DECRBY key decrementkey所储存的值减去给定的减量值(decrement) 。
20 APPEND key value如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

应用场景:共享session、分布式锁,计数器、限流。

Redis为什么选择SDS结构,而C语言原生的char[]不香吗?

  • SDS中,O(1)时间复杂度,就可以获取字符串长度;而C 字符串,需要遍历整个字符串,时间复杂度为O(n)
  • SDS中,是二进制安全的,因此Redis的字符串可以存储任何序列。

列表

底层实现(内部编码):ziplist(压缩列表)、linkedlist(链表)

常用命令

命令 作用
LPUSH key value1 [value2] 将一个或多个值插入到列表头部
LPUSHX key value 将一个值插入到已存在的列表头部
RPUSH key value1 [value2] 在列表中添加一个或多个值到列表尾部
RPUSHX key value 为已存在的列表添加值
LSET key index value 通过索引设置列表元素的值
BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
LINDEX key index 通过索引获取列表中的元素
BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
LREM key count value 移除列表元素
LRANGE key start stop 获取列表指定范围内的元素
LPOP key 移出并获取列表的第一个元素
RPOP key 移除列表的最后一个元素,返回值为移除的元素。

应用场景:消息队列,文章列表,

哈希表

常用命令

命令 描述
HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。
HMGET key field1 [field2] 获取所有给定字段的值
HDEL key field1 [field2] 删除一个或多个哈希表字段
HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
HKEYS key 获取哈希表中的所有字段

内部编码:ziplist(压缩列表) 、hashtable(哈希表)

应用场景:缓存用户信息等。

注意点:如果开发使用hgetall,哈希元素比较多的话,可能导致Redis阻塞,可以使用hscan。而如果只是获取部分field,建议使用hmget

集合

集合(set)可以保存多个字符串元素,但是不允许有重复元素,并且集合中的元素是无序的,一个集合最多可以存储\(2^{32}-1\)个元素,集合可以进行内部的增删改查和多个集合取交集,并集,差集.Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

底层实现(内部编码):intset(整数集合)、hashtable(哈希表)

常用命令

命令 描述
SADD key member1 [member2] 向集合添加一个或多个成员
SREM key member1 [member2] 移除集合中一个或多个成员
SISMEMBER key member 判断 member 元素是否是集合 key 的成员
SPOP key 移除并返回集合中的一个随机元素
SDIFF key1 [key2] 返回第一个集合与其他集合之间的差集。
SINTER key1 [key2] 返回给定所有集合的交集

注意点:smemberslrange、hgetall都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,可以使用sscan来完成。

应用场景:用户标签,生成随机数抽奖、社交需求。

有序集合

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。适合做排行榜系统

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 \(2^{32} - 1\) (4294967295, 每个集合可存储40多亿个成员)。

在redis sorted sets里面当items内容大于64的时候同时使用了hash和skiplist两种设计实现。这也会为了排序和查找性能做的优化。所以如上可知: 添加和删除都需要修改skiplist,所以复杂度为O(log(n))。 但是如果仅仅是查找元素的话可以直接使用hash,其复杂度为O(1) 其他的range操作复杂度一般为O(log(n)) 当然如果是小于64的时候,因为是采用了ziplist的设计,其时间复杂度为O(n)

常用命令

命令 描述
ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZSCORE key member 返回有序集中,成员的分数值
ZCARD key 获取有序集合的成员数
ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员
ZRANK key member 返回有序集合中指定成员的索引
ZREM key member [member ...] 移除有序集合中的一个或多个成员

底层内部编码:ziplist(压缩列表)、skiplist(跳跃表) 应用场景:排行榜,社交需求(如用户点赞)。

Redis 键命令用于管理 redis 的键。

序号 命令及描述
1 DEL key该命令用于在 key 存在时删除 key。
2 DUMP key序列化给定 key ,并返回被序列化的值。
3 EXISTS key检查给定 key 是否存在。
4 EXPIRE key seconds为给定 key 设置过期时间,以秒计。
5 EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
6 PEXPIRE key milliseconds设置 key 的过期时间以毫秒计。
7 PEXPIREAT key milliseconds-timestamp设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
8 KEYS pattern查找所有符合给定模式( pattern)的 key 。
9 MOVE key db将当前数据库的 key 移动到给定的数据库 db 当中。
10 PERSIST key移除 key 的过期时间,key 将持久保持。
11 PTTL key以毫秒为单位返回 key 的剩余的过期时间。
12 TTL key以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
13 RANDOMKEY从当前数据库中随机返回一个 key 。
14 RENAME key newkey修改 key 的名称
15 RENAMENX key newkey仅当 newkey 不存在时,将 key 改名为 newkey 。
16 SCAN cursor [MATCH pattern] [COUNT count]迭代数据库中的数据库键。
17 TYPE key返回 key 所储存的值的类型。

数据库

要熟悉Redis数据库,就必须理解Redis数据库的组织形式,这样才能建立对Redis的扎实概念,从理解出发认识Redsi。本节主要说明Redis服务器如何保存数据库、数据库如何管理键值对、服务器如何管理保存键的过期时间

服务器的数据库

Reids服务器将所有数据库都保存在服务器redis.h/redisSever结构体的db数组中。

1
2
3
4
5
6
7
8
struct redisSever{
//...
//指示服务器的数据库数量
int dbnum;
//一个数组,保存服务器中所有的数据库
redisDB* db;
//...
};

Redis客户端切换数据库,在RedisClient结构的db属性记录了客户端当前的目标数据库,

1
2
3
4
5
>typedef struct redisClient{
>//...
>//记录当前客户端正在使用的db数据库
>redisDB* db;
>};
我们可以通过SELECT 数字来切换至第几号数据库

数据库的键空间

Redis是一个键值对数据库服务器,因此服务器中每个数据库中都由一个redis/redisDB结构体表示,其中的dict字典保存了数据库的所有键值对,我们将这个字典称为键空间

1
2
3
4
5
6
7
8
typedef struct redisDb{
//...
//数据库的键空间
dict* dict;
//过期字典
dice* expires;
//....
};

  • 键空间的键也就是数据库的键也,每一个键都是一个字符串对象
  • 键空间的值也就是数据库的值,每个值可以是字符串、列表、哈希、集合和有序集合对象
1
2
3
4
5
redis>SET msg "hello world"
redis>RPUSH alphabet "a" "b" "c"
redis>HSET book name "Redis in Action"
redis>HSET book author "Josiah L. Carlson"
redis>HSET book publisher "Manning"

在执行这些命令后,对应的数据库库键空间如下:

键的生存时间

为什么要设置键的生存时间,键的生存时间指的是再内存中的存在时间:

  • 如果缓存没有失效时间,对于读多写少的场景,如果产生脏数据会永远没有机会更新,还会存在冷数据的问题,一段时间内没有被访问过的数据也会持续占据内存空间,造成空间浪费。(空间使用率)
  • 如果时间设置的很短,缓存频繁失效,高并发下数据库压力会上升,严重情况可能会宕机。
  • 缓存失效时间要随机。如果所有的key失效时间同时失效,大量的请求直接落到DB上,这就是常说的缓存雪崩。

Redis有四种不同的命令来设置键的生存时间:

  • EXPIRE <KEY> <TTL>命令将键KEY的生存时间设置为TTL秒
  • PEXPIRE <KEY> <TTL>命令将键KEY的生存时间设置为TTL毫秒
  • `EXPIREAT 命令将key的过期时间设置为指定timestamp秒数时间戳
  • PEXPIRAT <KEY> <timestam>命令将key的过期时间设置为指定timestamp毫秒数时间戳

redisDb结构体还有一个名为expires字典保存数据库中所有键的过期时间,expires被称为过期字典:

  • 过期字典的键是一个指针,这个指针指向键空间的某个键对象
  • 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间。

过期删除策略

一个键过期了,redis服务器是采用什么策略将这些键从内存删除呢,一共有三个策略,redis采用了惰性删除和定期删除策略

  • 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作
    • 定时删除策略对内存最友好,因为该策略是尽可能快的删除键;但缺点是对CPU最不友好,在过期键比较多的时候,删除过期键可能会占用CPU相当一部分时间。
  • 惰性删除:放任键的过期不管,但是每次从键空间中获取键时,都会检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键
    • 惰性删除对CPU的来说是最友好的,因为程序只有在取出键时才对键进行过期检查,这保证了键的删除操作只在非做不可的时候进行,并且删除的键时当前处理的目标键。缺点是对内存最不友好,如果一个键已经过期,而这个键又仍然保留在数据库中,那么只要这个过期键不被删除,它所占用的内存就不会释放。
  • 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面过期的键。至于要删除多少个过期键,以及检查多少个数据库,由算法决定。
    • 定期删除是对定时删除和惰性删除的折中选择

参考文献 菜鸟教程 《Redis设计与实现》