Redis是一个由C语言开发的一个NoSQL(非关系型数据库),也是一个高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。
本章主要带大家复习一下:
- 五种数据类型
- 六种淘汰策略
- 两种持久化方式
1 五种数据类型
在开始之前,我们先来了解一下RedisObject:
/* Redis 对象 */
typedef struct redisObject {
unsigned type:4;// 类型
unsigned notused:2;// 对齐位
unsigned encoding:4;// 编码方式
unsigned lru:22;// LRU 时间(相对于 server.lruclock)
int refcount;// 引用计数
void *ptr;// 指向对象的值
} robj;
从下图可以看出,RedisObject主要是由【数据类型(type)】、【编码方式(encoding)】和其他数据组成的一个对象:
1.1 RedisObject中的type
而我们常说的redis的五种数据类型,实际上就是RedisObject中的type属性,它可以是下面常量中的其中一个:
/* 对象类型 */
#define REDIS_STRING 0 // 字符串(string)
#define REDIS_LIST 1 // 列表(hashe)
#define REDIS_SET 2 // 集合(list)
#define REDIS_ZSET 3 // 有序集(set)
#define REDIS_HASH 4 // 哈希表(sorted set)
1 字符串
字符串String是Redis中最基本的类型,一个key对应一个value,value可以是String也可以是数字。string 类型是二进制安全的,意思是 redis 的 string 可以包含任何数据,比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
- 常用命令
| 命令 | 用例 | 描述 |
|---|---|---|
| GET | GET key | 获取存储在键中的值 |
| SET | SET key value | 设置存储在键中的值 |
| DEL | DEL key | 删除存储在键中的值 |
| INCR | INCR key | 将键当前存储的值加上给定的整数值 |
| srtlen | srtlen key | 获取字符穿的长度 |
| APPEND | APPEND key value | 将值追加到键当前存储值的末尾 |
- 使用场景
- 存储json类型的对象
- 访问量统计:每次访问博客和文章使用 INCR 命令进行递增(阅读数等)
- 共享session:将分布式应用session存储到Redis中
- 常规计数:博客数,阅读数
2 哈希表
Hash是一个键值(key-value)的集合。Redis 的 Hash 是一个 String 的 Key 和 Value 的映射表,Hash 适合存储对象。
将对象转成Json存储在字符串的数据类型中也可以实现,但对于经常需要修改对象中的某些字段时,hash更为合适,因为hash可以像数据库中update一个属性一样只修改某一项属性值,而字符串类型的则更新整个对象。
- 常用命令
| 命令 | 用例 | 描述 |
|---|---|---|
| HGET | HGET hashName key | 从散列里面获取一个值 |
| HSET | HSET hashName key value | 为散列里面一个键设置值 |
| HMGET | HMGET hashName key [key...] | 从散列里面获取一个或多个键的值 |
| HMSET | HMSET hashName key value [key value...] | 为散列里面一个或多个键设置值 |
| HDEL | HDEL hashName key [key...] | 删除散列里面的一个或多个键值对 |
| HLEN | HLEN hashName | 返回散列包含的键值对数量 |
| HEXISTS | HEXISTS hashName key | 检查键是否存在于散列里面 |
| HKEYS | HKEYS hashName | 获取散列包含的所有键 |
| HVALS | HVALS hashName | 获取散列包含的所有值 |
| HGETALL | HGETALL hashName | 获取散列包含的所有键值对 |
- 使用场景
- 存储对象数据。
- 用户登录信息存储(或可用字符串类型存储json替代)
3 列表
List 列表是字符串的链表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。
Redis的列表相当于Java的LinkedList,但底层实现是快速链表(quicklist)。Redis将链表和ziplist结合起来组成了quicklist,也就是将多个ziplist使用双向指针串联起来使用,既满足了快速的插入删除性能,又不会出现太大的空间冗余。
- 常用命令:
| 命令 | 用例 | 描述 |
|---|---|---|
| RPUSH | RPUSH listName value [value...] | 将一个或多个值推入列表左端 |
| LPUSH | LPUSH listName value [value...] | 将一个或多个值推入列表右端 |
| RPOP | RPOP listName | 移除并返回列表最左端元素 |
| LPOP | LPOP listName | 移除并返回列表最右端元素 |
| LINDEX | LINDEX listName offset | 返回列表中偏移量为offset的元素 |
| LRANGE | LRANGE listName start end | 返回列表中偏移量从start至end范围内的所有元素,包括start和end |
| LTRIM | LTRIM listName start end | 队列表进行切片,只保留偏移量从start至end范围内的所有元素,包括start和end |
| BLPOP | BLPOP listName [listName...] timeout | 从第一个非空列表中弹出位于最左端的元素,或者在timeout秒之内阻塞并等待可弹出的元素出现 |
| BRPOP | BRPOP listName [listName...] timeout | 从第一个非空列表中弹出位于最右端的元素,或者在timeout秒之内阻塞并等待可弹出的元素出现 |
- 使用场景:
- 使用List数据类型模拟队列,堆栈的数据结构
- 文章的关注列表,粉丝列表等
- 消息队列
4 无序集合
Set 是 String 类型的无序集合。Set的结构底层实现是字典,只不过所有的value都是NULL,其他的特性和字典一摸一样。Set 中的元素是没有顺序的,而且是没有重复的。同时带有求交集、并集、差集等操作。
- 常用命令:
| 命令 | 用例 | 描述 |
|---|---|---|
| SADD | SADD setName item [item...] | 将一个或多个元素添加到集合中,返回被添加的元素中原本不存在于集合里面的元素数量 |
| SREM | SREM setName item [item...] | 从集合里面移除一个或多个元素,并返回被移除元素的数量 |
| SISMEMBER | SISMEMBER setName item | 检查元素是否存在于集合里 |
| SCARD | SCARD setName | 返回集合的元素总数 |
| SPOP | SPOP setName | 移除并返回集合中的一个随机元素 |
| SRANDMEMBER | SRANDMEMBER setName [count] | 返回集合中指定数量的随机元素 |
| SMOVE | SMOVE sourceSet destSet item | 如果sourceSet包含元素item,那么从sourceSet里面移除元素item,将元素item添加到destSet中;如果item被移除成功,那么命令返回1,否则返回0 |
| SDIFF | SDIFF setName [setName...] | 计算差集运算,返回计算结果 |
| SDIFFSTORE | SDIFFSTORE destSet setName [setName...] | 计算差集运算,将结果存储在destSet里面 |
| SINTER | SINTER setName [setName...] | 计算交集运算,返回计算结果 |
| SINTERSTORE | SINTERSTORE destSet setName [setName...] | 计算交集运算,将结果存储在destSet里面 |
| SUNION | SUNION setName [setName...] | 计算并集运算,返回计算结果 |
| SUNIONSTORE | SUNIONSTORE destSet setName [setName...] | 计算并集运算,将结果存储在destSet里面 |
- 使用场景:
- 利用唯一性,统计访问网站的所有独立ip,身份证号等
- 好友推荐时,根据tag求交集,大于某个阈值就可以推荐
- 抽奖功能,使用SPOP或SRANDMEMBER的随机弹出元素功能
5 有序集合
Zset 和 Set 一样是 String 类型元素的集合,不允许重复的元素的同时,还能保持有序。它的内部实现是一个Hash字典 + 一个跳表。通过score排序,兼具了排行的作用,非常适合用于做海量的数据的排行处理!
当你需要一个有序的并且不重复的集合列表,那么可以选择 Sorted Set 结构。和 Set 相比,Sorted Set还关联了一个 Double 类型权重的参数Score,使得集合中的元素能够按照 Score 进行有序排列,Redis 正是通过分数来为集合中的成员进行从小到大的排序。
- 常用命令:
| 命令 | 用例 | 描述 |
|---|---|---|
| ZADD | ZADD zsetName score member [score member...] | 将带有给定分值的成员添加到有序集里面 |
| ZREM | ZREM zsetName member [member...] | 从有序集里面移除一个或多个成员,并返回被移除成员的数量 |
| ZCARD | ZCARD zsetName | 返回有序集包含的成员数量 |
| ZINCRBY | ZINCRBY zsetName plusScore member | 将给定成员的分值加上plusScore |
| ZCOUNT | ZCOUNT zsetName min max | 返回分值介于min和max之间的成员数量 |
| ZRANK | ZRANK zsetName member | 返回成员在有序集中的排名 |
| ZSCORE | ZSCORE zsetName member | 返回成员在有序集中的分值 |
| ZRANGE | ZRANGE zsetName start stop [WITHSCORES] | 返回有序集中排名介于start和stop之间的成员,如果给定了可选的WITHSCORES选项,那么命令将成员的分值也一并返回 |
| ZREVRANK | ZREVRANK zsetName member | 返回有序集里成员的排名,成员按照分值由大到小排列 |
| ZREVRANGE | ZREVRANGE zsetName start stop [WITHSCORES] | 返回有序集里给定排名范围的成员,成员按照分值由大到小排列 |
| ZRANGEBYSCORE | ZRANGEBYSCORE zsetName min max [WITHSCORES] [LIMIT offset count] | 返回有序集中,分值介于min和max之间的所有成员 |
| ZREMRANGEBYRANK | ZREMRANGEBYRANK zsetName start stop | 移除有序集中排名介于start和stop之间的所有成员 |
| ZREMRANGEBYSCORE | ZREMRANGEBYSCORE zsetName min max | 移除有序集中分值介于min和max之间的所有成员 |
| ZINTERSTORE | ZINTERSTORE destZset zsetCount zset [zset...] [WEIGHTS weight [weight...]] [AGGREGATE SUM:MIN:MAX] | 计算交集运算,将结果存储在destSet里面 |
| ZUNIONSTORE | ZUNIONSTORE destZset zsetCount zset [zset...] [WEIGHTS weight [weight...]] [AGGREGATE SUM:MIN:MAX] | 计算并集运算,将结果存储在destSet里面 |
- 使用场景:
- 积分排行榜:根据积分排序从小到大
- 获取某个范围的数据:考试80-100分的数据
- 带权重的消息队列
1.2 RedisObject中的encoding
数据在redis底层中的结构,主要有以下8种,也就是redisObject中的编码方式(encoding),他的值就是下面常量中的一个:
/* 对象编码 */
#define REDIS_ENCODING_RAW 0 // 编码为字符串
#define REDIS_ENCODING_INT 1 // 编码为整数
#define REDIS_ENCODING_HT 2 // 编码为哈希表
#define REDIS_ENCODING_ZIPMAP 3 // 编码为 zipmap
#define REDIS_ENCODING_LINKEDLIST 4 // 编码为双端链表
#define REDIS_ENCODING_ZIPLIST 5 // 编码为压缩列表
#define REDIS_ENCODING_INTSET 6 // 编码为整数集合
#define REDIS_ENCODING_SKIPLIST 7 // 编码为跳跃表
不同的数据类型,在底层的数据结构不尽相同,下面这个图就比较直观的展示不同的数据类型在redis底层的存储结构:
1.3 参考博客
这部分内容参考一些大佬的博客,感谢:
2 六个淘汰策略
Redis作为缓存使用,难免会遇到内存空间存储瓶颈,当Redis内存超出物理内存限制时,内存数据就会与磁盘产生频繁交换,使Redis性能急剧下降。此时如何淘汰无用数据释放空间,存储新数据就变得尤为重要了。
在 redis 中,允许用户设置最大使用内存大小 server.maxmemory,也就是redis可使用的内存上限值,redis将会以一定的策略进行淘汰。
redis.conf:
# maxmemory <bytes>
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
# 默认配置
# The default is:
# maxmemory-policy noeviction
# 实际配置
maxmemory 100mb
maxmemory-policy noeviction
2.1 淘汰策略的命名分析
Redis的官方配置中告诉我们有6种策略:
- volatile-lru:从设置了过期时间的数据集中,选择最近最少使用的数据释放;
- allkeys-lru:从所有的数据集中,选择最近最少使用的数据释放;
- volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放;
- allkeys-random:从所有的数据集中,随机选择一个数据进行释放;
- volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
- noeviction:默认策略,不删除任意数据,这时如果内存不够时,会直接返回错误。
看起来很多,但其实我们仔细看下名字,还是能发现明显的规律,除了第六个noeviction,另外五个都可以拆分成左右两边:
- 左边表示目标元素,也就是可以被驱逐的元素,是所有元素的一个子集
- volatile:表示【设置了过期时间的数据集】
- allkeys:表示【所有的数据】
- 右边表示淘汰策略,针对左边的元素子集,采取的淘汰算法
- lru:Least Recently Used的缩写,即【最近最少使用】
- random:顾名思义,【随机】
- ttl:按照过期时间,【时间最短最优先】(因此只适用于有设置过期时间的数据集)
3 两种持久化方式
Redis为持久化提供了两种方式:
- RDB:在指定的时间间隔能对你的数据进行快照存储。(默认方式)
- AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
3.1 RDB快照
在指定的时间间隔能对你的数据进行快照存储。
- redis.conf:
# 发生快照的频次
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
除了在配置文件中使用save关键字设置RDB快照,还可以在命令行中手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件。
3.2 AOF(append only file)
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
- redis.conf:
# 通过配置打开AOF
appendonly yes
#三种同步方式
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。
当然AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF,触发AOF重写时,redis会fork一个子进程去做,不会对redis正常命令处理有太多影响。
3.3 参考博客
这部分内容参考一些大佬的博客,感谢:
4 小结
- 1 五种数据类型
- 字符串(string)
- 列表(hashe)
- 集合(list)
- 有序集(set)
- 哈希表(sorted set)
- 2 六种淘汰策略
- volatile-lru
- allkeys-lru
- volatile-random
- allkeys-random
- volatile-ttl
- noeviction
- 3 两种持久化方式
- RDB
- AOF