Redis知识点详解

发布时间:2022-06-27 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Redis知识点详解脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

NoSQL

问题现象

  • 海量用户
  • 高平发

关系型数据库

  • 性能瓶颈:磁盘IO性能低下
  • 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群

解决思路

  • 降低磁盘IO次数,越低越好 —— 内存存储
  • 去除数据间关系,越简单越好 —— 不存储关系,仅存储数据

Nosql

NoSQL:即Not-Only-SQL(泛指沸关系型数据库),作为关系型数据库的补充。

作用:应对基于海量用户和数据前提下的数据处理问题。

特征:

  • 可扩容,可伸缩
  • 大数据量下高性能
  • 灵活的数据模型
  • 高可用

解决方案(电商场景)

1.商品基本信息

MySQL

  • 名称
  • 价格
  • 厂商

2.商品的附加信息

MongoDB

  • 描述
  • 详细
  • 评论

3.图片信息 分布式文件系统

4.搜索关键字 ES、Lucene、solr

5.热点信息

高频、波段性 Redis、memcache、tair

Redis

概念

Redis(Remote Dictionary Server)是用C语言开发的一个开源的高性能键值对(key-value)数据库

特征

  1. 数据间没有必然的关联关系
  2. 内部采用单线程机制进行工作
  3. 高性能。官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s
  4. 多数据类型支持
    1. 字符串类型 string
    2. 列表类型 list
    3. 散列类型 hash
    4. 集合类型 set
    5. 有序集合类型 sorted_set
  5. 持久化支持,可以进行数据灾难恢复

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知识点详解

这就是串行!

常用五大数据结构

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列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部。

它的底层实际上是一个双向链表,对两端的操作性能很高,通过索引下标的操作中间性能会较差。

Redis知识点详解

2.2常用命令

LPUSH/RPUSH …

从左边 / 右边插入一个或者多个值

从左边放

Redis知识点详解

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知识点详解

Redis将链表和zipList结合起来组成了quickList。也就是将多个ziplist使用双向指针串起来。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

编码转换

当同时满足下面两个条件时,使用ziplist(压缩列表)编码:

  1. 列表保存元素个数小于512个
  2. 每个元素长度小于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 编码:

  1. 集合对象中所有元素都是整数
  2. 集合对象所有元素数量不超过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(压缩列表):

  1. 列表保存元素个数小于512个
  2. 每个元素长度小于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]

返回有序集合,下标在 之间的元素,从小到大排序

带上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编码

  1. 保存的元素数量小于128
  2. 保存的所有元素长度都小于64字节

不能满足上面两个条件的使用skiplist编码。意识两个条件可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value进行修改

5.4 压缩列表

压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含容易多个节点(entry),每个节点可以保存一个字节数值或者一个整数值。

压缩列表的原理:压缩列表并不是对数据利用某种算法进行压缩,而是将数据按照一定规则编码在一块连续的内存区域,目的是节省内存。

Redis知识点详解

压缩列表的每个节点构成如下:

Redis知识点详解

  1. previous_entry_length :记录压缩列表前一个字节的长度。previous_entry_length的长度可能是1个字节或者5个字节,如果上一个节点的长度小于254,则该节点旧可以表示前一个结点的长度了。如果前一个结点的长度大于等于254,则previous_entry_length的第一个字节为254,后面用4个字节表示当前节点前一个节点的长度。利用此原理即当前节点位置减去上一个节点的长度即得到上一个节点的起始位置,压缩列表可以从尾部向头部遍历。这么做很有效的减少内存的浪费。
  2. encoding:节点的encoding保存的是节点的content的内容类型以及长度,encoding类型一共有两种,一种字节数组一种整数,encoding区域长度为1字节、2字节或者5字节长。
  3. 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来决定。

升级

当我们新增的元素类型比原集合类型的长度要大时,需要对整数集合进行升级,才能将新元素放入整数集合中。具体步骤:

  1. 根据新元素类型,扩展整数集合底层数值的大小,并为新元素分配空间。
  2. 将底层数值现有的所有元素都转成与新元素相同类型的元素,并将转换后的元素放到正确的位置,放置过程中,维持整个元素顺序都是有序的。
  3. 将新元素添加到整数集合中(保证有序)。

降级

整数集合不支持降级操作,一旦对数组进行了升级,编码就会一种保持升级后的状态。

Redis配置文件详解

Redis6的发布和订阅

1.什么是发布和订阅

Redis发布订阅(pub/sub)是一种消息通信模式:

发送者(pub)发送消息,订阅者(sub)接收消息

Redis客户端可以订阅任意数量的频道

Redis知识点详解

Redis知识点详解

Redis事务

1.Redis事务的定义

Redis事务是一个单独的隔离操作:

事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队

2. Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。

组队的过程中可以通过discard来放弃组队。

Redis知识点详解

3. 事务的错误处理

组队中每个命令出现了报告错误,执行时整个的所有队列都会被取消。

Redis知识点详解

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他命令都会执行。

Redis知识点详解

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快照的优势和劣势

优势

  1. RDB是一个非常紧凑(compact)的文件,它保存了Redis在某个时间点上的数据集。这种文件非常适合用于备份和灾难恢复。
  2. 生成RDB文件的时候,Redis主进程会Fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
  3. RDB在恢复大数据集时的速度比AOF的恢复速度要块。

劣势

  1. RDB方式数据没办法做到实时持久化/秒级持久化。因为 bgsave 每次运行都要执行 fork操作创建子进程,属于重量级操作,如果不采用压缩算法(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)。
  2. RDB文件使用特定二进制格式保存,Redis版本演变过程中有多个格式RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题(版本不兼容)
  3. 在一定间隔时间做一次备份,所以Redis意味down掉的话,就会丢失最后一次快照后的所有修改(数据丢失)。

1.5 触发的方式

自动触发

在redis.conf配置文件中的 SHAPSHOTTING下。

Redis知识点详解

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数组将会是如下的样子:

Redis知识点详解

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 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据恢复的工作。

Redis知识点详解

RDB快照持久化方式就是将 k1 k2 k3 这三个键值对保存到RDB文件中,而AOF持久化则是将执行的 set k1 v1 、set k2 v2 、set k3 v3这三个命令保存到AOF文件中。

2.2 AOF 配置

Redis知识点详解

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文件的内容压缩,只保留可以恢复数据的最小指令集。

Redis知识点详解

如果不进行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 的优缺点

优点

  1. AOF持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis最多也就丢失1秒的数据而已。
  2. AOF文件使用Redis命令追加的形式来构造,因此,即使Redis只能向 AOF 文件写入命令的判断,使用 redis-check-aof 工具也很容易修正 AOF 文件。
  3. AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心用了FLUSHALL命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢复数据。

缺点

  1. 对于具有相同数据的Redis,AOF文件通常会比 RDB 文件体积更大。
  2. 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载较高时,RDB比 AOF 具有更好的性能保证。
  3. RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF方式更健壮。官方文档也指出,AOF 的确也存在一些BUG,这些BUG在 RDB 没有存在。

2.7 AOF 持久化流程

  1. 客户端的请求写命令会被 append 追加到 AOF 缓冲区内;
  2. AOF 缓冲区根据 AOF 持久化策略(always everysec no)将操作sync同步到磁盘的 AOF 文件中;
  3. AOF 文件大小超过重写策略或手动重写时,会对 AOF 文件rewrite重写,压缩AOF文件容量;
  4. Redis 服务器重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的。

3. 如何选择

如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB快照 是最好的,定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且RDB恢复数据集的速度也要比AOF恢复的速度块,而是使用 RDB 还可以避免AOF 一些隐藏的BUG,否则就使用 AOF持久化。

但是一般情况下不建议单独使用某一种持久化机制,而是应该两种一起用。

如果只是做纯内存缓存,可以都不用。

主从复制

1. 主从复制定义

前面学习的Redis,我们都在一台服务器上进行操作的,也就是说读和写以及备份操作都是在一台Redis服务器上进行的,那么随着项目访问量的增加,对Redis服务器的操作也越加频繁,虽然Redis读写速度都很快,但是一定程度上会造成一定的延迟。

那么为了解决访问量大的问题,通常会采取的一种方式是主从架构Master/Slave,Master以谢为主,Slave以读为主,Master主节点更新后,根据配置,自动同步到Slave节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Redis知识点详解

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 命令来完成。

  1. Slave服务器向Master服务器发送 SYNC 命令
  2. Master服务器收到 SYNC 命令后开始执行 BGSAVE 命令,在后台生成一个 RDB 文件,并使用一个缓冲区记录从开始执行的所有写命令
  3. 当主服务器的 BGSAVE 命令执行完毕时,主服务器会将 BGSAVE 命令生成的 RDB 文件发送给 slave 服务器,slave收到RDB文件,并将服务器状态更新为RDB 文件记录的状态。
  4. 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是否按照预期良好地运行(至少是保证主节点是存在的),若一台主机出现问题时,哨兵会自动将该主机下的某一个从机设置为新的主机,并让其他从机和新主机建立主从关系。

Redis知识点详解

3.2 哨兵模式工作原理

三个定时任务

  1. 每10秒每个sentinel 对master 和 slave 执行info命令,该命令的作用是用来发生 Slave 节点 和 确定主从关系。
  2. 每2秒每个 sentinel 通过 master 节点的 channel(名称为 sentinel :hello)交换信息(pub/sub),用来交换对节点的看法(节点的主观下线和客观下线)以及自身信息。
  3. 每1秒每个 sentinel 对 其他 sentinel 和 主从节点执行 ping 命令,用于心跳检测,作为节点存活的判断依据。

主观下线和客观下线

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节点,这里的合适包括如下几点:

  1. 选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,不存在则继续下一步判断。
  2. 选择复制偏移量最大的slave节点(复制最完整的),如果存在则返回,不存在则继续下一步判断。
  3. 选择 runid 最小的slave节点(启动最早的节点)

2 . 对上面选出来的salve节点执行 slaveof no one 命令让其成为新的 master 节点。

3 . 向剩余的 slave 节点发送命令,让它们成为新的 master 节点的 slave节点,复制规则和设置的 parallel-syncs 参数有关,1表示串行,>1表示并行。

4 . 更新原来的master节点配置为salve节点,并保持对其进行关注,一旦这个节点重新恢复正常后,会命令它去复制新的master节点信息(原来的master节点恢复后是作为slave节点)。

可以从sentinel 日志中出现的几个消息来进行查看故障转移

  1. +switch-master 表示切换主机点(从节点晋升为主节点)
  2. +sdown:主观下线
  3. +odown:客观下线
  4. +convert-to-slave:切换从节点(原主机点将为从节点)

Redis 集群

1、为什么需要集群

  1. 并发量
    • 通常来说,单台Redis能够执行10万/秒的命令,这个并发基本上能够满足我们所有需求了,但有时候比如做离线计算,为了更快的得出结果,有时候我们希望超过这个并发,那这个时候单机Redis就不满足我们需求了,就需要集群了。
  2. 数据量
    • 通常来说,单台服务器的内存大概在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命令查看集群信息

Redis知识点详解

3.3 redis cluster 如何分配这六个节点

一个集群至少要有三个主节点

选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。

分配原则尽量保证每个主数据库运行在不同的IP地址上,每个从库和主库不在一个IP地址上。

3.4 什么是 slots

[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知识点详解

至于为什么Redis不使用一致性哈希发布,而是虚拟槽分区。因为虚拟槽分区虽然没有一致性哈希那么灵活,但是CRC16(key)%16384已经分布很均匀了,并且对于后面节点增删操作起来也很方便。

3.5 集群命令

按组添加 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"

3.6 故障恢复

如果主节点宕机,Slave节点会自动升级为Master节点。注意 15秒超时

Redis知识点详解

主机点恢复后,主机点将会变为 Slave 节点。

Redis知识点详解

如果所有某一段插槽的主从节点都宕机,Redis服务是否该能继续?

如果某一段虚拟槽分区都宕机,而 cluster-require-full-coverage为yes,那么整个集群都挂掉。

如果某一段插槽的主从服务器都宕机,而cluster-require-full-coverage为no,那么该虚拟槽分区数据全部都不能使用,也无法存储。

redis.conf中的参数 cluster-require-full-coverage

4 Redis 集群提供了以下好处

实现扩容

分摊压力

无中心配置相对简单

5 Redis集群的不足

多建操作是不被支持的

多键的 Redis 事务是不被支持的。lua脚本不被支持。

由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至 redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

Redis应用问题

1. 缓存穿透

1.1 概念

缓存穿透:缓存和数据库中都没有的数据,可用户还是源源不断地发起请求,导致每次请求都会到数据库,从而压垮数据库。

1.2 流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kd8mzDgc-1640403329552)(C:UsersDELLAppDataRoamingTyporatypora-user-imagesimage-20211224174331029.png)]

Redis知识点详解

比如客户查询一个根本不存在地东西,首先从Redis中查不到,然后会去数据中查询,数据库中也查询不到,那么就不会将数据放入缓存中,后面如果还有类似源源不断的请求,最后都会压到数据库来处理,从而给数据库造成巨大压力。

1.3 解决办法

  1. 业务层效验
    • 用户发过来的请求,根据请求参数进行效验,对于明显错误的参数,直接拦截返回。
    • 比如,请求参数为主键自增id,那么对于请求小于0的id参数,明显不符合,可以直接返回错误请求。
  2. 不存在数据设置短过期时间
    • 对于某个查询为空的数据,可以将这个空结果进行Redis缓存,但是设置很短的过期时间,比如30秒,可以根据实际业务设定。注意一定不要影响到正常业务。
  3. 布隆过滤器
    • 布隆过滤器是一种数据结构,利用极小的内存,可以判断大量的数据"一定不存在或者可能存在"。
    • 对于缓存穿透,我们可以将查询的条件都哈希到一个足够大的布隆过滤器中,用户发送的请求会被先被布隆过滤器拦截,一定不存在的数据就直接拦截返回了,从而避免下一步对数据库的压力。
  4. 进行实时监控
    • 当发现 Redis 的命中率开始急剧降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

2. 缓存击穿

2.1 概念

Redis中一个热点key在失效的同时,大量的请求过来,从而会全部到达数据库,压垮数据库。

2.2 流程

Redis知识点详解

这里要注意的是这是某一个热点Key过期失效,和缓存雪崩是有区别的。比如淘宝618,对于某个特价热门的商品信息,缓存在Redis中,刚好0点,这个商品信息在Redis中过期查不到了,这时候大量的用户又同时正好访问这个商品,就会造成大量的请求同时到达数据库。

2.3 解决办法

  1. 设置热点数据永不过期
    • 对于某个需要频繁获取的信息,缓存在Redis中,并设置其永不过期。当然这种方式比较粗暴,对于某些业务场景是不适合的。
  2. 定时更新
    • 比如这个热点数据的过期时间是 1h,那么每到59minutes时,通过定时任务去更新这个热点 key,并重新设置其过期时间。
  3. 互斥锁
    • 这是解决缓存击穿常用的方法。
    • 互斥锁简单来说就是在Redis中根据Key获取的Value为空时,先上锁,然后从数据库加载,加载完毕,释放锁。其他线程也在请求该key时,发现获取锁失败,则睡眠一段时间(比如100ms)后重试。

3. 缓存雪崩

3.1 概念

Redis中缓存的数据大面积同时失效,或者Redis宕机,从而会导致大量请求直接到数据库,压垮数据库。

3.2 流程

Redis知识点详解

对于一个业务系统,如果Redis宕机或大面积的key同时过期,会导致大量请求同时打到数据库,这是灾难性的问题。

3.3 解决办法

  1. 设置有效期均匀分布
    • 避免缓存设置相近的有效期,我们可以在设置有效期时增加随机值,或者统一规划有效期,使得过期时间均匀分布。
  2. 数据预热
    • 对于即将来临的大量请求,我们可以提前走一遍系统,将数据提前缓存在Redis中,并设置不同的过期时间。
  3. 保证Redis服务高可用
    • 通过Redis的哨兵模式和集群模式,防止Redis集群单节点故障,实现高可用。
  4. 构建多级缓存构架
    • nginx缓存 + redis缓存 + 其他缓存(ehcache等)

4. 分布式锁

4.1 问题描述

​ 随着业务发展的需要,原单体单机部署的系统被演变成分布式集群系统后,由于分布式系统多线下、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

分布式锁主流的实现方案:

  1. 基于数据库实现分布锁
  2. 基于缓存(Redis等)
  3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  1. 性能:Redis最高
  2. 可靠性:zookeeper最高

过期删除策略和内存淘汰策略

1. 设置过期键过期时间

1.1 设置键的命令

  1. EXPIRE :表示将键 key 的生存时间设置为 ttl 秒。
  2. PEXPIRE : 表示将 key 的生存时间设置为 ttl 毫秒
  3. EXPIREAT : 表示将键 key 的生存时间设置为 timestamp 所指定的秒数时间戳。
  4. PEXPIREAT : 表示将键 key 的生存时间设置为 timestamp 所指定的毫秒数时间戳。

在Redis内部实现中,前面三个设置过期时间的命令最后都会转换为最后一个PEXPIREAT命令来完成。

1.2 移除键的过期时间

PERSIST : 表示将key的过期时间移除。

1.3 放回键的剩余生存时间

TTL : 以秒的单位放回键key的剩余生存时间。

PTTL :以毫秒的单位放回键 key 的剩余生存时间。

2. Redis过期时间的判定

​ 在Redis内部,每当我们设定一个键的过期时间时,Redis就会将该键带上过期时间存放到一个过期字典中。当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,如果存在,那就获取其过期时间。然后将过期时间的当前系统时间进行对比,比系统时间大,那就没有过期;反之判定该键过期。

3. 过期删除策略

  1. 定时删除
    • 在设置某个 key 的过期时间时,同时创建一个定时器,让定时器在该过期时间到来时,立即对其进行删除的操作。
    • 优点:定时删除对内存是最友好的,能够保持内存的key一旦过期就能立即从内存中删除。
    • 缺点:对CPU最不友好,在过期键比较多的时候,删除过期键会占用一部分CPU时间,对服务器的响应时间和吞吐量造成影响。
  2. 惰性删除
    • 设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之放回该key.
    • 优点:对CPU友好,我们只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。
    • 缺点:对内存不友好,如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键永远不会被删除,内存永远不会释放,从而造成内存泄漏。
  3. 定期删除
    • 每隔一段时间,我们就对一些key进行检查,删除里面过期的key。
    • 优点:可以通过限制删除操作执行的时长和频率来减少操作对CPU的影响。另外定期删除,也能有效释放过期键占用的内存。
    • 缺点:难以确定删除操作执行的时长和频率。
      • 如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。
      • 如果执行的太少,那又和惰性删除一样了,过期键占用的内存不会及时得到释放。
      • 另外最重要的是,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误。

4.Redis过期删除策略

Redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用。

惰性删除:Redis的惰性删除策略由 db.c / expireifNeeded函数实现,所有键读写命令执行之前都会调用 expireifNeeded 函数对其进行检查,如果过期,则删除该键,然后执行键不存在的操作;未过期则不作操作,进行执行原有的命令。

**定期删除 :**由redis.c/activeExpireCycle函数实现,函数以一定的频率运行,每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。

注意:并不是一次运行就检查所有的库,所有的键,而是随机检查一定数数量的键。

定期删除函数的运行频率,在Redis2.6版本中,规定每秒运行10次,大概100ms运行一次。在Redis版本后,可以通过修改配置文件redis.conf的hz选项来调整这个次数。

通过过期删除策略,对于某些永远使用不到的键,并且多次定期删除也没选定到并删除,那么这些键同样会一直驻留在内存中,又或者在Redis中存入了大量的键,这些操作可能会导致Redis内存不够用,这时候就需要Redis的内存淘汰策略了。

5. 内存淘汰策略

1. 设置Redis最大内存

在配置文件redis.conf中,可以通过参数 maxmemory来设定最大内存。

Redis知识点详解

不设定该参数默认是无限制的,但是通常会设定其为物理内存的四分之三。

2. 设置内存淘汰方式

当现有内存大于maxmemory时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy,有如下几种淘汰方式:

  1. Volatile-LRU 利用LRU算法移除设置过过期时间的key(LRU:最近使用 Least Recently Used)。
  2. allkeys-LRU 利用LRU算法移除任何key(和上一个相比,删除的key包括设置过期时间和不设置过期时间的)。通常使用该方式。
  3. volatile-Random 移除设置过过期时间的随机KEY
  4. allkeys-random 无差别的随机移除
  5. volatile-ttl 移除即将过期的key(minor TTL)
  6. noeviction 不移除任何key,至少返回一个错误,默认选项,一般不会选用。

在redis.conf配置文件中,可以设置淘汰方式。

Redis 底层原理

每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个都是由RedisObject结构来表示:

typedef struct redisObject{
     //类型
     unsigned type:4;
     //编码
     unsigned encoding:4;
     //指向底层数据结构的指针
     void *ptr;
     //引用计数
     int refcount;
     //记录最后一次被程序访问的时间
     unsigned lru:22;
 
}robj

1. type 属性

对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:

Redis知识点详解

在Redis中,键总是一个字符串对象,而值可以是字符串、列表、集合等对象,所以我们通常说的键为字符串键,表示的是这个键对应的值为字符串对象,我们说一个键为集合键时,表示的是这个键对应的值为集合对象。

*2. encoding 属性 和 ptr指针

对象的ptr指针指向对象底层数据结构,而数据结构由encoding属性来决定。

Redis知识点详解

而每种类型的对象都至少使用了两种不同的编码

Redis知识点详解

3. refcount属性

内存回收

C语言不具备自动回收内存功能,于是Redis自己构建一个内存回收机制,通过在redisObject结构中的refcount属性实现。

  1. 创建一个新对象,属性refcount初始化为1
  2. 对象被一个新的程序使用,属性refcount加1
  3. 对象不再被一个程序使用,属性refcount减1
  4. 当refcount值变为0时,对象所占用的内存就会被释放

Redis知识点详解

内存共享

refcount属性除了能实现内存回收以外,还能用于内存共享。

  1. 将数据库键的值指针指向一个现有值得对象
  2. 被共享的值对象引用refcount加1

Redis知识点详解

Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然降低会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。

当现有内存大于maxmemory时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy,有如下几种淘汰方式:

  1. Volatile-LRU 利用LRU算法移除设置过过期时间的key(LRU:最近使用 Least Recently Used)。
  2. allkeys-LRU 利用LRU算法移除任何key(和上一个相比,删除的key包括设置过期时间和不设置过期时间的)。通常使用该方式。
  3. volatile-Random 移除设置过过期时间的随机KEY
  4. allkeys-random 无差别的随机移除
  5. volatile-ttl 移除即将过期的key(minor TTL)
  6. noeviction 不移除任何key,至少返回一个错误,默认选项,一般不会选用。

在redis.conf配置文件中,可以设置淘汰方式。

Redis 底层原理

每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个都是由RedisObject结构来表示:

typedef struct redisObject{
     //类型
     unsigned type:4;
     //编码
     unsigned encoding:4;
     //指向底层数据结构的指针
     void *ptr;
     //引用计数
     int refcount;
     //记录最后一次被程序访问的时间
     unsigned lru:22;
 
}robj

1. type 属性

对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:

[外链图片转存中…(img-2kN3UEav-1640402748672)]

在Redis中,键总是一个字符串对象,而值可以是字符串、列表、集合等对象,所以我们通常说的键为字符串键,表示的是这个键对应的值为字符串对象,我们说一个键为集合键时,表示的是这个键对应的值为集合对象。

*2. encoding 属性 和 ptr指针

对象的ptr指针指向对象底层数据结构,而数据结构由encoding属性来决定。

[外链图片转存中…(img-9hRIizhU-1640402748672)]

而每种类型的对象都至少使用了两种不同的编码

[外链图片转存中…(img-4fKGhbG4-1640402748673)]

3. refcount属性

内存回收

C语言不具备自动回收内存功能,于是Redis自己构建一个内存回收机制,通过在redisObject结构中的refcount属性实现。

  1. 创建一个新对象,属性refcount初始化为1
  2. 对象被一个新的程序使用,属性refcount加1
  3. 对象不再被一个程序使用,属性refcount减1
  4. 当refcount值变为0时,对象所占用的内存就会被释放

[外链图片转存中…(img-r4M2bKVH-1640402748673)]

内存共享

refcount属性除了能实现内存回收以外,还能用于内存共享。

  1. 将数据库键的值指针指向一个现有值得对象
  2. 被共享的值对象引用refcount加1

[外链图片转存中…(img-5O04cWCd-1640402748673)]

Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然降低会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。

脚本宝典总结

以上是脚本宝典为你收集整理的Redis知识点详解全部内容,希望文章能够帮你解决Redis知识点详解所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。