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名为 balance1000的键值

  • 开启两个客户端连接窗口.

  • 在两个客户端都执行 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()开启事务后, 调用multidecr(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.confredis6381.conf


再使用vi编辑命令修改其中的文件名和端口号即可;

开启这三个redis服务

麻了,之前1月初搭配过这个案例,其中的配置文件还没有改…
重新改了一遍,改成了端口为6319,6320和6321;

使用命令 redis-cli -p 端口号 依次开启三个redis;

  • 输入命令info replication 查看信息时,发现这三个都是主机身份;

注意:想要达成主从关系; 配置从机即可;
那么,这里我就让 63206321作为从机;
使用命令 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学习笔记 [事务和锁机制持久化机制模拟主从复制集群搭建]的主要内容,如果未能解决你的问题,请参考以下文章

redis事务和锁机制

redis6事务和锁机制

redis6事务和锁机制

Redis学习笔记——异步机制:如何避免单线程模型的阻塞?

Redis学习笔记——异步机制:如何避免单线程模型的阻塞?

事务和锁机制是什么关系? 开启事务就自动加锁了吗?