Redis

一、Redis是什么?

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。Redis提供数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。Redis具有内置的复制,Lua脚本,LRU逐出,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性。

内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件
基于内存亦可持久化的日志型、Key-Value数据库

Redis官网Redis中文网Redis下载地址本文使用版本
解压后

二、Redis为什么效率快?

  1. Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快。(纯内存操作
  2. 使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。(单线程操作,避免了频繁的上下文切换
  3. 采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。(非阻塞I/O多路复用机制
  4. 采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
  5. 全程使用hash(key-value)结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。

三、Redis的五种数据结构

数据类型

数据类型和编码方式

Redis的数据对象的定义:

typedef struct redisObject {
    unsigned type:4; // OBJ_STRING 0 
    				 // OBJ_LIST 1  
    				 // OBJ_SET 2 
    				 // OBJ_ZSET 3 
    				 // OBJ_HASH 4
    				 
    unsigned encoding:4; // OBJ_ENCODING_RAW 0     /* Raw representation */
						 // OBJ_ENCODING_INT 1     /* Encoded as integer */
						 // OBJ_ENCODING_HT 2      /* Encoded as hash table */
						 // OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */ // 已废弃
						 // OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
						 // OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
						 // OBJ_ENCODING_INTSET 6  /* Encoded as intset */
						 // OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
						 // OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
						 // OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
	void *ptr; // 指向底层实现数据结构的指针
	
    unsigned lru:REDIS_LRU_BITS;
    int refcount;
} robj;

type和encoding的对应关系如下图:
type和encoding的对应关系
编码与数据结构实现:encoding与ptr的对应关系
编码与数据结构实现

3.1 字符串(String)

命令描述
set key value设置指定 key 的值
get key获取指定 key 的值。
del key通过 key,删除键值对
setnx key value只有在 key 不存在时设置 key 的值。
setex key seconds value将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
strlen key返回 key 所储存的字符串值的长度。
append key value如果 key 已经存在并且是一个字符串, APPEND 将 value 追加到 key 原来的值的末尾。
ttl key查看key还有多长时间过期

字符串底层存储的是字符数组

什么情况下选择哪种encoding?(可以使用 OBJECT encoding key 命令来查看编码)

  • int
    设置为long类型的整数,那么encoding是int。
    字符串的int类型

  • embstr
    当我们保存的值小于40的时候,则使用的encoding是embstr。(Redis3.0版本是40)
    embstr没有提供修改函数,所以是只读的。要想对它进行修改,首先会转换成raw,再执行修改,然后结束。
    专门用于保存短字符串的一种编码方式,优点是速度快。

    1. raw调用两次的内存分配函数来分别创建redisObject和sdsstr;
    2. embstr通过一次的内存分配函数来分配一块连续的空间,包含着redisObject和sdsstr,所以执行速度是快的。

字符串的embstr

  • raw
    当我们保存的值大于等于40的时候,则使用的encoding是raw。(Redis3.0版本是40)字符串的raw
  • SDS
    简单的动态的字符串,是redis自己开发的一个字符串的抽象类型。

为什么要自己开发一个字符串类型,而不使用C的字符串?

  1. 计算字符串的长度:C字符串会一个一个元素去遍历,直到\0结束符号,复杂度O(n);SDS复杂度O(1),执行更快。
  2. C字符串有一个问题,缓冲区溢出。C字符串如果是在增加元素之前,没有进行足够的内存分配,那么就会出现缓冲区溢出的请款;SDS是动态的执行空间的扩充,API会自动的进行空间扩展。
  3. C字符串内存的重新分配是很耗性能的。
    SDS采用两种方式解决:
    a. 第一种是空间预分匹配;
    1> 如果对SDS进行修改后,SDS的长度<1M, 那么此时分配的len=free;
    2> 如果对SDS进行修改后,SDS的长度>=1M,len只会按照1M去分配。
    b. 第二种是惰性空间释放。
    1> 删除字符时,不及时释放,还保留空间。
  4. C字符串是以\0结尾的,并且字符串里不能包含空字符串,适用范围比较小;SDS是通过len来判断结尾的,可以保存任意的数据。
struct sdshdr {
	unsigned int len; // 已使用的字符长度
	unsigned int free; // 未使用的字符长度
	char buf[]; // 数组
}

3.2 散列(Hash)

命令描述
hset key field value将哈希表 key 中的字段 field 的值设为 value 。
hgetall key获取在哈希表中指定 key 的所有字段和值
hget key field1 [field2]获取存储在哈希表中指定字段的值/td>
hdel key field1 [field2]删除一个或多个哈希表字段
hexists key field查看哈希表 key 中,指定的字段是否存在。
hlen key获取哈希表中字段的数量
hvals key获取哈希表中所有值
hkeys key获取所有哈希表中的字段
hsetnx key field value只有在字段 field 不存在时,设置哈希表字段的值。
  • ziplist 编码

同时满足以下两种情况,那么就是ziplist 编码,否则就是hashtable 编码:

  1. hash里面的键值对中,key和value的长度全部小于46个字节;
  2. hash里面的键值对中,元素个数小于512个。
    散列的ziplist编码
  • hashtable 编码
    散列的hashtable编码

3.3 列表(List)

场景:消息队列;
特点:有序,可重复,插入和删除快,查找比较慢。

命令描述
lindex key index通过索引获取列表中的元素
rpush key value1 [value2]在列表中添加一个或多个值
lrange key start stop获取列表指定范围内的元素
brpoplpush从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
lrem key count value移除列表元素
llen key获取列表长度
ltrim key start stop对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
lpop key移出并获取列表的第一个元素
lpushx key value将一个或多个值插入到已存在的列表头部
linsert key BEFOREAFTER pivot value
rpop key移除并获取列表最后一个元素
lset key index value通过索引设置列表元素的值
lpush key value1 [value2]将一个或多个值插入到列表头部
rpushx key value为已存在的列表添加值

基本操作

  • ziplist 编码
    列表的ziplist编码

  • linkedlist 编码
    列表的linkedlist编码

3.4 集合(Set)(无序集合)

场景:去重;
(存数值时是有序的)

命令描述
sadd key member1 [member2]向集合添加一个或多个成员
smembers key返回集合中的所有成员
sismember key member判断 member 元素是否是集合 key 的成员
scard key获取集合的成员数
spop key取出集合中的一个元素
del key删除集合
sinter key1 [key2]返回给定所有集合的交集
srem key member1 [member2]移除集合中一个或多个成员
spop key移除并返回集合中的一个随机元素
  • intset 编码
    针对整形集合作为底层实现的。

什么情况下使用intset编码?

  1. 集合对象保存的所有元素都是整数值。
  2. 集合对象保存的元素<=512个。

集合的intset编码

  • hashtable 编码
    底层是字典。每个键都是字符串对象。字典对应的值都为NULL。
    集合的hashtable编码

3.5 有序集合(ZSet)

命令描述
zadd key score1 member1 [score2 member2]向有序集合添加一个或多个成员,或者更新已存在成员的分数
zscore key member返回有序集中,成员的分数值
zrange key start stop [WITHSCORES]正序输出
zrangebyscore key -inf +inf正序输出
zrevrange key 0 -1倒序输出
zrem key member [member …]移除有序集合中的一个或多个成员
zcard key查看key中的元素个数
zrevrank key member返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
zcount key min max计算在有序集合中指定区间分数的成员数
  • ziplist 编码
    在有序集合ziplist编码
  • skiplist 编码
    有序集合skiplist编码

全局常用命令

命令描述
keys *查询所有key值
type key查询key的类型
pexpire key milliseconds设置 key 的过期时间以毫秒计。
exists key检查给定 key 是否存在。
expire key seconds为给定 key 设置过期时间,以秒计。

四、应用

4.1 分布式锁

如何实现?
  1. 加锁和解锁的key要一致;
  2. 不用永久加锁,设置过期时间;
  3. 一定保证加锁与设置过期时间的原子性;
  4. 要支持过期续租,或者是重入(ThreadLocal);

加锁: setnx + 过期时间;
解锁:del key , 要保证原子性;

锁过期问题:
重叠解锁问题(在锁过期的问题基础上):
单点的问题:(redlock算法解决)

4.2 消息队列

应用:消息队列

  • 消息类型
    1. 实时类消息;
    2. 延时类消息;(通过score实现延时)
  • 消费者怎么消费数据?
    1. blpop和brpop拉取数据,有数据就拉取,没数据就一直blocking阻塞,直到有数据。此时redis会检查阻塞的消费者一直没做任何事情,就会断开消费者的连接,此时需要try catch住,继续执行第二次连接;

4.3 位图

应用:位图

五、持久化

Redis本身运行时数据保存在内存中,支持RDBAOF两种持久化机制(这两种方式可以单独使用其中一种,或者混合使用),持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。Redis默认就采用了一种持久化方式,即RDB。(Redis持久化的优先级:AOF>RDB)

5.1 RDB持久化(默认)

支持手工执行和服务器定期执行。二进制文件保存数据。
手动执行,命令:SAVE或BGSAVE。

RDB方式是通过快照完成的,一段时间内Redis会自动将内存中的所有数据进行快照,并且存储到硬盘上(进行快照的条件在配置文件中指定)。当下次启动redis的时候,回去读取硬盘上的快照恢复数据,进而实现Redis的持久化。

持久化时,会在指定的目录中生成一个.rdb结尾的快照文件。

redis.h
struct redisServer {
	...
	// 保存saveparam的数组
	struct saveparam *saveparam;
	// 修改计数器,记录上一次成功执行SAVE或BGSAVE后数据进行多少次修改(包括写入、删除、更新等操作)。
	// sadd name "zhangsan"   dirty计数器+1
	// sadd name "zhangsan" "lisi" "wangwu" "zhaoliu"   dirty计数器+4
	long long  dirty;
	// 上一次执行保存的时间,记录上一次成功执行SAVE或BGSAVE的时间
	time_t lastsave;
	...
}
redis.h
struct saveparam { // 定时操作的定时配置
	// 执行的秒数
	time_t seconds;
	// 修改的次数
	int changes;
}

RDB持久化

redis.conf配置文件
  • 设置触发条件:
    默认配置
    触发条件解释
  • 设置rdb文件路径:
    设置RDB文件路径
测试
  1. 修改配置文件
    在这里插入图片描述

  2. 启动Redis服务,指定配置文件
    在这里插入图片描述

  3. 启动客户端,触发持久化
    在这里插入图片描述

  4. 验证
    在这里插入图片描述

  5. 关闭服务重新打开(直接获取可获得值)
    在这里插入图片描述

5.2 AOF持久化(耗内存)

记录redis来记录数据库的变更。

RDB方式不能提供强一致性,如果Redis进程崩溃,那么两次RDB之间的数据也随之消失。那么AOF的出现很好的解决了数据持久化的实时性,AOF以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令来恢复数据。AOF会先把命令追加在AOF缓冲区,然后根据对应策略写入硬盘(appendfsync)。

客户端 -> Redis服务器 -> 执行命令 -> 保存被执行的命令 -> AOF文件中。

aof_buff : 打开AOF开关以后每次执行完一个写命令,都会把写命令以请求协议格式保存到aof_buff缓冲区中。

在这里插入图片描述

  1. 写入和同步

    • appendfsync always:将aof_buf里的内容写入并同步到AOF文件中,真正的把指令存入磁盘。优点:数据不丢失;缺点:效率低。
    • appendfsync everysec:将aof_buf里的内容写入到AOF文件中,上次同步时间,距离现在超过一秒,进行AOF同步。
    • appendfsync no:将aof_buf里的内容写入到AOF文件中,但是不对AOF文件进行同步。同步操作由操作系统负责,通常最长30s。
      在这里插入图片描述
  2. 启动Redis服务时指定配置文件
    在这里插入图片描述

  3. 启动客户端,触发持久化
    在这里插入图片描述

  4. 验证
    在这里插入图片描述

  5. 关闭服务重新打开(直接获取可获得值)
    在这里插入图片描述

AOF的缺点:

  • AOF文件越来越大,造成空间的大量浪费,数据加载也非常的慢。
  • 多条执行命令的保存,有很大几率都是浪费的。(采用AOF重写解决)
AOF重写
auto-aof-rewrite-percentage 100 // 比上次重写后的体积增加了100%
auto-aof-rewrite-min-size  64mb // aof文件体积超过64MB

同时满足满足上边两个条件就会将多条语句,通过一条语句实现数据的还原。

Redis服务器fork了一个子进程去执行AOF重写,这样主进程不会阻塞。
针对数据不一致的情况,Redis服务器设置了一个AOF重写缓冲区,在子进程建立了的时候开始用。

AOF重写

Redis主进程fork子进程来执行AOF重写,这个子进程创建新的AOF文件来存储重写结果,防止影响旧文件。因为fork采用了写时复制机制,子进程不能访问在其被创建出来之后产生的新数据。Redis使用“AOF重写缓冲区”保存这部分新数据,最后父进程将AOF重写缓冲区的数据写入新的AOF文件中然后使用新AOF文件替换老文件。
以日志的形式记录每个写操作(读操作不记录),只需追加文件但不可以改写文件,Redis启动时会根据日志从头到尾全部执行一遍以完成数据的恢复工作。包括flushDB也会执行。
主要有两种方式触发:有写操作就写、每秒定时写(也会丢数据)。
因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对应一条set语句,写到临时文件中,然后再替换到旧的日志文件(类似rdb的操作方式)。默认触发是当aof文件大小是上次重写后大小的一倍且文件大于64M时触发。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。一般情况下,只要使用默认开启的RDB即可,因为相对于AOF,RDB便于进行数据库备份,并且恢复数据集的速度也要快很多。
开启持久化缓存机制,对性能会有一定的影响,特别是当设置的内存满了的时候,更是下降到几百reqs/s。所以如果只是用来做缓存的话,可以关掉持久化。

六、Redis集群搭建

Redis集群搭建

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抽抽了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值