Redis
2021-09-08 03:57:00 5 举报
AI智能生成
Redis 需要掌握知识点
作者其他创作
大纲/内容
Redis 的应用场景
1、内存数据库
不存在 DB 中的信息
登录信息
浏览记录
购物车
........
2、缓存服务器
缓存 DB 信息减少 DB 压力,如商品数据信息
3、session存储
4、任务队列 list
秒杀
请求限流
........
5、分布式锁
6、应用排行
7、数据过期 冷热数据
基本数据类型
String
HashMap
Set
List
Zset
GEO
Stream
数据结构
简单动态字符串
链表
字典
跳跃表
整数集合
压缩列表
对象
IO
同步 IO 和异步 IO
同步:用户空间要的数据,必须等到内核空间给它才做其他事情
异步:用户空间要的数据,不需要等到内核空间给它后才做其他事情。内核空间会异步通知用户进程, 并把数据 直接给到用户空间。
阻塞 IO 和非阻塞 IO
阻塞:阻塞方式下读取或者写入函数将一直等待
非阻塞:读取或者写入函数会立即返回一个状态值。等数据准备就绪后可以采用回调函数(Call Back)的方式获得数据。
IO 模型
同步阻塞 IO
同步非阻塞 IO
IO 多路复用
select
poll
epoll
为什么 Redis 中要使用 IO 多路复用
Redis 是单线程的,所有操作都是按顺序线性执行,但是读写操作等待用户输入输出都是阻塞额,所以 IO 操作在一般情况下不能直接返回,这会导致某一文件的 IO 阻塞导致整个进程无法对其他客户提供服务, IO 多路复用就是为了解决这个问题。
Redis内存管理
redis的过期策略
定期删除(主动检查)
1、从具有相关过期的秘钥集中测试20个随机秘钥
2、删除所有找到的已过期的秘钥
3、如果超过 25%的秘钥已过期,从步骤1重新开始
惰性删除(被动检查)
在获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期,如果过期了此时就会删除
redis过期策略能否保证不会内存溢出?
如果定期删除抽样检查并未检查到过期数据,且该数据又没有被访问到无法进行惰性删除,那么它依旧会长期驻扎,所以需要配合 redis 的内存管理机制防止内存溢出
redis的内存管理机制
volatile-lru
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key
volatile-ttl
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,剩余时间最短的 key 优先移除
volatile-random
当内存不足以容纳新写入数据时,在设置了过期时间的键值对中,随机移除某个 Key
allkeys-lru
当内存不足以容纳新写入数据时,在所有键值对中,移除最近最少使用的 Key。
allkeys-random
当内存不足以容纳新写入数据时,在所有键值对中,随机移除某个 Key。
no-enviction
默认策略,当内存不足以容纳新写入数据时,新写入操作会报错
Redis 事务
命令
MULTI
用于标记事务开始,之后的命令会被放入队列之中等待执行
EXEC
执行之前放入队列的命令,事务结束
DISCARD
清除队列
WATCH
监控某个key 的 value 值,当被监控的key值变化后队列清除,队列里的命令不会执行,可实现 Redis 乐观锁
基本要素
Redis 的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合
Redis 将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
Redis 不支持回滚操作
事务失败处理
语法错误
事务里的命令都不执行,队列全部清除
运行错误
正确的命令执行,错误命令执行失败
Redis 持久化操作
RDB
什么是 RDB?
子进程将数据写入一个临时文件,持久化结束替换上次的持久化文件 即 dump.rdb,因为是子进程操作,主进程没有 IO 操作,保证了性能
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。 这种方式是默认已经开启了,不需要配置.
RDB 持久化触发时机
1、shutdown时,如果没有开启aof,会触发 RDB
2、配置文件中默认的快照配置 (redis 自动调用的)
save <second> <changes>
3、执行命令save或者bgsave
save
阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
bgsave
fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求
4、执行flushall命令
5、第一次执行主从复制操作
RDB 优缺点
优点
RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无需执行任何磁盘 I/O 操作。同时这个也是一个缺点,如果数据集比较大的时候, fork 可以能比较耗时,造成服务器在一段时间内停止处理客户端的请求;
缺点
使用 RDB 方式实现持久化,一旦 Redis 异常退出,就会丢失最后一次快照以后更改的所有数据。这个时候我们就需要根据具体的应用场景,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受范围。如果数据相对来说比较重要,希望将损失降到最小,则可以使用 AOF 方式进行持久化
AOF
什么是 AOF?
AOF 持久化触发时机
根据配置文件配置项
no:表示等操作系统进行数据缓存同步到磁盘(快,持久化没保证)
always:同步持久化,每次发生数据变更时,立即记录到磁盘(慢,安全)
everysec:表示每秒同步一次(默认值,很快,但可能会丢失一秒以内的数据)
AOF 重写机制
什么是 AOF 重写
当AOF文件增长到一定大小的时候Redis能够调用 bgrewriteaof对日志文件进行重写 。当AOF文件大小的增长率大于该配置项时自动开启重写(这里指超过原大小的100%)。重写的目的就是 给备份文件瘦身
AOF 重写过程
Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发
生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕, Redis 就会从旧 AOF 文件切
换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕, Redis 就会从旧 AOF 文件切
换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
AOF 重写相关参数(配置文件中可见)
auto-aof-rewrite-percentage 100
表示当前aof文件大小超过上一次aof文件大小的百分之多少的时候会进行重写。如果之前没有重写过,以
启动时aof文件大小为准,如上一次是64mb,超过 128mb 则开始重写
启动时aof文件大小为准,如上一次是64mb,超过 128mb 则开始重写
auto-aof-rewrite-min-size 64mb
允许重写 aof 文件的最小限制,也就是文件大小小于 64mb 的时候,不需要进行优化
混合持久化
基本信息
4.0版本的混合持久化默认关闭的,通过 aof-use-rdb-preamble 配置参数控制,yes则表示开启,no表示禁用,5.0之后默认开启。
过程
混合持久化是通过 bgrewriteaof 完成的,不同的是当开启混合持久化时,fork 出的子进程先将共享的内存副本全量的以 RDB 方式写入 AOF 文件,然后在将重写缓冲区的增量命令以 AOF 方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。简单的说:新的 AOF 文件前半段是 RDB 格式的全量数据后半段是 AOF 格式的增量数据,
如何选择 RDB 和 AOF?
1、内存数据库 rdb+aof 混合持久化,数据不能丢
2、缓存服务器 rdb
3、不建议 只使用 AOF (性能差),恢复时: 先 AOF 再 RDB
Redis 主从同步
为什么要使用主从同步?
1、redis-serer 单点故障
2、单台 redis QPS 有限
主从同步应用场景分析
1、读写分离场景,规避 redis 单机瓶颈
2、故障切换,master 节点宕机后 slave 顶替
主从同步基本概念
1、多台 redis 服务器只有一台主机,从机可以有多台
2、主机只负责写(也可以设置为读写),从机只读
3、从服务器只负责读取,并同步主机数据
4、当主服务器宕机,可以从从服务器中选出一台作为主服务器
5、主从同步过程中,master 非阻塞(bgsave命令)
6、slave 初次同步需删除数据,阻塞连接请求
主从同步流程
1、先启动主服务器
2、当从服务器启动时,读取同步的配置,根据配置决定是否使用当前数据响应客户端,然后发送 SYNC 命令。当主服务器收到同步命令时,就会执行 bgsave 命令备份数据,但主服务器并不会拒绝客户端的读写,而是将客户端的写命令写入缓冲区,从服务器未收到主服务器备份的快照文件时,会根据其配置决定使用现有数据响应客户端或者拒绝。
3、当 bgsave 命令被主服务器执行完后,开始向从服务器发送备份文件,这时候从服务器会丢掉所有现有数据,开始载入发送的快照文件。
4、当主服务器发送完备份文件后,从服务器就会执行这些写入命令,此时会把 bgsave 执行之后的缓存区内的写入命令也发送给从服务器,从服务器完成备份文件解析,就开始像往常一样接受命令,等待命令写入。
5、缓冲区的命令发送完成之后,当主服务器执行一条写命令后,就同时往从服务器发送同步写入命令,从服务器就和主服务器保持一致,而此时从服务器完成主服务器发送的缓冲区命令后,就开始等待主服务器命令了
主从同步实现原理
全量同步
1、同步快照阶段
Master 创建并发送快照给 Slave , Slave 载入并解析快照。 Master 同时将此阶
段所产生的新的写命令存储到缓冲区。
段所产生的新的写命令存储到缓冲区。
2、同步写缓冲阶段
Master 向 Slave 同步存储在缓冲区的写操作命令。
3、同步增量阶段
Master 向 Slave 同步写操作命令。
总结
1、全量同步会触发 RDB 持久化,执行 bgsave 命令
2、增量同步是在全量同步结束后执行增量操作
增量同步
Redis 增量同步主要指 Slave 完成初始化后开始正常工作时,Master 发生的写操作同步到 Slave 的过
程。通常情况下, Master 每执行一个写命令就会向 Slave 发送相同的写命令,然后 Slave 接收并执
行。
程。通常情况下, Master 每执行一个写命令就会向 Slave 发送相同的写命令,然后 Slave 接收并执
行。
总结
1、Redis 的主从同步,分为全量同步和增量同步。
2、只有从机第一次连接上主机是全量同步。
3、断线重连有可能触发全量同步也有可能是增量同步( master 判断 runid 是否一致,不一致则是全量同步)。
主从同步缺陷
1、Master 同步到 Slave 机器有一定的延迟,Slave 机器数量的增加也会使这个问题更加严重。
2、当主机宕机之后,将不能进行写操作,需要手动将从机升级为主机,从机需要重新制定 master
Redis 哨兵模式
主要功能
1、监控:通过发送命令·让 Redis 服务器返回监测其运行状态,包括主从服务器
2、故障自动切换:当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知到其他服务器,修改配置文件切换主机
架构
1、哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的Redis节点,不存储数据。
2、数据节点:主节点和从节点都是数据节点。
sentinal.conf 相关配置说明
1、port 26379
哨兵sentinel实例运行的端口 默认26379
2、dir /tmp
哨兵sentinel的工作目录
sentinel monitor <master-name> <master ip> <master port> <quorum>
1、哨兵sentinel监控的redis主节点的 ip port
2、 master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
3、 quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
4、sentinel auth-pass <master-name> <password>
当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提
供密码,设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
供密码,设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
5、 sentinel down-after-milliseconds <master-name> <milliseconds>
指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
6、sentinel parallel-syncs <master-name> <numslaves>
这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
7、sentinel failover-timeout <master-name> <milliseconds>
故障转移的超时时间,默认三分钟
核心运作流程
1、服务发现和健康检查流程
1、搭建 Redis 主从集群
2、启动哨兵节点,客户端通过哨兵发现 Redis 实例信息
3、哨兵通过连接 master 节点发现主从集群内所有实例信息
4、哨兵监控 redis 实例健康情况
2、主观下线与客观下线概念
主观下线
假设主服务器宕机,哨兵1监测到了这个结果,并不会马上进行故障切换,仅仅是哨兵1主观认为主机master不可用
客观下线
当后面的哨兵2,3,4...等一定数量 (数量根据配置决定) 的哨兵也监测到了主服务器不可用时,哨兵之间会发起一次投票,选举出一台新的 master,进行故障切换操作,投票结果由一个哨兵发起,切换成功后通过发布订阅模式让各个哨兵把自己监控的redis服务器切换为新选举出的主机,这个过程称为客观下线
主观下线与客观下线区别
客观下线是主节点才有的概念;如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作。
3、故障判定原理分析
1. 每个 Sentinel (哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器, Slave 从服
务器以及其他 Sentinel (哨兵)进程发送一个 PING 命令。
务器以及其他 Sentinel (哨兵)进程发送一个 PING 命令。
2. 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-aftermilliseconds 选项(sentinal.conf 中的配置项)所指定的值, 则这个实例会被 Sentinel (哨兵)进程标记为主观下线( SDOWN )。
3. 如果一个 Master 主服务器被标记为主观下线( SDOWN ),则正在监视这个 Master 主服务器的所有 Sentinel (哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。
4. 当有足够数量的 Sentinel (哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了主观下线状态( SDOWN ), 则 Master 主服务器会被标记为客观下线( ODOWN )。
5. 在一般情况下, 每个 Sentinel (哨兵)进程会以每 10 秒一次的频率向集群中的所有 Master 主服务器、 Slave 从服务器发送 INFO 命令。
6. 当 Master 主服务器被 Sentinel (哨兵)进程标记为客观下线( ODOWN )时, Sentinel (哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
7. 若没有足够数量的 Sentinel (哨兵)进程同意 Master 主服务器下线, Master 主服务器的客观下线状态就会被移除。若 Master 主服务器重新向 Sentinel (哨兵)进程发送 PING 命令返回有效回复, Master 主服务器的主观下线状态就会被移除。
4、故障自动迁移过程
1、它会将失效 Master 的其中一个 Slave 升级为新的 Master , 并让失效 Master 的其他 Slave 改为复制新的 Master
2、当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使用现在的 Master 替换失效Master
3、Master 和 Slave 服务器切换后, Master 的 redis.conf 、 Slave 的 redis.conf 和 sentinel.conf 的配置文件的内容都会发生相应的改变,即 Master 主服务器的 redis.conf 配置文件中会多一行 slaveof 的配置, sentinel.conf 的监控目标会随之调换。
5、定时任务
1、每10秒通过向主从节点发送info命令获取最新的主从结构;
2、每2秒通过发布订阅功能获取其他哨兵节点的信息
3、每秒通过向其他节点发送ping命令进行心跳检测,判断是否下线(monitor),当哨兵连续3次检测都没有数据返回.则表明主节点宕机.
总结
优点
在主从复制的基础上,哨兵引入了主节点的自动故障转移,进一步提高了Redis的高可用性;
缺陷
1、哨兵无法对从节点进行自动故障转移,在读写分离场景下,从节点故障会导致读服务不可用,需要我们对从节点做额外的监控、切换操 作。
2、写操作无法做负载均衡
3、存储能力受到单机限制
Redis Cluster 集群分片存储
简介
redis cluster集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群中所有的主节点参与选举.redis集群中全部的节点都能互相通信.所有的节点都有投票权,Redis cluster集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到 1000节点。redis cluster集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单
单实例 redis 存在的问题
1、存在读写性能瓶颈
2、可能超出单台服务器内存
3、即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储。即每台redis存储不同的内容,
主要功能
1、自动分片
2、故障转移
3、容错扩容机制
架构细节
1、所有的redis主节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
2、客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
3、节点的fail是通过集群中超过半数的节点检测失效时才生效.
4、有一台主节点宕机,选举是由剩余的主节点投票,从宕机的主节点的从节点中选举一台作为新的主机
5、有一台主节点宕机,且无从服务器替换,则整个 redis 集群宕机(redis 的 Hash 槽位无法自动转移)
Hash 一致性算法
使用目的
说明
解决分布式缓存的问题,在分布式环境下,定位数据与其存储到对应服务器的一种规则,避免循环遍历查询每一台服务器
案例分析
假设,我们有一个社交网站,需要使用Redis存储图片资源,存储的格式为键值对,key值为图片名称,value为该图片所在文件服务器的路径,我们需要根据文件名查找该文件所在文件服务器上的路径,数据量大概有2000W左右,按照我们约定的规则进行分库,规则就是随机分配,我们可以部署8台缓存服务器,每台服务器大概含有500W条数据,并且进行主从复制,由于规则是随机的,所有我们的一条数据都有可能存储在任何一组Redis中,例如我们用户查找一张名称为”a.png”的图片,由于规则是随机的,我们不确定具体是在哪一个Redis服务器上的,因此我们需要进行1、2、3、4,4次查询才能够查询到(也就是遍历了所有的Redis服务器),但这样做效率极低,所以可以按照一定规则进行存储,查询时也可以按照规则找到对应存储的节点,不用遍历,这规则就是 Hash 一致性算法
什么是 Hash 一致性
1、一致性Hash算法是对2^32取模,该算法将整个哈希值空间组织成一个虚拟的圆环,空间范围是0-2^32-1,整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1, 0和2^32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为 Hash 环。
2、将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置
3、使用如下算法将数据定位到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一台服务器就是其应该定位到的服务器
容错性和扩展性
容错性
假设 Hash 环上有 A、B、C 三个节点按顺时针分布,如果 B节点挂掉,那么 B节点的数据就会按顺时针方向找到 C 节点,B节点的数据就会转移到 C节点上存储
扩展性
假设 Hash 环上有 A、B、C 三个节点按顺时针分布,假设要新增一个节点 X,该节点的位置位于 B 和 C 之间,那么 B 按顺时针方向到 X 节点之间的数据,这部分原来由 C 节点存储的数据就全部交由 X 节点进行存储
缺陷
数据倾斜问题
如果在分片的集群中,节点太少,并且分布不均,一致性哈希算法就会出现部分节点数据太多,部分节点数据太少。也就是说无法控制节点存储数据的分配。
redis cluster 数据存储
说明
redis 集群(cluster)并没有选用上面一致性哈希,而是采用了哈希槽(slot)的这种概念。主要的原因就是上面所说的,一致性哈希算法对于数据分布、节点位置的控制并不是很友好。
hash 槽
概念
Redis 集群中内置了 2^14 (16384) 个哈希槽
hash 槽必须分满,连续分布且不能断,一旦断掉则整个集群挂掉
一个槽里可以存放多个键值对
算法
1、crc16(key) 算出 hash值
2、将上一步的得到的 hash 值对 16384 取模,得到一个 0-16384 范围的值
3、第二步得到的值就是 slot 槽位下标,根据下标顺时针找到的第一个点就是对应的存储节点
容错性和扩展性
表象与一致性哈希一样,都是对受影响的数据进行转移。而哈希槽本质上是对槽位的转移,把故障节点负责的槽位转移到其他正常的节点上。扩展节点也是一样,把其他节点上的槽位转移到新的节点上。
注意:对于槽位的转移和分派,redis 集群是不会自动进行的,而是需要人工配置的。所以 redis 集群的高可用是依赖于节点的主从复制与主从间的自动故障转移。
数据存储原理
Redis 集群中内置了 16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,一个槽可以放入多个键值对,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
redis 集群容错
节点失效判断
1、集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.
2、 挂掉主节点的从节点自动升级为主节点,由redis集群操作
集群失效判断
1、如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
2、如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。 (投票无效)
说明:Redis集群中当某个主节点宕机后,由其他主节点进行推选其从节点作为新的主节点.如果当前宕掉的主节点没有从节点时,从其他主节点中借用多余的从节点(其他主节点如果只有 1 个从节点则无法借用).
Redis 分布式锁
锁的场景
单应用中使用锁(在同一个 JVM 中,单进程多线程)
通过 synchronized、ReentrantLock 来同步访问共享资源
分布式应用中使用锁:(不同 JVM 中,多进程多线程)
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
分布式锁的实现方式
1、基于数据库的乐观锁实现分布式锁
2、基于 zookeeper 临时节点的分布式锁
3、基于 Redis 的分布式锁 set
分布式锁需要考虑的问题
1、互斥
在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,
2、防止死锁
死锁原因
在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。
解决思路
设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。
3、性能
对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。所以在锁的设计时,需要考虑两点
1、锁的颗粒度要尽量小
2、锁的范围尽量要小
4、重入
分布式锁理论演变推进
1、 synchronized、ReentrantLock 本地锁只能锁住当前进程,在分布式情况下无法锁住所有进程,因为每个进程都持有独占本地锁
2、分布式锁可以利用中间件实现,锁住所有进程,同一时间只有一个进程能获取到锁,其余进程循环尝试获取
3、原理:利用redis的 setNX 设置一个key,此方法表示如果 key 存在,则设置失败,值生成一个唯一的值,防止锁过期自动删除,别的进程进来抢占锁后,该进程再对锁进行释放,此时释放的是别的进程的锁,并设置过期时间,触发锁过期自动释放锁,通过返回的布尔值表示枷锁成功或失败
4、加锁
1、加锁需要设置锁的过期时间,防止业务执行期间宕机导致死锁
2、抢占锁和设置锁的过期时间必须是原子操作,防止抢占到锁后还未设置过期时间就宕机,造成死锁
5、解锁
1、在加锁的时候,需要给锁配上一个当前进程的唯一标识(UUID)作为值,在解锁的时候获取一下锁的值即设置的 UUID,比对相同再解锁,避免因为锁过期,其他进程抢占到锁后,当前进程再解锁,此时解的是其他进程的锁
2、获取锁的唯一标识和解锁必须是原子操作,防止解到其他进程的锁,利用 lua 脚本
6、总结
1、加锁和设置过期时间必须是原子操作
2、解锁和获取锁的唯一标识必须是原子操作
Redisson 框架
概述
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
原理分析
过程
1、线程去获取锁,获取成功,则根据 hash 算法选择一个节点,然后执行 lua 脚本加锁,将数据存入 redis
2、线程获取锁,获取失败,则一直通过循环尝试获取锁,直到获取成功后执行第一个步骤
3、如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。
watch dog 看门狗机制
当持有锁的线程业务还没有执行完,锁的持有时间就到了,该线程还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间。他每隔 10 秒会查看,如果还持有锁,就延长生存时间,直到该线程执行完业务后解锁
常见缓存问题
缓存穿透
概述
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如
DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大
的压力。
也就是说,对不存在的key进行高并发访问,导致数据库压力瞬间增大,这就叫做【缓存穿透】。
DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大
的压力。
也就是说,对不存在的key进行高并发访问,导致数据库压力瞬间增大,这就叫做【缓存穿透】。
解决方案
1、缓存查询结果为空,也对该 key 进行缓存,过期时间可以设置短一些,或者该 key 对应的数据 insert 后清理缓存
2、提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。
缓存雪崩
概述
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如
DB)带来很大压力。
DB)带来很大压力。
解决方案
1、 key的失效期分散开,可以给一个固定值基础上加一个随机值,保证不同的 key 设置不同的有效期,避免大量的 key 集体失效
2、双缓存。设置两个缓存,例如缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。
缓存击穿
概述
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
1、利用分布式锁,持有锁的线程才能访问 DB
缓存数据库一致性
数据读取过程
客户端查询数据,先查 redis ,如果命中缓存直接返回,如果没有命中,则查询数据库,把查询到的结果缓存进 redis,再返回数据
概述
当更新了数据库后,如何保证 redis 中的缓存数据同步更新
解决方案
双写模式
1、先更新缓存,再更新数据库(不能用)
案例分析
如果更新缓存成功,更新数据库失败,
结果
缓存脏数据
2、先更新数据库,再更新缓存(不建议)
案例分析
1. 线程A更新了数据库
2. 线程B更新了数据库
3. 线程B更新了缓存
4. 线程A更新了缓存
结果
线程 B 对缓存的更新丢失了
失效模式
3、先删除缓存,再更新数据库
这种策略避免了策略 2 中缓存更新地市的情况,但在高并发下依然存在不一致的情况
案例分析
1、线程 A 首先删除了缓存,还未更新数据库
2、线程 B 做读操作,由于线程 A 已经删除了缓存导致缓存未能命中,所以线程 B 读取数据库,这时候读到的是旧值,然后线程 B 将读到的旧值写入 redis 缓存
3、线程 A 更新数据库
结果
数据库中的值与 redis 缓存的值不一致
解决方案
延时双删:线程 A 在完成数据库更新后,稍作延迟再删一次缓存(这里的延迟时间一定要大于一次业务的读操作时间)
4、先更新数据库,再删除缓存
这种策略在高并发下同样会有不一致现象
案例分析
1、线程 A 在查询数据,正准备将读取的数据写入缓存
2、线程 B 更新了数据库,然后执行了删除缓存操作
3、线程 A 把之前读取的旧值写入缓存
结果
数据库中的值与 redis 缓存的值不一致
解决方案
延时双删:线程 B 在删除了缓存后,稍作延时再删一次
Springboot 整合 Redis
redisTemplate
设置 redis 连接池
redisTemplate.setConnectionFactory(redisConnectionFactory);
ps
redisConnectionFactory 作为方法参数传入,使用 lettuceConnectionFactory
设置key 序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
ps
默认情况 redis 的 key 值使用 jdk 序列化
设置 value 序列化方式
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
日期时间序列化问题
在 jdk1.8 之后,推荐使用 LocalDateTime
示例
redis 序列化器
0 条评论
下一页