一、简介
使用场景
- 缓存
- 排行榜系统
- 计数器应用
- 社交网络
- 消息队列系统
Redis适合存贮数据规模不太大,热数据。
单线程也快
- 纯内存访问,内存的响应时长约为100纳秒,这是Redis达到每秒万级别访问的基础
- 非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多时间
- 单线程避免了线程切换和竞态产生的消耗
二、数据类型
常用数据类型有string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。
这些数据结构都是Redis对外的数据结构,实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,且随Redis版本迭代会有变化。这样设计好处,一是内部编码改进后对外部无影响,二是多种内部编码可以在不同场景下发挥各自的优势。
object encoding key
返回内部编码实现
1、字符串
优先键都是字符串类型,而且其他几种数据结构都是再字符串基础上构建的。
字符串类型的值可以是字符串(简单的字符串,复杂的字符串如JSON、XML)、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB
命令
1、设置值
set key value
对同一个键多次设置会覆盖旧值
setnx key value
只有该键不存在才能设置成功。由于Redis单线程,如果有多个客户端同时执行setnx,只有一个能成功,setnx可以作为分布式锁的一种实现方案。
2、获取值
get key
不存在返回nil
3、批量设置值
mset key value [key value ...]
4、批量获取值
mget key [key ...]
批量操作可以提高效率
Redis可以支撑每秒数万的读写操作,是指Redis服务端的处理能力,而网络可能成为性能瓶颈。
5、计数
incr key
自增1,返回自增后的值,键不存在当作0自增,返回1
decr key // 自减
incrby key increment // 自增指定数字
decrby key increment // 自减指定数字
incrbyfloat key increment // 自增浮点数
内部编码
- int: 8个字节的长整型
- embstr: 小于等于39个字节的字符串
-
raw: 大于39个字节的字符串
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
2、哈希
Redis中的哈希类型是指键值对里的值也是键值对类型,形如value={{field1, value1}, ...{fielNd, valueN}}
命令
1、设置值
hset key field value
hsetnx类似setnx,但作用域由键变成field
2、获取值
hget key value
3、批量设置获取
hmset key field value [field value ...]
hmget key field [field ...]
4、删除field
hdel key field [field ...]
5、计算field个数
hlen key
6、判断field存在
hexists key field
7、获取所有field
hkeys key
8、获取所有值
hvals key
9、field值自增
hincrby key field increment
hincrbyfloat key field increment
内部编码
- ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,更节省内存
-
hashtable(哈希表):当哈希类型无法满足ziplist条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
3、列表
列表类型是用来存储多个有序的字符串的,一个列表最多存储232-1个元素,可以两端插入和弹出元素,故可充当栈和队列。
命令
1、添加元素
rpush key value [value ...] // 从右边插入
lpush key value [value ...] // 从左边插入
2、获取列表长度
llen key
3、索引查值
索引从0开始计算
lrange key start end // start至end的索引范围内的值,包括end
lindex key index // index处的值
4、删除元素
rpop key // 从右边弹出
lpop key // 从左边弹出
5、阻塞弹出
brpop key [key ...] timeout
blpop key [key ...] timeout
- 列表为空:如果timeout=3,则会等待3秒返回nil,如果timeout=0,会一直阻塞等待下去
- 列表非空:timeout无效,立即返回
- 多个键时,会遍历,只要有一个键能弹出元素就返回
内部编码
- ziplist(压缩列表):当列表类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为列表的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,更节省内存
- linkedlist(链表):当列表类型无法满足ziplist条件时,Redis会使用linkedlist作为哈希的内部实现。
- quicklist:结合了ziplist和linkedlist两者的优势
使用场景
- lpush + lpop = 栈
- lpush + rpop = 队列
-
lpush + brpop = 消息队列
4、集合
一个集合最多存储232-1个元素,集合中元素是不重复的,无序的。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。
命令
1、添加元素
sadd key element [element ...]
返回添加成功元素个数
2、删除元素
srem key element [element ...]
返回删除成功元素个数
3、计算元素个数
scard key
直接内部用于统计数量的变量的值,时间复杂度为0(1)
4、判断元素是否存在
sismember key element
存在返回1,反之0
5、随机返回指定个数元素
srandmember key [count]
count是可选参数,不写则默认为1
6、随机弹出一个元素
spop key
返回弹出的元素,集合不存在返回nil
7、获取所有元素
smembers key
元素过多可能阻塞Redis,慎用
集合间的操作
1、交集
sinter key [key ...]
2、并集
sunion key [key ...]
3、差集
sdiff key [key ...]
集合间的运算比较耗时,所以Redis提供了三个命令(原命令+store)将集合间的交集、并集、差集保存在destination key中。
sinterstore destination key [key ...]
sunionstore destination key [key ...]
sdiffstore destination key [key ...]
内部编码
- intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合内部编码实现,节省内存
-
hashtable(哈希表):当集合元素无法满足intset条件时,Redis会使用hashtable
5、有序集合
有序集合的元素也不能重复,同时它给每个元素设置一个分数(score)作为排序的依据,分数可以有重复。有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能。
列表、集合和有序集合三者异同点
命令
1、添加成员
zadd key score member [score member ...]
返回添加成功元素个数。有序集合相比集合提供了排序,但也产生了代价,zadd的时间复杂度为O(log(n)),sadd为O(1)
2、计算成员个数
zcard key
时间复杂度为O(1)
3、计算某个成员的分数
zscore key member
返回分数,成员不存在返回nil
4、计算成员的排名
zrank key member // 分数从低到高
zrevrank key member // 分数从高到低
排名从0开始计算,成员不存在返回nil
5、删除成员
zrem key member [member ...]
返回删除成功元素个数
6、增加成员的分数
zincrby key increment member
返回增加后的分数,increment可以为负数
7、返回指定排名范围的成员
zrange key start end [withscores]
zrevrange key start end [withscores]
withscores同时返回成员的分数
8、返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
limit offset count选项可以限制输出的起始位置和个数。同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大。
9、返回指定分数范围成员个数
zcount key min max
10、删除指定排名内的升序元素
zremrangebyrank key start end
11、删除指定分数范围的成员
zremrangebyscore key min max
内部编码
- ziplist(压缩列表):当有序集合元素个数小于zset-max-ziplist-entries配置(默认128个),同时所有值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为有序集合的内部实现,ziplist更节省内存
-
skiplist(跳跃表):当ziplist无法满足条件时,Redis会使用skiplist作为内部实现
键管理
1、查看所有键
keys *
keys命令会遍历所有键,时间复杂度为O(n),当Redis保存了大量键时,线上环境禁止使用。
2、键总数
dbsize
dbsize命令直接获取Redis内置的键总数变量,时间复杂度为O(n)
3、检查键存在
exists key
存在返回1,反之0
4、删除键
del key [key...]
可一次删除多个键,返回成功删除键个数,删除一个不存在的键返回0
5、键过期
expire key seconds // 键在seconds秒后过期
expireat key timestamp // 键在秒级别时间戳后过期
persist key // 清除过期设置
键要先存在才能设置其过期时间,如果seconds为负,则直接删除键
ttl key
ttl返回键剩余时间,返回以下情况
- 大于等于0的整数:键剩余时间秒数
- -1:键没设置过期时间
- -2:键不存在
6、键所对于值的数据结构类型
type key
可返回string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),键不存在返回none
7、键重命名
rename key newkey
如果newkey已经存在,则newkey的值会被覆盖,可以使用renamenx命令,在newkey不存在时才重命名成功。由于重命名会执行del命令删除旧键,如果键对应的值比较大,会阻塞Redis
8、随机返回一个键
randomkey
三、持久化
RDB
Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:
save [seconds] [changes]
意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存,例如
save 60 100
会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存。可以配置多条save指令,让Redis执行多级的快照保存策略。Redis默认开启RDB快照。
也可以通过BGSAVE命令手动触发RDB快照保存。
RDB优点
- 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
- 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
- 使用RDB文件进行数据恢复比使用AOF要快很多。
RDB缺点
- 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据。
- 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力。
AOF
采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。AOF默认是关闭的,如要开启,进行如下配置:
appendonly yes
AOF提供了三种fsync配置,always/everysec/no,通过配置项[appendfsync]指定:
- appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快
- appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
- appendfsync everysec:折中的做法,交由后台线程每秒fsync一次
随着AOF不断地记录写操作日志,因为所有的操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。
AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:
auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb
上面两行配置的含义是,Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite。同时如果增长的大小没有达到64mb,则不会进行rewrite。
AOF优点
- 最安全,在启用appendfsync always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据。
- AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复。
- AOF文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
AOF缺点
- AOF文件通常比RDB文件更大
- 性能消耗比RDB高
- 数据恢复速度比RDB慢
建议策略
Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:
- AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响
- AOF + fsync every second是比较好的折中方案,每秒fsync一次
- AOF + fsync never会提供AOF持久化方案下的最优性能
- 使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置
- 每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟
Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝24GB / 4kB * 8 = 48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms。
可以通过INFO命令返回的latest_fork_usec字段查看上一次fork操作的耗时(微秒)