Redis知识点详解
Posted oOoOoOooOO
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis知识点详解相关的知识,希望对你有一定的参考价值。
NoSQL
问题现象
- 海量用户
- 高平发
关系型数据库
- 性能瓶颈:磁盘IO性能低下
- 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群
解决思路
- 降低磁盘IO次数,越低越好 —— 内存存储
- 去除数据间关系,越简单越好 —— 不存储关系,仅存储数据
Nosql
NoSQL:即Not-Only-SQL(泛指沸关系型数据库),作为关系型数据库的补充。
作用:应对基于海量用户和数据前提下的数据处理问题。
特征:
- 可扩容,可伸缩
- 大数据量下高性能
- 灵活的数据模型
- 高可用
解决方案(电商场景)
1.商品基本信息
- 名称
- 价格
- 厂商
2.商品的附加信息
MongoDB
- 描述
- 详细
- 评论
3.图片信息 分布式文件系统
4.搜索关键字 ES、Lucene、solr
5.热点信息
高频、波段性 Redis、memcache、tair
Redis
概念
Redis(Remote Dictionary Server)是用C语言开发的一个开源的高性能键值对(key-value)数据库
特征
- 数据间没有必然的关联关系
- 内部采用单线程机制进行工作
- 高性能。官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s
- 多数据类型支持
- 字符串类型 string
- 列表类型 list
- 散列类型 hash
- 集合类型 set
- 有序集合类型 sorted_set
- 持久化支持,可以进行数据灾难恢复
Redis的应用
- 为热点数据加速查询(主要场景),如热点商品、热点新闻、热点资讯、推广类等高访问量信息等
- 任务队列、如秒数、抢购、购票排队等
- 即时消息查询,如各位排行榜、各类网站访问统计、公交到站消息、在线人数信息(聊天室、网站)、设备信号等
- 时效性信息控制,如验证码控制、投票控制等
- 分布式数据共享,如分布式集群架构中的session分离
- 消息队列
- 分布式锁
下载和安装
redis-benchmark :性能测试工具,可以在自己本子运行,看看自己本子性能如何
redis-check-aof :修复有问题的AOF文件
redis-check-rdb : 修复有问题的dump.rdb文件
redis-cli :客户端,操作入口
redis-sentinel :Redis集群使用
redis-server :Redis服务器启动命令
Redis介绍相关知识
默认16个数据库,类似数组下标从0开始,初始默认使用0号库
使用select 来切换数据库。如select 4
同意密码管理,所有库同样密码
dbsize 查看当前数据库的 key 的数量
flushdb 清空当前库
flushall 通杀全部库
Redis 是单线程+多路IO复用
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select 和 poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如线程池)
串行 vs 多线程+锁(memcache) vs 单线程+多路IO复用(Redis)
(与Memcache三点不同:支持多数据类型,支持持久化,单线程+多路IO复用)
这就是串行!
常用五大数据结构
Redis键(Key)
KEY * 查看当前库使用key 后面可以用正则表达式
EXISTS KEY 判断某个key是否存在
type key 查看 key 是什么类型
del key 删除指定的key数据
unlink key 根据value选择非阻塞删除
仅将keys从 keyspace 元数据中删除,真正的删除会在后续异步操作
expire key(time)为给定的key设置过期时间(单位 秒)
ttl key 查看还有多少秒过期 -1表示永不过期 -2表示已过期
select [库] 命令切换数据库
dbsize 查看当前数据库的key数量
flushdb 清空当前库
flushall 通杀全部库
1.字符串(String)
1.1简介
String是Redis最基本的类型,你可以理解为Memcache一模一样的类型,一个key对应一个Value.
String类型是二进制安全的。意味着Redis的String可以包含任何数据。比如png图片或者序列号的对象
String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
1.2命令
SET key value [EX seconds|PX milliseconds|EXAT timestamp|KEEPTTL] [NX | XX ]
EX 设置过期时间 单位为秒
PX 设置过期时间 单位为毫秒
NX 只有键key不存在时,才会设置key的值
XX 只有键key存在时,才会设置key的值 和 NX互斥
GET
获取key的值
APPEND
将value追加到原值的末尾,如果key不存在,则重写创建一个key,返回字符串长度
SEXNX
只有在key不存在时,设置key的值
incr
将key中存储的数字值增1,value必须为数值,否则
(error) ERR value is not an integer or out of range
decr
将key中存储的数字值减1
incr 和 decr都是执行的过程都是原子的
所谓的原子操作指不会被线程调度机制打断的操作
这种操作一旦开始,就一直运行到结束,中间不会任何context swith(切换到另一个线程)
1.在单线程中,能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间
2.在多线程中,不能被其他进程(线程)打断的操作就叫原子操作
Redis单命令的原子性是由于Redis的单线程
incrby/decrby <步长>
将key中存储的数字值按自定义的步长增减。
MSET key value [key value …]
同时设置一个或多个key-value对
MGET key [key …]
获取多个key的value值
MSET key value [key value …]
同时设置一个或多个key-value对,当且仅当所有给定key都不存在
原子性,有一个失败则都失败
getrange<起始位置><结束位置>
获取值的范围,类似于Java中的substring [前闭,后闭]
setrange<起始位置>
用覆写所存储存的字符串值,从<起始位置>开始(索引从0开始)
如果偏移量大于key处字符串的当前长度,则该字符串将填充零字节以使偏移量适合
setex<过期时间>
设置键值的同时,设置过期时间,单位为second
getset
以新值换,获取旧值的同时获取新值。
1.3数据结构
String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。
struct sdshdr
//记录buf数组中已使用字节的数量
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
对于SDS数据类型,在进行字符修改的时候,会首先根据记录的len的属性检查可见是否满足需求,如果不满足,会进行相应的可见扩展,然后再进行修改操作,不会出现缓冲区溢出。
对于SDS,由于len属性和free属性的存在,对于修改字符串SDS实现了空间预分配和惰性空间释放两种策略:
1.空间预分配:对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需的内存冲分配次数。
2.惰性空间释放:对字符串进行缩短操作时,出现不立即使用内存重新分配来回收缩短后多于的字节,而是free属性将这些字节的数量记录下来,等待后续使用。
二进制安全
C字符串以空字符作为字符串结束的标记,而对于一些二进制文件(如图片等),内容可能包含空字符串,因此C字符无法正确存取;而所有SDS的API都是以处理二进制的方式来处理buf里面的元素,并且SDS不是以空字符来判断字符串是否结束,而是以len属性表示的长度来判断字符串是否结束。
2.Redis列表(List)
2.1简介
单键多值
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部。
它的底层实际上是一个双向链表,对两端的操作性能很高,通过索引下标的操作中间性能会较差。
2.2常用命令
LPUSH/RPUSH …
从左边 / 右边插入一个或者多个值
从左边放
LPOP/RPOP
从左边/右边吐出一个或者(count)个值,值在键在,值光键亡。
RPOP/LPUSH从列表右边吐出一个值插入到列表左边
lrang
按照索引下标获得元素(从左到右)
lrange k1 0 -1 0表示左边第一个,-1表示右边第一个,即(0-1)表示所有
lindex
按照索引下标获得元素,下标从0开始,-1代表右边第一个
llen
获取列表长度
linsertbefore(after)
在的前面(后面)插入,这个value值是列表中遇到的第一个
lrem
从左边删除n个value(从左到右)
lsert
将列表key下标为index的值替换成value
2.3数据结构
List的数据结构为快速链表quickList和ziplist
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也就是压缩列表
它将所有的元素挨着一起存储,分配的是一块连续的内存。
当数据量比较多的时候才会改成quicklist
因为普通的链表需要附加的指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next
Redis将链表和zipList结合起来组成了quickList。也就是将多个ziplist使用双向指针串起来。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
编码转换
当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
- 列表保存元素个数小于512个
- 每个元素长度小于64字节
不能满足这两个条件的时候使用linkedlist编码
3. Redis 集合(Set)
3.1简介
Redis set对外提供的功能于list类似是一个列表的功能,特殊之处在于Set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择。
Redis的Set是String类型的无序集合。它底层其实是一个value为null的hash表,所以添加、删除、查找的复杂度都是O(1)
3.2 常用命令
sadd…
将一个或多个member元素加入到集合key中,已经存在的member元素被忽略
smembers
取出该集合的所有值
sismember
判断集合是否含有该值,有为1,没有为0。
scard
返回该集合的元素个数
srem…
删除集合中的某个元素
spop[count]
随机从该集合中吐出一个(count)值
srandmember
随机从该集合中取出n个值,不会从集合中删除
smove
把集合中一个值从一个集合移动到另一个集合
sinter
返回两个集合中的交集元素
3.3 数据结构
集合对象的编码可以是intset或者hashtable
当集合对象同时满足以下两个条件时,使用 intset 编码:
- 集合对象中所有元素都是整数
- 集合对象所有元素数量不超过512
不能满足这两个条件的就使用hashtable编码。第二个条件可以通过redis.conf中的set-intset-entries进行配置。
4. Redis 哈希(Hash)
4.1 简介
Redis hash 是一个键值对集合
Redis hash 是一个String类型的field 和 value 的映射表,hash特别适合用于存储对象,类似于Java里面的Map<String, Object>
4.2 常用命令
hset[…]
给集合中的键赋值
hget
从集合取出 value
hmset…
批量设置hash的值
hexists
查看hash key中,给定域field是否存在
hkeys
列出该hash集合中的所有field
hvals
列出该hash集合的所有value
hincrby
为哈希表 key 中的域 field 的值加上增量 increment
hsetnx
将哈希表 key 中的域 field 的值设置为 value,当且仅当域 field 不存在
4.3 数据结构
和上面列表对象使用ziplist编码一样,当同时满足下面两个条件时,使用ziplist(压缩列表):
- 列表保存元素个数小于512个
- 每个元素长度小于63个字节
不能满足这两个条件的时候使用 hashtable 编码。第一个条件可以通过配置文件中的 set-max-intset-entries进行修改
5.Redis有序集合Zset(sorted set)
5.1 简介
Redis有序集合Zset集合与普通集合Set非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用于来按照从最低分到最高分的方式排序集合中的成员。集合中的成员是唯一的,但是评分是可以重复的。
因为元素是有序的,所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
5.2 常用命令
zadd key [NX|XX] [GT|LT] [CH] [INCR] score member [score member …]
NX:仅更新存在的成员,不新添加成员
XX:不更新存在的成员,只添加新成员
将一个或多个member元素及其score值加入到有序集key当中。
zrange
返回有序集合,下标在
带上WITHSCORES,可以让分数一起和值返回到结果集
zrangebyscore key minmax [withscores] [limit offset count]
返回有序集key中,所有score值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按score值(从小到大)递增次序排列。
zrevrangebyscore key max min [withscores] [limit offset count]
改为从大到小排序
zincrby
为元素的score加上增量
zrem
删除该集合,指定值的元素
zcount
统计该集合,分数区间内的元素个数
zrank
返回该值在集合中的排名,从0开始
5.3 数据结构
当有序集合对象同时满足以下两个条件,对象使用 ziplist编码
- 保存的元素数量小于128
- 保存的所有元素长度都小于64字节
不能满足上面两个条件的使用skiplist编码。意识两个条件可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value进行修改
5.4 压缩列表
压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含容易多个节点(entry),每个节点可以保存一个字节数值或者一个整数值。
压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。
压缩列表的每个节点构成如下:
- previous_entry_length :记录压缩列表前一个字节的长度。previous_entry_length的长度可能是1个字节或者5个字节,如果上一个节点的长度小于254,则该节点旧可以表示前一个结点的长度了。如果前一个结点的长度大于等于254,则previous_entry_length的第一个字节为254,后面用4个字节表示当前节点前一个节点的长度。利用此原理即当前节点位置减去上一个节点的长度即得到上一个节点的起始位置,压缩列表可以从尾部向头部遍历。这么做很有效的减少内存的浪费。
- encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种整数,encoding区域长度为1字节、2字节或者5字节长。
- content:content区域用于保存节点的内容,节点内容类型和长度由encoding决定。
5.5 整数集合
整数集合(intset)是Redis用于保存整数数值的集合抽象数据类型,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保存集合中不会出现重复元素。
typedef struct intset
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
intset;
整数集合的每个元素都是contents数值的一个数据项,它们按照从大到小的顺序排列,并且不包含任何重复项。
length属性记录了contents数值的大小
需要注意的是虽然contents数值声明为int8_t类型,但是实际上contents数值并不保存任何int8_t类型的值,其真正类型有encoding来决定。
升级
当我们新增的元素类型比原集合类型的长度要大时,需要对整数集合进行升级,才能将新元素放入整数集合中。具体步骤:
- 根据新元素类型,扩展整数集合底层数值的大小,并为新元素分配空间。
- 将底层数值现有的所有元素都转成与新元素相同类型的元素,并将转换后的元素放到正确的位置,放置过程中,维持整个元素顺序都是有序的。
- 将新元素添加到整数集合中(保证有序)。
降级
整数集合不支持降级操作,一旦对数组进行了升级,编码就会一种保持升级后的状态。
Redis配置文件详解
Redis6的发布和订阅
1.什么是发布和订阅
Redis发布订阅(pub/sub)是一种消息通信模式:
发送者(pub)发送消息,订阅者(sub)接收消息
Redis客户端可以订阅任意数量的频道
Redis事务
1.Redis事务的定义
Redis事务是一个单独的隔离操作:
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
2. Multi、Exec、discard
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。
3. 事务的错误处理
组队中每个命令出现了报告错误,执行时整个的所有队列都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他命令都会执行。
4. 事务冲突的问题
4.1 悲观锁
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 阻塞(block)直到拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁、写锁等,都是在做操作之前先上锁。
4.2 乐观锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在跟新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁设用于多读的应用类型,这样1可以提高吞吐量。Redis就是利用这种 check-and-set机制实现事务的。
4.3 WATCH key [key …]
在执行 multi 之前,先执行 watch key1 [key2]… 可以监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断。
4.4 UNWATCH key [key…]
取消 watch 命令对所有key的监视。
如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了.
5.Redis事务三特性
- 单独的隔离操作
- 事务中的所有命令都会序列化、按顺序执行。事务在执行过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念
- 队列中的命令没有提交之前都不会实际执行,因为事务提交前任何指令都不会被实际执行。
- 不保证原子性
- 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
6.Lua脚本在Redis中的优势
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。
Lua脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的Lua脚本功能,只有在Redis 2.6 以上的版本才可以使用。
利用 Lua 脚本淘汰用户,解决超卖问题。
Redis 2.6 版本以后,通过 lua 脚本解决争抢问题,实际上是redis利用其单线程的特性,用任务队列的方式解决多任务并发问题。
Redis持有化
由于Redis是一个内存数据库,所谓内存数据库,就是将数据库中的内容保持在内存中,这与传统的MySQL,Oracle等关系型数据库直接将内保持到硬盘中相比,内存数据库的读写效率比传统数据库要快得多(内存的读写效率远远大于硬盘的读写效率)。但是保持在内存中也随着带来了一个缺点,一旦断电或者宕机,那么内存数据库中的数据将会全部丢失。
为了解决这个缺点,Redis提供将内存数据持久化到硬盘,以及用持久化来恢复数据库的功能。
Redis支持两种形式的持久化,一种是RDB快照(snapshotting),另外一种是AOF(append-only-file)
1. RDB持久化
1.1 定义
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复是将快照文件直接读到内存里。
1.2 备份是如何执行的
Redis会单独创建(Fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不需要进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对数据恢复完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
1.3 Fork
- Fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
- 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了"写时复制技术"。
- 一般情况下父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
1.4 RDB快照的优势和劣势
优势
- RDB是一个非常紧凑(compact)的文件,它保存了Redis在某个时间点上的数据集。这种文件非常适合用于备份和灾难恢复。
- 生成RDB文件的时候,Redis主进程会Fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
- RDB在恢复大数据集时的速度比AOF的恢复速度要块。
劣势
- RDB方式数据没办法做到实时持久化/秒级持久化。因为 bgsave 每次运行都要执行 fork操作创建子进程,属于重量级操作,如果不采用压缩算法(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)。
- RDB文件使用特定二进制格式保存,Redis版本演变过程中有多个格式RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题(版本不兼容)
- 在一定间隔时间做一次备份,所以Redis意味down掉的话,就会丢失最后一次快照后的所有修改(数据丢失)。
1.5 触发的方式
自动触发
在redis.conf配置文件中的 SHAPSHOTTING下。
SAVE :
这里是用来配置触发Redis的RDB持久化条件,也就是什么时候会将内存中的数据保存到硬盘。比如"sava m n",表示m秒内数据集存在n次修改时,自动触发 bgsave 命令。
默认配置:
save 900 1: 表示900秒内如果至少有 1 个 key 的值变换,则保存
save 300 10 : 表示300秒内如果至少有 10 个key的值变化,则保存
save 60 10000 : 表示 60 秒内如果至少有 10000 key的值变化,则保存
stop-writes-on-bgsave-error
默认值为yes。当启用了RDB快照且最后一次后台保存数据失败,Redis是否停止接收数据。
这会让用户意思到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。
如果Redis重启了,那么又可以重新开始接收数据了。
rdbcompression
默认值为yes。对于存储在磁盘中的快照,可以设置是否进行压缩存储。如果是的话,Redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,👿可以设置为no,关闭此功能,但是存储在磁盘上的快照会比较大。
rdbchecksum
默认值是yes。在存储快照后,我们还可以让Redis使用CRC64算法来进行数据效验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭次功能。
dbfilename
设置快照的文件名,默认是 dump.rdb
dir
设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默和当前配置文件保存在同一目录。
手动触发
手动触发Redis进行RDB快照持久化的命令有两种:
1 . save
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB快照过程完成为止。
显然该命令对于内存比较大的实例会造成长时间阻塞,这是致命的缺陷,为了解决此问题,Redis提供了第二种方式。
2 . bgsave
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是Redis进程执行fork()操作创建子进程,RDB持久化由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
基本上Redis内部所有的RDB操作都是采用 bgsave 命令。
执行 flushall 命令,也会产生 dump.rdb 文件,但里面是空的。
1.6 恢复数据
将备份文件( dump.rdb )移动到 redis 安装目录并启动服务即可,Redis 就会自动加载文件数据至内存。Redis 服务器在载入RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。
/usr/local/bin/
1.7 RDB快照自动保存原理
Redis有个服务器状态结构:
struct redisService
//1、记录保存save条件的数组
struct saveparam *saveparams;
//2、修改计数器
long long dirty;
//3、上一次执行保存的时间
time_t lastsave;
首先看记录保存save条件的数组saveparam,里面每个元素都是一个saveparams结构:
struct saveparam
// 秒数
time_t seconds;
// 修改数
int changes;
前面在redis.conf配置文件中进行了关于save的配置
save 900 1: 表示900秒内如果至少有 1 个 key 的值变换,则保存
save 300 10 : 表示300秒内如果至少有 10 个key的值变化,则保存
save 60 10000 : 表示 60 秒内如果至少有 10000 key的值变化,则保存
那么服务器状态中的saveparam数组将会是如下的样子:
dirty 计数器
dirty 计数器记录距离上一次成功执行save命令或者 bgsave 命令之后,Redis服务器进行了多少次修改(包括写入、删除、更新等操作)。
lastsave
lastsave属性是一个时间戳,记录上一次成功执行 save1 命令或者 bgsave 命令的时间。
通过这两个命令,当服务器成功执行一次修改操作,那么drity计数器就会加1,而lastsave属性记录上一次执行 save 或 bgsave 的时间,Redis服务器还有一个周期型操作函数 saverCron,默认每隔100毫秒就会执行一次,该函数会遍历并检测 saveparams数组中的所有保存条件,只要有一个条件被满足,那么就会执行 bgsave命令。
执行完成之后,dirty计数器更新为0,lastsave也更新为执行命令的完成时间。
2. AOF持久化
2.1 AOF简介
Redis的持久化方式之一RDB快照是通过保存数据库中的键值对来记录数据库的状态。而另一种持久化方式AOF则是通过保存Redis服务器所执行的写命令来记录数据库状态。
以日志的形式来记录每个写操作(增量保存),将Redis 执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据。换而言之,Redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据恢复的工作。
RDB快照持久化方式就是将 k1 k2 k3 这三个键值对保存到RDB文件中,而AOF持久化则是将执行的 set k1 v1 、set k2 v2 、set k3 v3这三个命令保存到AOF文件中。
2.2 AOF 配置
appendonly
默认值为 no,也就是说Redis,默认使用的是RDB方式持久化,如果想要开启AOF持久化方式,需要将 appendonly 修改为 yes。
appendfilename
aof文件名,默认是"appendonly.aof"
appendfsync
AOF持久化策略的配置
no 表示不执行 fsync,由操作系统保证数据同步到磁盘,速度最快,但是不太安全;
always 表示每次写入都执行 fsync,以保证数据同步到磁盘,效率很低;
everysec 表示每秒执行一次 fsync,可能会导致丢失这1s数据。
通常选择 everysec,兼顾安全性和效率。
no-appendfsync-on-rewrite
在AOF重写或写入RDB文件的时候,会执行大量IO,此时对于 everysec 和 always 的AOF模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段默认设置为 no。如果对延迟要求很高的应用,这个字段可以设置为 yes,否则还是设置为 no 。这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入。
auto-aof-rewrite-percentage
默认值为100。
AOF自动重写配置,当目前AOF文件大小超过上一次重写的AOF文件大小的百分之多少进行重写,即当 AOF 文件增加到一定大小的时候,Redis能够调用 bgwriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-min-size: 64mb
设置允许重写的最小 AOF文件大小,避免了达到约定变得比但尺寸仍然很小的情况还要重写。
**auto-load-truncated **
文件可能在尾部是不完整的,当redis启动的时候,AOF文件的数据被载入内存。重启可能发生在redis所在主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项,出现这种现象 redis 宕机或者异常终止不会造成尾部不完整现象,可以选择让redis退出,或者导入尽可能多的数据。
如果选择的是yes,当截断的AOF文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。默认值为yes。
2.3 开启AOF持久化
将redis.conf的 appendonly 配置改为 yes 即可
AOF保存文件的位置和RDB保存文件的位置一样,都是通过redis.conf配置文件的dir路径。
2.4 AOF文件恢复
重启Redis之后就会进行AOF文件的载入。
异常修复命令:redis-check-aof --fix进行修复
2.5 AOF 重写
由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF的文件会越来越大。文件越大,占用服务器内存越大以及AOF恢复要求时间越长。
为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过了所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。
如果不进行AOF文件重写,那么AOF文件将保存四条SADD命令,如果使用AOF重写,那么AOF文件中将只会保留下面一条命令:
sadd animals cat fish dog big
也就是说AOF文件重写并不是对源文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的AOF文件。
Redis是单线程工作,如果重写AOF需要比较长的时间,那么在重写AOF期间,Redis将长时间无法处理其他的命令,这显然是不能忍受的。Redis为了解决这个问题,将AOF重写程序放到子程序中进行,这样有两个好处:
- 子进程进行 AOF 重写期间,服务器进程(父进程)可以进行处理其他命令。
- 子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。
使用子进程解决了Redis阻塞的问题,但是新的问题也产生了:因为子进程在进行AOF重写期间,服务器进程依然在处理其他命令,这新的命令有可能对Redis的数据进行了修改操作,使得当前数据库状态和重写后的AOF文件状态不一致。
为了解决这个数据状态不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF重写缓冲区。当子进程完成AOF重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF重写缓冲区的内容都写到新的AOF文件中。
这样将AOF重写对服务器造成的影响降到最低。
2.6 AOF 的优缺点
优点
- AOF持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis最多也就丢失1秒的数据而已。
- AOF文件使用Redis命令追加的形式来构造,因此,即使Redis只能向 AOF 文件写入命令的判断,使用 redis-check-aof 工具也很容易修正 AOF 文件。
- AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心用了FLUSHALL命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢复数据。
缺点
- 对于具有相同数据的Redis,AOF文件通常会比 RDB 文件体积更大。
- 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载较高时,RDB比 AOF 具有更好的性能保证。
- RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF方式更健壮。官方文档也指出,AOF 的确也存在一些BUG,这些BUG在 RDB 没有存在。
2.7 AOF 持久化流程
- 客户端的请求写命令会被 append 追加到 AOF 缓冲区内;
- AOF 缓冲区根据 AOF 持久化策略(always everysec no)将操作sync同步到磁盘的 AOF 文件中;
- AOF 文件大小超过重写策略或手动重写时,会对 AOF 文件rewrite重写,压缩AOF文件容量;
- Redis 服务器重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的。
3. 如何选择
如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB快照 是最好的,定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且RDB恢复数据集的速度也要比AOF恢复的速度块,而是使用 RDB 还可以避免AOF 一些隐藏的BUG,否则就使用 AOF持久化。
但是一般情况下不建议单独使用某一种持久化机制,而是应该两种一起用。
如果只是做纯内存缓存,可以都不用。
主从复制
1. 主从复制定义
前面学习的Redis,我们都在一台服务器上进行操作的,也就是说读和写以及备份操作都是在一台Redis服务器上进行的,那么随着项目访问量的增加,对Redis服务器的操作也越加频繁,虽然Redis读写速度都很快,但是一定程度上会造成一定的延迟。
那么为了解决访问量大的问题,通常会采取的一种方式是主从架构Master/Slave,Master以谢为主,Slave以读为主,Master主节点更新后,根据配置,自动同步到Slave节点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
2. 主从关系
2.1 增量复制
在主机点执行的写操作,能从slave节点获取到。
2.2 全量复制
通过执行 SLAVEOF 127.0.0.1 6378 ,如果主节点6379以前还存在一些key,那么执行命令之后,slave节点也会将以前的信息复制过来。
2.3 Master/Slave读写分离
Master节点能够执行写操作,Slave节点能够执行读命令。
可以在配置文件中修改 slave-read-only 默认为yes
从Slave节点写入的数据从其他Slave节点和Master节点,无法获取。
2.4 主节点宕机
主机点Master宕机后,Slave角色不会发生变化。
主节点Master挂掉之后,马上启动主机Master,主节点Master又恢复了主机点的角色。
2.5 主从复制原理
Redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作。
同步
当slave节点发出 SLAVEOF 命令,要求Slave服务器复制Master服务器时,Slave服务器通过向Master服务器发送 SYNC 命令来完成。
- Slave服务器向Master服务器发送 SYNC 命令
- Master服务器收到 SYNC 命令后开始执行 BGSAVE 命令,在后台生成一个 RDB 文件,并使用一个缓冲区记录从开始执行的所有写命令
- 当主服务器的 BGSAVE 命令执行完毕时,主服务器会将 BGSAVE 命令生成的 RDB 文件发送给 slave 服务器,slave收到RDB文件,并将服务器状态更新为RDB 文件记录的状态。
- Master服务器将缓冲区的所有命令也发送给Slave服务器,Slave服务器执行相应的命令。
命令传播
当同步操作完成之后,主服务器会进行相应的修改命令,这又会导致Mater服务器和Slave服务器状态不一致。
为了让 Master 服务器和 Slave 服务器保持一致,Master 服务器需要对Slave 服务器执行命令传播操作,Master服务器会将自己的写命令发送给Slave服务器执行。Slave服务器执行相应的命令之后,Master服务器状态就会保持一致。
如果Slave服务器在同步主服务器期间,突然断开了连接,而这时候主服务器进行了一些写操作,这时候Slave服务器恢复连接,如果我们在进行同步,那么就必须将主服务器重新生成一个RDB文件,然后给Slave服务器加载,这样虽然能保持一致性,但是其实断开连接之前主从服务器状态是保持一致的,不一致的是Slave服务器断开连接,而主服务器执行了一些写命令,那么Slave服务器恢复连接后能不能只要断开连接的哪些写命令,而不是整个RDB快照呢?
同步操作其实是一个非常耗时的操作,主服务器需要先通过BGSAVE 命令来生成一个RDB文件,然后需要将该文件发送给Slave服务器,,Slave服务器接收该文件之后,接着加载该文件,并且加载期间,Slave服务器是w无法处理其他命令的。
为了解决这个问题,Redis从2.8.版本之后,使用了新的同步命令 PSYNC 来代替 SYNC 命令。该命令的部分重同步功能用于处理断线后重复制的效率问题。也就是是当Slave服务器在断线后连接主服务器时,主服务器只将断开连接后执行的写命令发送给Slave服务器,Slave服务器只需要接收并执行这些写命令即可保持主从一致。
2.6 主从复制的缺点
主从复制虽然解决了主节点的单点故障问题,但是由于所有的写操作都是在Master节点上操作,然后同步到Slave节点,那么同步就会有一定的延时,当系统很繁忙的时候,延时问题就会更加严重,而且会随着Slave节点的增多而愈加严重。
3. 哨兵模式
3.1 定义
由于主节点只有一个,一旦主节点挂掉之后,Slave节点没法担起主节点的任务,那么整个系统也无法运行。如果主节点挂掉之后,Slave节点能够自动变成主节点,那么问题就解决了,于是哨兵模式诞生了。
哨兵模式就是不断监控Redis是否按照预期良好地运行(至少是保证主节点是存在的),若一台主机出现问题时,哨兵会自动将该主机下的某一个从机设置为新的主机,并让其他从机和新主机建立主从关系。
3.2 哨兵模式工作原理
三个定时任务
- 每10秒每个sentinel 对master 和 slave 执行info命令,该命令的作用是用来发生 Slave 节点 和 确定主从关系。
- 每2秒每个 sentinel 通过 master 节点的 channel(名称为 sentinel :hello)交换信息(pub/sub),用来交换对节点的看法(节点的主观下线和客观下线)以及自身信息。
- 每1秒每个 sentinel 对 其他 sentinel 和 主从节点执行 ping 命令,用于心跳检测,作为节点存活的判断依据。
主观下线和客观下线
主观下线
SDOWN:subjectively down 直接翻译为"主观"失效,即当前sentinel哨兵认为某个Master服务器为"不可用"状态。
客观下线
ODOWN : objectively down 直接翻译为"客观"失效,即多个sentinel实例都认为Master处于"SDOWN"状态,那么此时Master将处于ODOWN,ODOWN可以简单理解为Master已经被哨兵集群确定为"不可用",将会开启故障转移机制。
sentinel monitor mymaster 127.0.0.1 6379 2
最后的2表示投票数,也就是说当一台sentient发现一个主服务器无法 ping 通时,就标记为主观下线 sdown;同时另外的sentient 也发现该主服务器宕机,也标记位主管下线 sdown,当多个 sentient (大于等于2)时都标记该主服务器宕机,这时候就变为客观下线,然后进行故障转移。
故障转移
故障转移是由 sentinel 领导者节点来完成的(只需要一个sentinel节点),关于sentinel领导者节点的选取也是每个sentinel向其他的sentient节点发送(我要成为领导者)命令,超过半数sentient节点同意,并且也大于quorum,那么它将成为领导者,如果有多个sentinel 成为了领导者,则会过段时间在进行选举。
sentinel领导节点选举出来后,会通过如下几步进行故障转移:
1 . 从slave节点中选出一个合适的节点作为新的Master节点,这里的合适包括如下几点:
- 选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,不存在则继续下一步判断。
- 选择复制偏移量最大的slave节点(复制最完整的),如果存在则返回,不存在则继续下一步判断。
- 选择 runid 最小的slave节点(启动最早的节点)
2 . 对上面选出来的salve节点执行 slaveof no one 命令让其成为新的 master 节点。
3 . 向剩余的 slave 节点发送命令,让它们成为新的 master 节点的 slave节点,复制规则和设置的 parallel-syncs 参数有关,1表示串行,>1表示并行。
4 . 更新原来的master节点配置为salve节点,并保持对其进行关注,一旦这个节点重新恢复正常后,会命令它去复制新的master节点信息(原来的master节点恢复后是作为slave节点)。
可以从sentinel 日志中出现的几个消息来进行查看故障转移
- +switch-master 表示切换主机点(从节点晋升为主节点)
- +sdown:主观下线
- +odown:客观下线
- +convert-to-slave:切换从节点(原主机点将为从节点)
Redis 集群
1、为什么需要集群
- 并发量
- 通常来说,单台Redis能够执行10万/秒的命令,这个并发基本上能够满足我们所有需求了,但有时候比如做离线计算,为了更快的得出结果,有时候我们希望超过这个并发,那这个时候单机Redis就不满足我们需求了,就需要集群了。
- 数据量
- 通常来说,单台服务器的内存大概在16G~256G之间,由于Redis数据量都是存在内存中的,那如果实际业务要保持在Redis的数据量超过了单台机器的内存,这个时候最简单的方法是增加服务器内存,但是单台服务器内存不可能无限制的增加,纵向扩展不了了,便想到如何进行横向扩展,这时候我们就会想将这些业务数据分散存储在多台Redis服务器中,但是要保证多台Redis服务器能够无障碍的进行内存数据沟通,这也就是Redis集群。
2、什么是集群
Redis集群实现了对Redis的水平扩容,即启动N个Redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的 1/N。
Redis集群通过分区(partition)来提高一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
3、集群操作
3.1 创建集群
redis-cli -a 246899 --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6389 127.0.0.1:6390 127.0.0.1:6391 --cluster-replicas 1
3.2 通过cluster nodes命令查看集群信息
3.3 redis clu
以上是关于Redis知识点详解的主要内容,如果未能解决你的问题,请参考以下文章
带你整理面试过程中关于Redis 主从模式哨兵模式和集群模式详解的相关知识点
Redis对于字符串(String)知识点理解和实操过程例子的详解记录