漫游Redis(一):Redis基本概念

2,928 阅读12分钟

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)】和其他数据组成的一个对象:

redis_redisObject

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底层的存储结构:

redis_encoding

1.3 参考博客

这部分内容参考一些大佬的博客,感谢:

Redis数据结构与基本命令

Redis 五种数据结构

细谈Redis五大数据类型

Redis五种数据类型及应用场景

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种策略:

  1. volatile-lru:从设置了过期时间的数据集中,选择最近最少使用的数据释放;
  2. allkeys-lru:从所有的数据集中,选择最近最少使用的数据释放;
  3. volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放;
  4. allkeys-random:从所有的数据集中,随机选择一个数据进行释放;
  5. volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
  6. noeviction:默认策略,不删除任意数据,这时如果内存不够时,会直接返回错误。

看起来很多,但其实我们仔细看下名字,还是能发现明显的规律,除了第六个noeviction,另外五个都可以拆分成左右两边:

  • 左边表示目标元素,也就是可以被驱逐的元素,是所有元素的一个子集
    • volatile:表示【设置了过期时间的数据集】
    • allkeys:表示【所有的数据】
  • 右边表示淘汰策略,针对左边的元素子集,采取的淘汰算法
    • lru:Least Recently Used的缩写,即【最近最少使用】
    • random:顾名思义,【随机】
    • ttl:按照过期时间,【时间最短最优先】(因此只适用于有设置过期时间的数据集)

3 两种持久化方式

Redis为持久化提供了两种方式:

  • RDB:在指定的时间间隔能对你的数据进行快照存储。(默认方式)
  • AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。

3.1 RDB快照

在指定的时间间隔能对你的数据进行快照存储。

redis_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客户端执行命令savebgsave可以生成dump.rdb文件。

3.2 AOF(append only file)

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

redis_AOF

  • redis.conf:
# 通过配置打开AOF
appendonly yes  

#三种同步方式
appendfsync always      #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec    #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no          #从不同步。高效但是数据不会被持久化。

当然AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF,触发AOF重写时,redis会fork一个子进程去做,不会对redis正常命令处理有太多影响。

3.3 参考博客

这部分内容参考一些大佬的博客,感谢:

一起看懂Redis两种持久化方式的原理

redis持久化的几种方式

5分钟彻底理解Redis持久化

4 小结

  • 1 五种数据类型
    • 字符串(string)
    • 列表(hashe)
    • 集合(list)
    • 有序集(set)
    • 哈希表(sorted set)
  • 2 六种淘汰策略
    • volatile-lru
    • allkeys-lru
    • volatile-random
    • allkeys-random
    • volatile-ttl
    • noeviction
  • 3 两种持久化方式
    • RDB
    • AOF