Redis(4)缓存,事务,问题

前言

redis 发布和订阅
redis 持久化
redis 事务
redis 缓存问题

一、redis的发布 订阅

官网

在这里插入图片描述
创建订阅者 cla ,clb, clc,
在这里插入图片描述

subscribe :表示我们成功订阅到响应第二个元素提供的频道。 第三个城市代表我现在订阅频道的数量

在这里插入图片描述
message: 这是另外一个客户端发出的发布命令的结果。第二个元素是来源频道的名称,第三个参数是实际消息的内容。

多个订阅一个发布

在这里插入图片描述

1.模式匹配订阅

在这里插入图片描述
数据库与作用域

发布/订阅与key所在空间没有关系,它不会受任何级别的干扰,包括不同数据库编码。

在这里插入图片描述

二、redis的持久化

官网介绍

redis提供两种方式进行持久化,一种是RDB(redis database)持久化(原理是将Reids在内存中的数据记录定时dump到磁盘上的RDB持久化),另外一种是AOF持久化(原理是将Reids的写操作日志("set aaa 111" "lpush lista 1122"....)以追加的方式写入文件)。

2.1、区别

RDB
RDB持久化是指在指定时间间隔内将内存中的数据集快照(这一时刻的内容)写入磁盘,实际操作过程是fork(分叉)一个子进程,先将数据集写入临时文件,写入成功后,在替换之前的文件,用二进制压缩存储
在这里插入图片描述
AOF
AOF持久以日志的形式记录服务器所处理的每一个写的操作(注意),查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

在这里插入图片描述

2.2、RDB的具体实现

查看配置文件
vim /usr/redis/bin/redis.conf
看196行左右
看 save the DB on disk
在这里插入图片描述
在这里插入图片描述

 #after 900 sec (15 min) if at least 1 key changed 

在15分钟之内,如果有一个key发生变化都会发生一次RDB

#after 300 sec (5 min) if at least 10 keys changed

在5分钟之内,如果有10个key发生变化都会发生一次RDB

#after 60 sec if at least 10000 keys changed

在1分钟之内,如果有10000个key发生变化都会发生一次RDB

在1分钟之内 极限key的改变数量:81000次/秒*60 = 4860000 个 key

生成的文件名称为:
#The filename where to dump the DB
:253 dbfilename dump.rdb 在启动redis服务的位置会生成该文件

2.3、AOF的具体实现

查看配置文件
vim /usr/redis/bin/redis.conf
699
appendonly 开启AOF的话该为yes
在这里插入图片描述
703
aop 写命令存储的文件
在这里插入图片描述

705 aof配置
aor redis 默认支持的3种默认方式
在这里插入图片描述

no: don't fsync, just let the OS flush the data when it wants. Faster.

从来不执行aof 速度最快,最不安全(数据丢失时,就找不回来)

always: fsync after every write to the append only log. Slow, Safest

每一个写命令执行完成后都把命令追加到appendonly.aof 速度最慢,最安全(数据一条都不会丢失)

everysec: fsync only one time every second. Compromise. 

系统默认,即不是使用no也不使用always使用协调的方式 每一秒执行一次aof
730 appendfsync everysec 默认的方式

AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入 时,无法将数据集恢复成保存时的原样。 (举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug 。) 测试套件里为这种情况添加了测 试: 它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。 虽然这种 bug 在 AOF 文件中并不常见, 但是对比来 说, RDB 几乎是不可能出现这种 bug 的。

2.4、优缺点

RDB优点
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.

RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.

与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.
RDB的缺点
如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.

RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

AOF的优点
使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
AOF 缺点
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

RDB和AOP同时开启时,默认会使用AOF:

你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整

三、redis的事务管理

官方介绍

3.1、redis事务概念

  • 隔离性 :务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 原子操作:事务中的命令要么全部被执行,要么全部都不执行

3.2、事务的一些命令

MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。

MULTI: 命令用于开启一个事务,它总是返回 OK 。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。
EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。

在这里插入图片描述
DISCARD:当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出。

在这里插入图片描述

3.3、事务中的错误处理:

2.6.5之后的处理方式( 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。)

在这里插入图片描述
b的值是字符串 不能加加 所以报错 但是 事务中的a 还是加加啦

这个时候事务没有用的啦 redis 的事务没什么用
在这里插入图片描述
redis 自己没有做好这个事务 还把这个问题想转给程序员们 无语

3.4、watch实现乐观锁:

WATCH: 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。

在这里插入图片描述
在执行exec之前,在另外一个会话中改变a和b的值:

四、redis可能出现的问题

缓存图

4.1、缓存穿透

在这里插入图片描述
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决办法:
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中(布隆过滤器),查询时通过该bitmap过滤。

4.2、缓存击穿

在这里插入图片描述
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
如何避免?

  • 设置热点数据永远不过期。
  • 加互斥锁,互斥锁参考思路如下
    1)缓存中有数据,直接返回结果了
    2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

4.3、缓存雪崩

在这里插入图片描述
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。如何避免?

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
设置热点数据永远不过期。

4.4、缓存和数据库双写一致性问题

Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。如果对数据有强一致性要求,就不能放缓存。 我们只能降低发生不一致的概率,

只有两个情况,尽可能保证一致性和保证效率高的情况下尽量去一致

一致性问题

  1. 读缓存
  2. 如果缓存没有,就读数据库,有缓存就读缓存
  3. 读数据库时,吧值放进数据库

问题

  • 查询数据库数据时,有人进行修改操作,此时数据是读的老数据,缓存中也是老数据

  • 同时更新缓存时,业务量非常大问题,数据库更新失败数据不一致问题

  • 先删除缓存操作,有人进行修改操作,此时数据是读的数据库中的未修改老数据

  • 后删除缓存,如果发生多个操作数据命令,而操作命令的速度不是一定的如果在删除缓存前数据已经被请求结束了,这个时候数据也会发生问题举例例如 发生,节点正好发生了 STW。(Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾回收器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起)

  • 加锁,程序性能大幅度下降

  • 延迟双删,先删,操作完毕在延迟删,会有脏数据, 但已经适合大部分场景了,问题是(操作数据库或者操作缓存可能失败)

  • 加删除重试机制,但是还是有可能删除失败(实现删除的机制也很麻烦)

  • 异步优化方式:消息队列 ,消息队列记录删除失败的缓存key 在获取这个key的时候重试缓存操作
    在这里插入图片描述

  • 异步优化方式:基于订阅binlog的同步机制
    读写分离场景 我们知道数据库(以Mysql为例)主从之间的数据同步是通过binlog同步来实现的,因此这里可以考虑订阅binlog(可以使用canal之类的中间件实现),提取出要删除的缓存项,然后作为消息写入消息队列,然后再由消费端进行慢慢的消费和重试。
    在这里插入图片描述

4.5、Redis 常见的性能问题都有哪些?如何解决?

  1. Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务, 所以 Master 最好不要写内存快照。
  2. Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文 件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不 要启用内存快照做持久化,如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
  3. Master 调用 BGREWRITEAOF 重 写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。
  4. Redis 主从复制的性能 问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内

4.5、大量数据,如何保证热点数据,问题

在redis的内存达到一定大的时候,redis就会进行内存数据淘汰机制
redis采用的是定期删除+惰性删除策略。

就是在你获取key的时候,检查key是否设置过期时间,如果过期了就删除
但是,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。在redis.conf中有一行配置# maxmemory-policy volatile-lru该配置就是配内存淘汰策略的

  • voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 主流

  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  • no-enviction(驱逐):禁止驱逐数据 不要用

保证热点数据就是要保证请求次数多的数据放在缓存中,让请求少的而且不是核心数据的缓存先淘汰掉来保证系统的运行正常。

4.6、redis并发竞争key问题

同时有多个子系统去set一个key
真实的生产环境,基本都是redis集群环境,做了数据分片操作,你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。

  • 分布式锁实现拿到锁就执行
  • 是在一个顺序值 在执行时就去为自己设置一个顺序,

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。

  1. 如果对这个key操作,不要求顺序
    这个情况可以使用一个分布式锁,谁拿到锁谁操作 简单实现

    • 客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。  (这个方式不算分布式锁了)
    • 服务器角度,利用setnx实现锁。
    1. 互斥性。在任意时刻,只有一个客户端能持有锁。
    2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁
    3. 具有容错性。只要大部分redis节点还可运行,客户端就可以继续进行加锁和解锁。
    4. 解铃还须系铃人。就是加锁和解锁必须是同一个客户端,客户端不可以把别人的锁打开。
  2. 如果对这个key操作,要求顺序
    举例:
    假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
    期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下

系统A key 1 {valueA  3:00}
系统B key 1 {valueB  3:05}
系统C key 1 {valueC  3:10}

那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
写入时保存一个时间戳,写入前先比较自己的时间戳是不是早于现有记录的时间戳,如果早于,就不写入了。

  1. 通过消息队列进行串行化处理。
    4. 使用乐观锁 (没有顺序要求的情况下)(不能在分片集群中使用)
    watch 命令会监视给定的每一个key,当 exec 时如果监视的任一个key自从调用watch后发生过变化,则整个事务会回滚,不执行任何动作。
    (你傻了么,你怎么回滚)

参考文章

作者 :flyingwzb: Redis常见问题总结【精华】

作者: 皮哥四月红: Redis(6)删除策略(定时删除、惰性删除、定期删除)和数据逐出策略

作者 :可乐鸭头 博客园 : Redis分布式锁(并发竞争)

作者 Resourceful!:MySQL数据库与Redis缓存双写一致性

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Network porter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值