Redis学习笔记 [事务和锁机制持久化机制模拟主从复制集群搭建]
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis学习笔记 [事务和锁机制持久化机制模拟主从复制集群搭建]相关的知识,希望对你有一定的参考价值。
近期计划对redis再进行一段详细的学习,
在B站找到了尚硅谷的redis教学视频->【尚硅谷】Redis 6 入门到精通 超详细 教程,进行学习记录
文章目录
🛴1.事务和锁机制
🌈1.1基本操作
Redis的事务 是单独的隔离操作,事务中的所有命令都会进行序列化
,按照顺序
地执行下去,事务在执行的过程中,不会被其他的客户端命令请求中断
; 特点是 串联着多个命令来防止其他命令插队.
- 输入命令 Multi 后,后面输入的命令就会依次
进入到命令队列
中,但是不执行
; - 直到输入命令Exec之后,
redis
会把命令队列中的命令依次执行过去; - 在组成命令队列的过程中,可以使用
discard
放弃组队.
基本案例模拟
基本案例模拟搭建
- 使用命令
multi
开启事务,然后执行几个存值操作, 再使用exec
命令,依次执行队列中的命令.
- 先用
flushdb
命令清空数据库; 使用multi
开启事务, 写几个操作命令后; 使用discard
命令后,即可让队列中的命令放弃组队.
特殊案例模拟
需要注意的特殊案例
- 案例(1)在开启了
multi
命令之后,后面输入的命令一旦有错误报告,那么后面即使用了exec
命令执行队列命令,所有的队列命令执行都会被取消!!! - 案例(2)如果说使用了
multi
命令后,输入的命令没有错误报告,即组队成功了. 但是后面使用exec
命令在依次执行着队列中的命令时,如果某个命令出现错误,那么只有这个错误命令不会被执行,其他的命令都可以成功执行.
可以模拟搭建一下这两个案例
(1)首先试试第一种案例;在开启事务之后,写了错误的命令;
先用
flushdb
命令清空数据库;
(2)试试第二种案例,开启事务后,并没有错误的命令,但是在用exec命令执行时,某个命令出现的错误,;
同样,先用
flushdb
命令清理数据库
🌈1.2事务冲突 — 乐观锁与悲观锁
假设有这样一个场景;你的卡余额10000元,
现在有三个人同时登录你的账户,预备进行消费;
第一个人预备消费8000元;
第二个人预备消费5000元;
第三个人预备消费1000元;
那么若是在同一时间触发时,就会产生多线程对共享资源的并发问题;
解决这个问题;可采用悲观锁/乐观锁;
悲观锁
- 即就是每次操作数据时,都认为这个数据不安全,会被别的操作者修改,所以在每次取数据时都会上锁,
- 这样的话,别的操作者若是需要操作此数据时,会被
阻塞
—>得到锁为止; - 在传统的关系型数据库中就用到了这样的锁机制:表锁,行锁,读锁,写锁.都是在操作之前先加锁保证安全.
在这个消耗金钱的场景中,若是每次操作之前,都对这张卡加锁,那么当第二个操作者过来时,就会被阻塞.
乐观锁
- 每次操作数据时,乐观地认为别的操作者不会修改数据,那就不用上锁;
- 可以加入版本号,来区分不同的操作,判断该数据是否被别人修改;
- 适用于读操作多一点的场景,可提升吞吐量.
可为每次操作都加一个版本号,在数据操作时,比较版本号即可.
案例模拟
在redis中,执行
multi
命令之前,可使用watch key1 key2...
命令,监视一个/多个key
,
若在事务执行之前,这些key被其他的命令修改了,那么事务就会被打断.
- 命令
unwatch key1 key2...
,可以取消对设定的一些key的监视功能;- 注意在执行了
watch
监视命令之后, 紧接着 执行了exec
或者discard
命令 后,实际上监视已经失效;也就不用执行unwatch
命令.
- 先在数据库存入key名为
balance
为1000
的键值
- 开启两个客户端连接窗口.
- 在两个客户端都执行
watch balance
命令,监视这个名为balance
的键
- 在另个客户端都用
multi
命令开启事务;
- 在第一个客户端中, 对balance进行值加10的操作.
- 在客户端2中,对balance 进行值加30的操作 ;
注意此时就产生了同一个资源的事务冲突问题.
- 在第一个客户端中使用
exec
命令执行提交事务,那么balance
此时的值就是1010
;
- 在第二个客户端中,同样使用了
exec
命令执行,但是发现并没有成功执行;- 实际上在redis中这就是采用了一种
乐观锁机制
;
redis的事务特征,模拟案例
redis事务的特性
单独的隔离操作
;
- 在当前事务中所有的命令都会序列化,按顺序执行下去;
- 在事务执行时,不会被其他客户端的请求打断;
- 没有隔离级别;
- 不会保证原子性; 即使有一个命令在执行时报错,并不会影响其他的命令正常执行;
模拟秒杀案例的场景
可以将商品按照 key-val(String类型) 存入; 将抢到商品的用户存入到 key-set集合中;
- 判断商品的库存是否达到0,则停止抢购;
- 对于已经抢购成功的用户,会被存入到set集合中;可使用
sismeber
命令查看该用户是否在set集合中已存在;(若已抢购成功,直接阻止即可). - 在商品每次被抢到时,商品中的库存值 使用
decr
命令递减;
在CentOS7中;可使用命令yum install httpd-tools
安装ab测试工具.
…麻了,我这里一直安装不上.
使用ab测试工具可以模拟出并发的请求操作, 即可模拟多个请求,指定数量的并发请求;
在模拟中,会出现超买
/ 请求阻塞失败
的问题;
那么具体的解决方式;
这个超买的问题是因为前面的用户可能正在操作,但还没抢到时,后面得用户秒杀得到了商品,但是前面的用户可能又执行成功了;也就导致出现库存为负数的问题.
- 可以定义一个jedis连接池,将之前的单独创建jedis连接改为使用连接池,将
连接复用
,防止连接超时问题出现.- 对商品库存key使用
watch()
方法进行监视,保证乐观锁.- 然后将秒杀过程交给事务管理;使用
multi()
开启事务后, 调用multi
的decr(key)
方法代替直接使用jedis无事务的递减操作,使用nulti的sadd(key,val)
方法代理直接使用jedis无事务的添加成功用户操作. 保证采用事务后,达到隔离效果.
- 还有可能出现的问题,秒杀活动结束,但是商品库存还有剩余;此时可以采用LUA脚本语言来进行解决,注意只有在redis2.6版本之后才可生效,且LUA语言中的
number
会被转换为redis中的Integer
类型,若需要操作小数时,可采用字符串类型处理.
🛴2.持久化RDB
RDB实际就是在指定的时间间隔内,将内存中的数据库快照snapshot文件写入
硬盘;
在恢复数据时,会直接将快照数据读入内存.
可以这样理解RDB的持久化机制,采用了临时空间,先将数据存入到临时空间,然后再存入到rdb文件,
为什么不直接将数据存入rdb文件呢? —> 目的是防止服务出现问题时,数据直接存入rdb文件,但并未及时存入内存,出现数据不一致问题.
但是这样的机制可能导致最后一个数据无法存入.
- Redis会单独创建[
FORK
] 一个子进程来进行持久化,会将数据先写入到临时文件,等待持久化过程结束时,再用当前的临时文件替换
之前持久化的文件. - 整个执行过程中:主进程不进行任何的IO操作,保证了高性能;
- 若需要大规模的数据恢复,那么对于数据恢复的完整性要求不高时,采用RDB持久化比AOF更加高效
- 但是RDB的缺点:---->最后一次持久化的数据可能丢失.
Fork操作
fork
可以复制一个与当前的进程一样的进程. 复制的进程中所有数据数值都和原进程一致
(变量,环境变量,程序计数器…);作为原进程的子进程
;- 在linux程序中,
fork()
产生一个与父进程相同的子进程,但是子进程之后会被exec系统
调用;考虑到效率问题,linux引入写时复制
; - 在之前的父进程与子进程会共同使用一段物理内存,只有进程空间的各段内容需要发生变化时,会将父进程复制到子进程中.
rdb文件的备份过程:----------------------->
(1)先用config get dir
查询到rdb文件
的目录;
(2)然后将*.rdb
的文件拷贝到别的位置;
(3)rdb的恢复–>
- 先关闭redis;
- 将备份的文件拷贝到工作目录下 cpdump2.rdb dump.rdb
RDB持久化的优缺点
优点:
- 适合大规模的数据恢复;
- 适用于对数据完整性和一致性要求不高的;
- 恢复速度快,且节约磁盘空间.
缺点:
- 在fork复制子进程时,内存中的数据也被克隆了,需要考虑膨胀性;
- redis虽然在
frok过程
中采用写时复制技术
;但是数据过大时也要考虑性能问题; - 备份是根据设定的周期范围,但是若在该范围内服务出现停机,数据就会丢失;
- 比如说设置了20秒进行一次定时的存储,但是在这20秒内服务挂掉了,那么这部分数据就可能会丢失.
配置文件查看
- 首先vi命令编辑redis.conf的配置文件;
采用/SNAP
搜索;
具体的配置;
dbfilename
可配置生成rdb文件的名称
-
默认的文件生成路径
-
在ftp工具中,可清楚地看到rdb文件的位置.
-
配置参数
save
;
注意
save
命令 和bgsave
的不同之处;
save
:save 尽管保存,其他不管,全部阻塞,需要手动保存,不建议;bgsave
:redis将会在后台异步执行快照操作,此快照会影响到客户端的请求;
可使用lastsave
命令 获取到最后一次成功执行快照的时间.
-
配置参数
stop-writes-on bgsave-error
,推荐使用yes
; 当redis无法写入磁盘时,可以关闭redis的写操作.
-
参数
rdbcompression
; 可配置对于即将存入硬盘的快照数据,是否执行压缩存储;
-
参数
rdbchecksum
检验配置, 保证了再存储快照数据后,redis进行数据校验.
模拟案例(1) :基本查看rdf存储文件的变化.
模拟使用
- 先杀掉进程,再重启服务.
在连接B窗口可看到此时dump.rdb文件大小为92.
在连接A中可看设置几个键值对.
再去连接B中查看rdb文件的大小,已存储了数据.
模拟案例(2) 备份过程
- 先清除数据库的内容;
- 此时rdb文件占用空间已恢复到92大小;
- 存入几个数据;
那么现在的rdb文件中就有这个数据了;
如何演示这个数据的备份功能呢,可以将这个rdb文件复制出来一份进行操作;
- 使用
cp命令
复制文件
- 直接杀掉进程,停止redis服务;
- 删除之前的rdb文件
- 再用
mv
命令将复制出来的myde.rdb
文件变为dump.rdb
文件
- 重新连上redis服务后,注意到k4并没有被存入到数据库中;
- 由于之前的配置文件中设定了20秒内仅对3个key进行备份;数据k4就被丢失了.
🛴3.持久化AOF
基本概念
AOF机制: 以日志的形式来记录每个写操作;[增量保存]; 将redis的执行过的所有写指令记录下来(不会记录读操作
);
只允许追加文件,但是不能改写文件; 在redis启动时就会读取aof文件;根据日志内容将写指令执行一遍完成数据恢复.
配置文件
由于redis默认采用了RDB,那么就需要手动去配置开启
AOF持久化
.
找到redis的配置文件进行修改;
- 参数
appendonly
可设置是否追加,即开启aof持久化
.
- 然后在杀进程停掉服务,再进行重启,保证配置生效.
可看到已生成了aof文件
查看数据库的数据时,发现居然提示没有数据;但是刚才学习RDB持久化时,确实已经存储数据了.
这里提示无数据的原因是因为 同时开启了RDB和AOF的持久化,redis是默认读取aof的文件
;但是之前的数据是存在的,并不是丢失了.
模拟案例-aof的备份恢复
- 现在存入5个数据
- 可查看到aof中已存入数据了;
- 用
cp
命令将aof的文件复制一份;
- 然后在redis服务中使用
shutdown
命令停止服务;
- 使用
rm -f
命令删除之前的原始appendonly.aof
文件删除;
- 将复制出来的back文件,使用
mv
命令变为appendonly.aof
文件.
- 再重启redis服务;
- 使用
keys *
命令查看数据库中键;
模拟案例—aof文件的异常损坏恢复
若aof文件损坏,可通过
/usr/local/bin/redis-check-aof--fix appendonly.aof
恢复
用vim编辑器的 vi命令可看到 appendonly.aof
的文件构成;
实际上是一目了然的,键值对的关系非常清晰
-
故意写个自定义的内容,损坏原有的aof文件;
-
在其中一个连接中,关闭redis服务;
-
再进行重启redis服务时,出现错误提示;(由于它在启动服务时,会去读取
appendonly.aof
文件恢复数据,但是该文件已经被损坏了);
在文件目录下,可看到这样一个文件
redis-check-aof
,该文件就可以用作修复aof文件
;
-
输入命令
redis-cheak-aof --fix appendonly.aof
,修复aof文件;
-
开启redis服务;可查到数据;
AOF的同步频率设置
appendfsync always
始终同步,每次redis的写入都会立即存入日志;性能虽差但是数据完整性好;appendfsync everysec
每秒同步计入一次,若宕机的话,当前秒数的数据可能丢失.appendfsync no
redis将同步的计划交给操作系统,自己不主动备份.
AOF的数据压缩重写机制
数据压缩:可以这么理解,将多个操作命令语句压缩到一句命令操作.
由于AOF
采用了文件追加的方式进行持久化,当数据量很大时,文件就会越来越大;
为避免这样的情况出现,redis新增了重写机制,
当AOF文件的大小超过了所设定的阈值时,使用命令bgrewriteaof
;redis就会启动aof文件的内容进行压缩,仅保留下可以恢复数据的最小指令集.
数据文件重写的原理:
当AOF文件持续增长而越来越大时,就会fork出一个新的进程来将文件进行重写(其实就是先写入临时文件,然后再进行重命名处理);
注意
!: redis4.0版本之后的重写,实际上就是将rdb文件的快照,用二进制的形式附加在aof的头部,作为已有的历史数据,替换原来的操作
关于 no-appendfsync-on-rewrite
参数的设置
(1)设置为yes时,数据只是存入缓存,不会存入到aof文件中,也不会出现用户请求阻塞的问题,[假如出现宕机时,数据可能会丢失].性能会提升,但是数据安全性能降低.
(2)设置为no时,数据仍然是向磁盘中刷入,一旦遇到重写操作,可能会阻塞
. [性能有所下降,但是数据安全性能高].
那么这个重写的触发时机是什么?
redis会记录上一次的aof文件大小; 默认的配置是当前的aof文件大于64M
且为上次的aof文件大小的一倍
时,触发重写机制.
🛴4.主从机制
使用主从机制后, 可以做到读写分离,主从同步.
流程:
(1)当一个从机上线之后;匹配到主机; 从机可以向主机发出同步数据消息请求;
(2)主机收到同步消息后,将自己的数据文件持久化为rdb文件;发给从机.
(3)主机收到rdb文件后,进行数据读取使用.
(4)主机每次进行写操作之后,就会和从机进行数据同步.
配置一主机两从机案例
首先使用mkdir
命令在主目录下创建一个文件夹
从之前的etc目录下复制 redis的配置文件 到 redis-master-salve
文件夹下.
- 在配置文件中关闭
aof
持久化.
用vi redis6379.conf
创建一个文件;进行编辑处理.
include /redis-master-salve/redis79.conf
include /redis-master-salve/redis79.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
同样,复制两份文件,命名为redis6380.conf
与redis6381.conf
再使用vi编辑命令修改其中的文件名和端口号即可;
开启这三个redis服务
麻了,之前1月初搭配过这个案例,其中的配置文件还没有改…
重新改了一遍,改成了端口为6319,6320和6321;
使用命令 redis-cli -p 端口号
依次开启三个redis;
- 输入命令
info replication
查看信息时,发现这三个都是主机身份;
注意:想要达成主从关系; 配置从机即可;
那么,这里我就让 6320
和6321
作为从机;
使用命令 slaveof ip 端口
即可将身份转为从机.
几个特别的案例
案例(1) 在一主两从的 模拟主从关系中; 首先 主机A 写了一个数据之后,从机B和从机C都可以读取到消息;
- 此时让从机C关闭挂掉,
- 主机A在写几个数据; 主机B可以正常读取到主机A的数据;
- 此时重新启动从机C时, 需要注意的是,从机C已经脱离了原来的主从模式结构;自己变成一个独立的主机.
- 此时考虑将从机C恢复身份, 这时使用
keys *
命令可看到它已经将主机A中所有的数据都给读取过来了.
案例(2)
- 当主机挂掉之后,从机不会改变身份;只是在用
info replication
查看信息时显示主机的状态为down
;- 挂掉的主机重新启动后,恢复主机的身份.
- 当然,也可以在从机中使用命令
slaveof no one
,将从机升级为主机.
案例(3)
哨兵模式处理; 自动地监视主机的状态,当主机挂掉之后,可以在从机中进行选举自动产生新的主机;
当原来的主机恢复之后,变为从机身份;
这个从机的选举策略次序:
(1)可以从从机的优先级replica-priority
中选择[默认为100],当数字越小的优先级越高;
(2)根据从机中偏移量最大的从机; 偏移量是指获取主机数据的多少;
(3)根据从机的runid选择最小的; 在redis启动服务时,会生成一个40位的runid;
比如写个配置文件,
sentinel monitormymaster 127.0.0.1 6319 1
这里ip 端口就是监视的主机; 这个 1代表的是主机挂掉后,需要选举表决的从机数量.
🛴5.集群
概念
首先引入问题:当redis的容量不足时,如何进行扩容; 当出现大量的并发写操作时,如何将这些请求合理处理;
redis集群就是一种对redis的水平扩容,启动多个redis节点,将数据库的所有信息数据分布化地存到这些节点上;平均处理.
它采用了分区机制,即使其中一部分节点出现问题,也不会影响集群的使用.
优点:实现了扩容,无中心配置,比较简单;
缺点:对于多键操作不支持
若对于传统的一个服务集群搭建时,至少需要使用8个服务器;
那么采用了无中心化的集群搭建方式,最少使用6个服务器即可;
集群模拟搭建
例如redis6379的配置
include /redis-master-salve/redis.conf
pidfile "/var/run/redis_6379.pid"
port 6379
dbfilename "dump6379.rdb"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
复制5个文件出来,将其中的参数都改一改
在更改编辑文件时,可使用命令:%s/6379/端口号
;
启动所有的服务;
已经生成了nodes文件
找到之前解压后的redis安装文件;找到可看到其中包含这个src目录.
cd
命令进入其中; 可以用到其中的redis-cli…
在src目录下使用命令;注意ip配置自己的
./redis-cli --cluster create --cluster-replicas 1 192.168.59.128:6379 192.168.59.128:6380 192.168.59.128:6381 192.168.59.128:6389 192.168.59.128:6390 192.168.59.128:6391
执行后,6379,6380,6381
自动变为主机;
连接一个服务;
使用cluster nodes
命令从节点关系看集群的关系分配数据;
注意一个集群至少需要三个主节点;
之前使用的命令中--cluster-replicas 1
保证了集群中为每个主机分配一个从机;保证到每个主数据库运行在不同的ip地址,每个从机与主机不在一个ip地址.
SLOTS
刚才在配置集群时,最后有输出命令All 16384 slots covered.
slots
就是插槽,在一个redi集群有16384个插槽,数据库中的每个键都属于这些插槽中的一个.
在集群内部,会使用公式
CRC16(key键)%16384
,计算具体的位置.
- 注意在
redis-cli
每次录入值/查询键值 时,redis都会计算这个key的准确插槽, - 若不是当前客户端对应服务器的插槽;redis就会报错,且提示正确的redis实例地址和端口;
- redis-cli客户端提供
-c
参数:实现自动的重定向;
比如说这里存入一个k1,集群算出插槽位置在12706;就会自动切换到6381
的端口上
每次操作时会计算插槽的位置
尝试使用mset
存入多键值对,出现错误,无法计算插槽;
那么对于多键值对的操作,应该这样操作,使用将不同的键值对包含起来即可成功存入;
使用命令 cluster keyslot 键
可获取指定key的插槽值;
注意,如果使用查看指定的插槽值对应键的值;那么就需要切换到指定范围的端口;
比如说这里我在6380端口查询之前的k1插槽12706
的值;无法查到;
切换回6381端口即可获取
故障恢复案例
比如说这里我将主机6381
停掉了;
再次登上去集群;看看状态;显示原先的6381主机挂掉,从机6389
上位升级为主机.
🛴6.应用方面
缓存穿透问题
用户端发来请求,一致查询redis缓存中不存在的数据,该数据在数据库也不存在;
服务器的压力变大,redis命中率降低,大量请求打到数据库上查询.
引起缓存穿透可以看做这两种原因:
(1)redis中查询不到数据;
(2)用户发来大量的非法请求.
解决方案;
(1)可以将空值进行缓存;
(2)用bitmaps
类型设置一张用户请求的名单,对于不正常的访问直接拦截;
(3)使用布隆过滤器处理.
在B站找到一段关于布隆过滤器讲解的视频
缓存击穿
缓存击穿是指:在redis中的某个热点数据key突然失效,导致大量的请求打到了数据库上,造成击穿压力;
解决方法:
(1)将热点数据的过期时间延长;
(2)实时监控数据的过期时间;
(3)使用锁机制
- 对于失效的数据,首先判断数据是否为空,不要直接删除;
缓存雪崩
缓存雪崩是指:在redis中,大面积的key突然失效,导致大量的请求打在数据库上.
解决的方案:
(1)可以使用队列加锁的操作,限制用户的大量请求;
(2)可使用多级缓存处理:nginx缓存+redis缓存;
(3)保证key的失效时间不同.
以上是关于Redis学习笔记 [事务和锁机制持久化机制模拟主从复制集群搭建]的主要内容,如果未能解决你的问题,请参考以下文章