脚本宝典收集整理的这篇文章主要介绍了Redis知识点详解,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
问题现象
关系型数据库
解决思路
Nosql
NoSQL:即Not-Only-SQL(泛指沸关系型数据库),作为关系型数据库的补充。
作用:应对基于海量用户和数据前提下的数据处理问题。
特征:
解决方案(电商场景)
1.商品基本信息
MySQL
2.商品的附加信息
MongoDB
3.图片信息 分布式文件系统
4.搜索关键字 ES、Lucene、solr
5.热点信息
高频、波段性 Redis、memcache、tair
概念
Redis(Remote Dictionary Server)是用C语言开发的一个开源的高性能键值对(key-value)数据库
特征
Redis的应用
redis-benchmark :性能测试工具,可以在自己本子运行,看看自己本子性能如何
redis-check-aof :修复有问题的AOF文件
redis-check-rdb : 修复有问题的dump.rdb文件
redis-cli :客户端,操作入口
redis-sentinel :Redis集群使用
redis-server :Redis服务器启动命令
默认16个数据库,类似数组下标从0开始,初始默认使用0号库
使用select 来切换数据库。如select 4
同意密码管理,所有库同样密码
dbsize 查看当前数据库的 key 的数量
flushdb 清空当前库
flushall 通杀全部库
Redis 是单线程+多路IO复用
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select 和 poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如线程池)
串行 vs 多线程+锁(memcache) vs 单线程+多路IO复用(Redis)
(与Memcache三点不同:支持多数据类型,支持持久化,单线程+多路IO复用)
这就是串行!
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 通杀全部库
String是Redis最基本的类型,你可以理解为Memcache一模一样的类型,一个key对应一个Value.
String类型是二进制安全的。意味着Redis的String可以包含任何数据。比如png图片或者序列号的对象
String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
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
以新值换,获取旧值的同时获取新值。
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属性表示的长度来判断字符串是否结束。
单键多值
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部。
它的底层实际上是一个双向链表,对两端的操作性能很高,通过索引下标的操作中间性能会较差。
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
List的数据结构为快速链表quickList和ziplist
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也就是压缩列表
它将所有的元素挨着一起存储,分配的是一块连续的内存。
当数据量比较多的时候才会改成quicklist
因为普通的链表需要附加的指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next
Redis将链表和zipList结合起来组成了quickList。也就是将多个ziplist使用双向指针串起来。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
编码转换
当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
不能满足这两个条件的时候使用linkedlist编码
Redis set对外提供的功能于list类似是一个列表的功能,特殊之处在于Set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择。
Redis的Set是String类型的无序集合。它底层其实是一个value为null的hash表,所以添加、删除、查找的复杂度都是O(1)
sadd…
将一个或多个member元素加入到集合key中,已经存在的member元素被忽略
smembers
取出该集合的所有值
sismember
判断集合是否含有该值,有为1,没有为0。
scard
返回该集合的元素个数
srem…
删除集合中的某个元素
spop[count]
随机从该集合中吐出一个(count)值
srandmember
随机从该集合中取出n个值,不会从集合中删除
smove
把集合中一个值从一个集合移动到另一个集合
sinter
返回两个集合中的交集元素
集合对象的编码可以是intset或者hashtable
当集合对象同时满足以下两个条件时,使用 intset 编码:
不能满足这两个条件的就使用hashtable编码。第二个条件可以通过redis.conf中的set-intset-entries进行配置。
Redis hash 是一个键值对集合
Redis hash 是一个String类型的field 和 value 的映射表,hash特别适合用于存储对象,类似于Java里面的Map<String, Object>
hset[…]
给集合中的键赋值
hget
从集合取出 value
hmset…
批量设置hash的值
hexists
查看hash key中,给定域field是否存在
hkeys
列出该hash集合中的所有field
hvals
列出该hash集合的所有value
hincrby
为哈希表 key 中的域 field 的值加上增量 increment
hsetnx
将哈希表 key 中的域 field 的值设置为 value,当且仅当域 field 不存在
和上面列表对象使用ziplist编码一样,当同时满足下面两个条件时,使用ziplist(压缩列表):
不能满足这两个条件的时候使用 hashtable 编码。第一个条件可以通过配置文件中的 set-max-intset-entries进行修改
Redis有序集合Zset集合与普通集合Set非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用于来按照从最低分到最高分的方式排序集合中的成员。集合中的成员是唯一的,但是评分是可以重复的。
因为元素是有序的,所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
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开始
当有序集合对象同时满足以下两个条件,对象使用 ziplist编码
不能满足上面两个条件的使用skiplist编码。意识两个条件可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value进行修改
压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含容易多个节点(entry),每个节点可以保存一个字节数值或者一个整数值。
压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。
压缩列表的每个节点构成如下:
整数集合(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发布订阅(pub/sub)是一种消息通信模式:
发送者(pub)发送消息,订阅者(sub)接收消息
Redis客户端可以订阅任意数量的频道
Redis事务是一个单独的隔离操作:
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。
组队中每个命令出现了报告错误,执行时整个的所有队列都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他命令都会执行。
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 阻塞(block)直到拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁、写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在跟新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁设用于多读的应用类型,这样1可以提高吞吐量。Redis就是利用这种 check-and-set机制实现事务的。
在执行 multi 之前,先执行 watch key1 [key2]… 可以监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断。
取消 watch 命令对所有key的监视。
如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了.
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。
Lua脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的Lua脚本功能,只有在Redis 2.6 以上的版本才可以使用。
利用 Lua 脚本淘汰用户,解决超卖问题。
Redis 2.6 版本以后,通过 lua 脚本解决争抢问题,实际上是redis利用其单线程的特性,用任务队列的方式解决多任务并发问题。
由于Redis是一个内存数据库,所谓内存数据库,就是将数据库中的内容保持在内存中,这与传统的MySQL,Oracle等关系型数据库直接将内保持到硬盘中相比,内存数据库的读写效率比传统数据库要快得多(内存的读写效率远远大于硬盘的读写效率)。但是保持在内存中也随着带来了一个缺点,一旦断电或者宕机,那么内存数据库中的数据将会全部丢失。
为了解决这个缺点,Redis提供将内存数据持久化到硬盘,以及用持久化来恢复数据库的功能。
Redis支持两种形式的持久化,一种是RDB快照(snapshotting),另外一种是AOF(append-only-file)
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复是将快照文件直接读到内存里。
Redis会单独创建(Fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不需要进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对数据恢复完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
在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 文件,但里面是空的。
将备份文件( dump.rdb )移动到 redis 安装目录并启动服务即可,Redis 就会自动加载文件数据至内存。Redis 服务器在载入RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。
/usr/local/bin/
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也更新为执行命令的完成时间。
Redis的持久化方式之一RDB快照是通过保存数据库中的键值对来记录数据库的状态。而另一种持久化方式AOF则是通过保存Redis服务器所执行的写命令来记录数据库状态。
以日志的形式来记录每个写操作(增量保存),将Redis 执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据。换而言之,Redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据恢复的工作。
RDB快照持久化方式就是将 k1 k2 k3 这三个键值对保存到RDB文件中,而AOF持久化则是将执行的 set k1 v1 、set k2 v2 、set k3 v3这三个命令保存到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。
将redis.conf的 appendonly 配置改为 yes 即可
AOF保存文件的位置和RDB保存文件的位置一样,都是通过redis.conf配置文件的dir路径。
重启Redis之后就会进行AOF文件的载入。
异常修复命令:redis-check-aof --fix进行修复
由于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重写程序放到子程序中进行,这样有两个好处:
使用子进程解决了Redis阻塞的问题,但是新的问题也产生了:因为子进程在进行AOF重写期间,服务器进程依然在处理其他命令,这新的命令有可能对Redis的数据进行了修改操作,使得当前数据库状态和重写后的AOF文件状态不一致。
为了解决这个数据状态不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区是在创建子进程后开始使用,当Redis服务器执行一个写命令之后,就会将这个写命令也发送到 AOF重写缓冲区。当子进程完成AOF重写之后,就会给父进程发送一个信号,父进程接收此信号后,就会调用函数将 AOF重写缓冲区的内容都写到新的AOF文件中。
这样将AOF重写对服务器造成的影响降到最低。
如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB快照 是最好的,定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且RDB恢复数据集的速度也要比AOF恢复的速度块,而是使用 RDB 还可以避免AOF 一些隐藏的BUG,否则就使用 AOF持久化。
但是一般情况下不建议单独使用某一种持久化机制,而是应该两种一起用。
如果只是做纯内存缓存,可以都不用。
前面学习的Redis,我们都在一台服务器上进行操作的,也就是说读和写以及备份操作都是在一台Redis服务器上进行的,那么随着项目访问量的增加,对Redis服务器的操作也越加频繁,虽然Redis读写速度都很快,但是一定程度上会造成一定的延迟。
那么为了解决访问量大的问题,通常会采取的一种方式是主从架构Master/Slave,Master以谢为主,Slave以读为主,Master主节点更新后,根据配置,自动同步到Slave节点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
在主机点执行的写操作,能从slave节点获取到。
通过执行 SLAVEOF 127.0.0.1 6378 ,如果主节点6379以前还存在一些key,那么执行命令之后,slave节点也会将以前的信息复制过来。
Master节点能够执行写操作,Slave节点能够执行读命令。
可以在配置文件中修改 slave-read-only 默认为yes
从Slave节点写入的数据从其他Slave节点和Master节点,无法获取。
主机点Master宕机后,Slave角色不会发生变化。
主节点Master挂掉之后,马上启动主机Master,主节点Master又恢复了主机点的角色。
Redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作。
同步
当slave节点发出 SLAVEOF 命令,要求Slave服务器复制Master服务器时,Slave服务器通过向Master服务器发送 SYNC 命令来完成。
命令传播
当同步操作完成之后,主服务器会进行相应的修改命令,这又会导致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服务器只需要接收并执行这些写命令即可保持主从一致。
主从复制虽然解决了主节点的单点故障问题,但是由于所有的写操作都是在Master节点上操作,然后同步到Slave节点,那么同步就会有一定的延时,当系统很繁忙的时候,延时问题就会更加严重,而且会随着Slave节点的增多而愈加严重。
由于主节点只有一个,一旦主节点挂掉之后,Slave节点没法担起主节点的任务,那么整个系统也无法运行。如果主节点挂掉之后,Slave节点能够自动变成主节点,那么问题就解决了,于是哨兵模式诞生了。
哨兵模式就是不断监控Redis是否按照预期良好地运行(至少是保证主节点是存在的),若一台主机出现问题时,哨兵会自动将该主机下的某一个从机设置为新的主机,并让其他从机和新主机建立主从关系。
主观下线
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节点,这里的合适包括如下几点:
2 . 对上面选出来的salve节点执行 slaveof no one 命令让其成为新的 master 节点。
3 . 向剩余的 slave 节点发送命令,让它们成为新的 master 节点的 slave节点,复制规则和设置的 parallel-syncs 参数有关,1表示串行,>1表示并行。
4 . 更新原来的master节点配置为salve节点,并保持对其进行关注,一旦这个节点重新恢复正常后,会命令它去复制新的master节点信息(原来的master节点恢复后是作为slave节点)。
可以从sentinel 日志中出现的几个消息来进行查看故障转移
Redis集群实现了对Redis的水平扩容,即启动N个Redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的 1/N。
Redis集群通过分区(partition)来提高一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
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
一个集群至少要有三个主节点
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址上,每个从库和主库不在一个IP地址上。
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Redis集群数据发布没有使用一致性哈希分布,而是使用虚拟槽分区概念。
Redis内部内置了序号0~16383个槽位,每个槽位可以用来存储一个数据集合,将这些槽位按顺序分配到集群中的各个节点。每次新的数据到来,会通过哈希函数 CRC16(key)算出将要存储的槽位下标,然后通过该下标找到前面分配的Redis节点,最后将数据存储在该节点中。
至于为什么Redis不使用一致性哈希发布,而是虚拟槽分区。因为虚拟槽分区虽然没有一致性哈希那么灵活,但是CRC16(key)%16384已经分布很均匀了,并且对于后面节点增删操作起来也很方便。
按组添加 key value
127.0.0.1:6381> MSET name{user} lucy age{user} 20
-> Redirected to slot [5474] located at 127.0.0.1:6380
OK
计算key的slot值
127.0.0.1:6380> CLUSTER KEYSLOT k1
(integer) 12706
查看槽位有多少个Key
27.0.0.1:6380> CLUSTER COUNTKEYSINSLOT 5474
(integer) 2
只能查看自己服务器的槽位,不能查看其他服务器的槽位。
GLUSTER GETKEYSINSLOT
返回count个slot槽中的键
127.0.0.1:6381> CLUSTER GETKEYSINSLOT 12706 1
1) "k1"
如果主节点宕机,Slave节点会自动升级为Master节点。注意 15秒超时
主机点恢复后,主机点将会变为 Slave 节点。
如果所有某一段插槽的主从节点都宕机,Redis服务是否该能继续?
如果某一段虚拟槽分区都宕机,而 cluster-require-full-coverage为yes,那么整个集群都挂掉。
如果某一段插槽的主从服务器都宕机,而cluster-require-full-coverage为no,那么该虚拟槽分区数据全部都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage
实现扩容
分摊压力
无中心配置相对简单
多建操作是不被支持的
多键的 Redis 事务是不被支持的。lua脚本不被支持。
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至 redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。
缓存穿透:缓存和数据库中都没有的数据,可用户还是源源不断地发起请求,导致每次请求都会到数据库,从而压垮数据库。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kd8mzDgc-1640403329552)(C:UsersDELLAppDataRoamingTyporatypora-user-imagesimage-20211224174331029.png)]
比如客户查询一个根本不存在地东西,首先从Redis中查不到,然后会去数据中查询,数据库中也查询不到,那么就不会将数据放入缓存中,后面如果还有类似源源不断的请求,最后都会压到数据库来处理,从而给数据库造成巨大压力。
Redis中一个热点key在失效的同时,大量的请求过来,从而会全部到达数据库,压垮数据库。
这里要注意的是这是某一个热点Key过期失效,和缓存雪崩是有区别的。比如淘宝618,对于某个特价热门的商品信息,缓存在Redis中,刚好0点,这个商品信息在Redis中过期查不到了,这时候大量的用户又同时正好访问这个商品,就会造成大量的请求同时到达数据库。
Redis中缓存的数据大面积同时失效,或者Redis宕机,从而会导致大量请求直接到数据库,压垮数据库。
对于一个业务系统,如果Redis宕机或大面积的key同时过期,会导致大量请求同时打到数据库,这是灾难性的问题。
随着业务发展的需要,原单体单机部署的系统被演变成分布式集群系统后,由于分布式系统多线下、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
分布式锁主流的实现方案:
每一种分布式锁解决方案都有各自的优缺点:
在Redis内部实现中,前面三个设置过期时间的命令最后都会转换为最后一个PEXPIREAT命令来完成。
PERSIST : 表示将key的过期时间移除。
TTL : 以秒的单位放回键key的剩余生存时间。
PTTL :以毫秒的单位放回键 key 的剩余生存时间。
在Redis内部,每当我们设定一个键的过期时间时,Redis就会将该键带上过期时间存放到一个过期字典中。当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,如果存在,那就获取其过期时间。然后将过期时间的当前系统时间进行对比,比系统时间大,那就没有过期;反之判定该键过期。
Redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用。
惰性删除:Redis的惰性删除策略由 db.c / expireifNeeded函数实现,所有键读写命令执行之前都会调用 expireifNeeded 函数对其进行检查,如果过期,则删除该键,然后执行键不存在的操作;未过期则不作操作,进行执行原有的命令。
**定期删除 :**由redis.c/activeExpireCycle函数实现,函数以一定的频率运行,每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。
注意:并不是一次运行就检查所有的库,所有的键,而是随机检查一定数数量的键。
定期删除函数的运行频率,在Redis2.6版本中,规定每秒运行10次,大概100ms运行一次。在Redis版本后,可以通过修改配置文件redis.conf的hz选项来调整这个次数。
通过过期删除策略,对于某些永远使用不到的键,并且多次定期删除也没选定到并删除,那么这些键同样会一直驻留在内存中,又或者在Redis中存入了大量的键,这些操作可能会导致Redis内存不够用,这时候就需要Redis的内存淘汰策略了。
在配置文件redis.conf中,可以通过参数 maxmemory来设定最大内存。
不设定该参数默认是无限制的,但是通常会设定其为物理内存的四分之三。
当现有内存大于maxmemory时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy,有如下几种淘汰方式:
在redis.conf配置文件中,可以设置淘汰方式。
每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个都是由RedisObject结构来表示:
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//引用计数
int refcount;
//记录最后一次被程序访问的时间
unsigned lru:22;
}robj
对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:
在Redis中,键总是一个字符串对象,而值可以是字符串、列表、集合等对象,所以我们通常说的键为字符串键,表示的是这个键对应的值为字符串对象,我们说一个键为集合键时,表示的是这个键对应的值为集合对象。
对象的ptr指针指向对象底层数据结构,而数据结构由encoding属性来决定。
而每种类型的对象都至少使用了两种不同的编码
内存回收
C语言不具备自动回收内存功能,于是Redis自己构建一个内存回收机制,通过在redisObject结构中的refcount属性实现。
内存共享
refcount属性除了能实现内存回收以外,还能用于内存共享。
Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然降低会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。
当现有内存大于maxmemory时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy,有如下几种淘汰方式:
在redis.conf配置文件中,可以设置淘汰方式。
每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个都是由RedisObject结构来表示:
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//引用计数
int refcount;
//记录最后一次被程序访问的时间
unsigned lru:22;
}robj
对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:
[外链图片转存中…(img-2kN3UEav-1640402748672)]
在Redis中,键总是一个字符串对象,而值可以是字符串、列表、集合等对象,所以我们通常说的键为字符串键,表示的是这个键对应的值为字符串对象,我们说一个键为集合键时,表示的是这个键对应的值为集合对象。
对象的ptr指针指向对象底层数据结构,而数据结构由encoding属性来决定。
[外链图片转存中…(img-9hRIizhU-1640402748672)]
而每种类型的对象都至少使用了两种不同的编码
[外链图片转存中…(img-4fKGhbG4-1640402748673)]
内存回收
C语言不具备自动回收内存功能,于是Redis自己构建一个内存回收机制,通过在redisObject结构中的refcount属性实现。
[外链图片转存中…(img-r4M2bKVH-1640402748673)]
内存共享
refcount属性除了能实现内存回收以外,还能用于内存共享。
[外链图片转存中…(img-5O04cWCd-1640402748673)]
Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然降低会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。
以上是脚本宝典为你收集整理的Redis知识点详解全部内容,希望文章能够帮你解决Redis知识点详解所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。