初探redis:redis持久化之RDB和AOF,redis的事务和分布式锁

redis的持久化

持久化就是将数据存储在永久性存储介质(硬盘)中,当需要时再恢复保存的数据。持久化机制保证了数据的可用性。

redis的数据是存储在内存中,而内存中的数据会在关机后消失,而我们平时操作redis时发现,之前存储的数据仍然存在啊?这就是因为redis的持久化功能,redis提供了两种持久化方式:RDB和AOF

RDB

RDB是以快照形式对数据进行持久化,将数据作为二进制的RDB文件存储在硬盘上

这种形式就是将当前的全部数据文件保存下来,如果已经存在老文件,会被新文件覆盖。在每次redis启动时都会载入数据

RDB有这三种触发机制:

  • save(同步)
  • bgsave(异步)
  • 自动存储

save命令是同步IO存储,当redis执行save命令时,后面的命令会进入阻塞等待,直到save命令完成

在这里插入图片描述


bgsave(background save)则是异步IO存储,当redis执行bgsave时,并不会阻塞客户端命令,因为他会fork一个子线程,让子线程去存储RDB文件,客户端线程仍然能够响应其他命令。

在这里插入图片描述

save和bgsave的比较

命令savebgsave
IO类型同步异步
阻塞客户端命令
启动新线程
额外内存消耗
复杂度O(n)O(n)

redis有自动保存的机制,实际使用的还是bgsave,在conf文件中设又默认的配置

配置secondschanges
save9001
save30010
save6010000

表示在时间范围内,有多少次变化发生后,达到其中一个要求就会进行自动保存

比如在60秒内,超过10000次更新操作,那么就会自动保存

RDB的自动保存是要谨慎使用的,否则可能对性能有较大影响,需要根据业务配置参数,也可以选择关闭自动保存,采用手动命令保存的方式。


在一些特殊机制下也会进行RDB持久化,比如以下情况:

  • 全量复制
  • debug reload 服务器重启
  • shutdown save 关闭服务器时

RDB的优点

  • 优点:RDB文件可以压缩存储,快照模式适合数据备份,RDB的恢复速度高于AOF
  • 缺点:无法实时持久化,可能丢失数据。bgsave需要进行fork操作,会降低性能,不同版本的RDB文件可能不兼容

AOF

AOF和RDB的快照备份不同,采取的是日志文件形式的备份,即不存储数据实体,而是将所有的命令记录下来,恢复数据时,再把命令执行一次,起到备份的效果,AOF解决了数据持久化的实时性

RDB的存储方式虽然简单易用,但是同样存在一些缺点。当数据量大的时候,IO性能低,存储效率低下,耗时过长。且fork()创建子进程存储,需要消耗额外的内存。数据也不是实时存储的,当出现宕机时,可能会导致大量的数据丢失。

AOF会将写命令放在缓冲区,当满足情况时,会把缓冲区的命令刷新到硬盘上的AOF文件中

在这里插入图片描述


AOF刷盘的三种策略

  • always:每条命令都刷新
  • everysec:每秒都刷新
  • no:操作系统来决定
命令优点缺点
always不丢失数据IO开销大
everysec每秒记录一次,硬盘压力小可能丢失1秒数据
no不用管不可控

AOF重写

AOF一直存储的话,太多的命令可能会使得AOF过于臃肿,太多的命令也会导致数据恢复的速率过慢,所以redis引入AOF重写功能来压缩命令条数,借此可以减少不必要的操作命令。其作用就是将数据转换为写命令同步到新的AOF文件中。

通过AOF重写,减少了AOF文件的大小,同时提升了持久化的效率,数据恢复时也节省了时间。

AOF重写会化简多条命令,并忽略过期的、没用的数据。

原生AOF重写AOF
set name aaset name cc
set name bbset num 9
set name cclpush list a b c
set num 7
incr num
incr num
lpush list a
lpush list b
lpush list c
无效数据…

AOF重写机制分为:主动重写和自动重写,但是主要还是执行bgrewriteaof命令

在这里插入图片描述


AOF自动重写的配置

状态含义
aof_current_size当前AOF文件尺寸
aof_base_sizeAOF文件基础尺寸
配置含义
auto-aof-rewrite-min-sizeAOF自动重写的最小尺寸
auto-aof-rewrite-precentageAOF自动重写的增长百分比
  • 当aof_current_size > auto-aof-rewrite-min-size时会自动重写

  • 或者(aof_current_size - aof_base_size)/aof_base_size > auto-aof-rewrite-precentage时也会自动重写

RDB和AOF的抉择

持久化方式RDBAOF
启动优先级
占用存储空间
存储速度
恢复速度
数据安全性会丢失数据根据策略决定
资源消耗

RDB和AOF需要根据业务情况选择相应的策略来抉择。如果对于数据比较敏感,不允许大量的数据丢失,可以开启AOF,关闭RDB,并且最好选择everysec策略。

如果仅仅将redis作为一个缓存来使用,数据都来自数据源,那么AOF也可以关闭掉。

redis的事务

虽然redis的每条指令是原子性的,但是多条指令就不是一个原子操作了。如果有多个客户端在同一个redis服务器上并发执行,就有可能导致并发安全的问题,所以redis也提供了事务来满足隔离性。

redis的事务是通过队列来实现的,开启事务后,会生成一个命令队列,所有将要执行的命令都会存储在队列中,而不是立刻执行。

当事务执行时,其他客户端的指令不能插入事务的命令序列中。但即使事务中的某条指令执行失败,也不会阻塞其他命令,也就是说redis的事务并不是原子性的,没有回滚功能。

但是如果是语法错误,比如输入的是一个不存在的命令,那么会导致该事务就会被取消

同时也可以手动取消该事务,对应的队列会被销毁,所有队列中的指令都会被清空。

  • multi 开启事务
  • 命令入队
  • exec 执行事务 / discard 取消事务

同时redis提供了监视功能,可以用来保证事务操作的正确性。

在事务开启之前,监视一个或多个key的值,如果事务执行时,这些key只要发生过变化,那么事务就会被打断。

watch key1 key2... 对key进行监视

unwatch 取消对所有key的监视

watch可以类比于乐观锁,进行事务操作时,先监视要操作的数据,如果在事务执行时这些数据已经被改动过了,那么就取消事务。

redis的分布式锁

虽然通过watch能实现类锁的功能,但是watch只能监视这个数据是否发生了变化,但是不能监控具体的数据。比如图书借阅功能,书籍库存>0才能被借阅,但是如果使用watch,有一个人借了,发生了数据变化,其他事务就借不了,这显然不满足要求。

所以又有了锁的用武之地了。

对于多线程的情况,Java就提供了各种锁来解决,synchronized、lock都可以解决并发问题。但是,有一个问题是,这些锁都是单机锁!如果只有一个服务器,所有请求都走这个服务器,那么单机锁就满足需求了。

但如果有服务采取集群分布时就不对劲了,单机锁不能在多个服务器之间生效,比如现在有两台服务器,只剩最后下一本书可以被借阅,两台服务器都使用的单机锁,对于服务器A来说只能有一个线程能尝试获取共享资源,但是对于服务器B来说,也有一个线程能去尝试获取共享资源。这就出现了线程并发问题了。所以对于分布式服务,还需要分布式锁来解决。

redis就是实现分布式锁的方式之一。https://redis.io/topics/distlock,redis官方有更详细的说明。这里简单地介绍一下分布式锁的实现,


在string类型中,有一个指令:setnx key value。作用是:只有当key不存在时,才设置key。

通过这条指令就能在redis上进行加锁,比如setnx lock 1,如果返回1表示锁设置成功,返回0表示锁存在。

所以就有如下操作:

  • setnx key value 加锁
  • del key 解锁

当两个线程去访问redis时,想要进行操作,必须先去获取锁。

但是有个问题,如果持锁线程突然死机了,锁又没有释放,其他线程就会一直获取不到锁,导致线程饿死。

为了防止这种情况,需要对锁添加有效时间,超时就强制释放锁。

  • expire key timeout 设置锁超时时间
  • setex key seconds value 将值 value 关联到 key ,并将 key 的过期时间
if (setnx(lock, 1) == 1){
    expire(key, 30)
    try {
        //TODO 业务逻辑
    } finally {
        del(key)
    }
}

但是仍然有许多问题:

  1. setnx 和 expire 并不是原子操作。

加入刚获取到锁,还没添加时效性,就死机了,还是会有死锁的产生。

这个问题也有很多方式来解决,比如LUA脚本,jedis3.0之后提供了SetParams对象


  1. 超时解锁

比如说A拿到锁,设置30秒,但是A的业务执行了50秒,在30秒之后B拿到了锁。也就是说30秒之后A和B是在并发执行。

同时还有个问题,当A执行完了50秒,B还在持有锁时,A把B的锁给释放了,此时C又拿到了锁和B并发执行,一下子就乱套了。

对此可以采取这些方式解决解决:

  • 可以设置持有锁判断,只有当前线程持有锁,才能进行解锁操作。
  • 可以设置一个守护线程,当前事务锁快要过期时,通过守护线程再增加锁的时效性。

此外redis锁并不是可重入的,而且命令是立刻返回的。
所以还需要考虑锁的重入、再次申请等问题。redis的分布式锁只是缓解并发的一种手段,还需要结合实际情况,多方面考虑。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值