带上问题来学redis,看到不吃亏(什么是redis?缓存问题、数据一致性、redis配置文件汉化版)

愿打开此篇能对你有帮助。

在这里插入图片描述

redis是什么?

官方套话我就不多说了,做后端的朋友多多少少肯定耳濡目染了。

redis
是一个NOSQL类型数据库,
是一个高性能的key-value数据库,
是为了解决高并发、高可用、大数据存储等一系列的问题而产生的数据库解决方案,
是一个非关系型的数据库,
但是,它也是不能替代关系型数据库,只能作为特定环境下的扩充。

这么说可还算中肯?没有神话它,也把它的情况点出来了。

总的来说,它是一个很好用且应用范围很广的数据库中间件,缓存中间件

为什么说redis是缓存中间件??

redis由于数据的读取和操作都在内存当中操作,读写的效率较高,所以经常被用来做数据的缓存。把一些需要频繁访问的数据,而且在短时间之内不会发生变化的,放入redis中进行操作,从而提高用户的请求速度和降低MySQL数据库(后面都默认数据库 = MySQL)的负载。

弄了两张对比图:
没有用redis时,服务器对数据库的访问情况是这样的:
在这里插入图片描述
用了redis之后,服务器对数据的访问是这样的:
在这里插入图片描述

为什么要这么做?别急,等我们看完“缓存穿透”就知道了。

redis.conf翻译与配置

博主不辞辛劳翻译了一下redis.conf配置文件,感觉里面东西还是挺好的。
redis.conf翻译与配置(一)【redis6.0.6】
redis.conf翻译与配置(二)【redis6.0.6】
redis.conf翻译与配置(三)【redis6.0.6】
redis.conf翻译与配置(四)【redis6.0.6】
redis.conf翻译与配置(五)【redis6.0.6】
redis.conf翻译与配置(六)【redis6.0.6】
翻译亦是不易,大家多多支持


redis VS memcache

先来看看 MC 的特点:

  • MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀;
  • MC 功能简单,使用内存存储数据;
  • MC 对缓存的数据可以设置失效期,过期后的数据会被清除;
  • 失效的策略采用延迟失效,就是当再次使用数据时检查是否失效;
  • 当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。

另外,使用 MC 有一些限制,这些限制在现在的互联网场景下很致命,成为大家选择Redis、MongoDB的重要原因:

  • key 不能超过 250 个字节;
  • value 不能超过 1M 字节;
  • key 的最大失效时间是 30 天;
  • 只支持 K-V 结构,不提供持久化和主从同步功能。

再简单说一下 Redis 的特点,方便和 MC 比较。

1、与 MC 不同的是,Redis 采用单线程模式处理请求。这样做的原因有 2 个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
2、Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
3、相比 MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
4、Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。


缓存穿透

什么是缓存穿透?

业务系统要查询的数据根本就存在!当业务系统发起查询时,按照上述流程,首先会前往缓存中查询,由于缓存中不存在,然后再前往数据库中查询。由于该数据压根就不存在,因此数据库也返回空。这就是缓存穿透。

缓存穿透的危害!!!

如果存在海量请求查询压根就不存在的数据,那么这些海量请求都会落到数据库中,数据库压力剧增,可能会导致系统崩溃(你要知道,目前业务系统中最脆弱的就是IO,稍微来点压力它就会崩溃,所以我们要想种种办法保护它)。

那么我们现在再来想想,为什么需要用缓存。答案已经很明显了,不用缓存,相当于直接击穿。

该当如何?????

方案一:缓存空值

这个方案简单讲一下。
之所以发生缓存穿透,是因为缓存中没有存储这些空数据的key,导致这些请求全都打到数据库上。
那么,我们可以稍微修改一下业务系统的代码,将数据库查询结果为空的key也存储在缓存中。当后续又出现该key的查询请求时,缓存直接返回null,而无需查询数据库。

方案二:布隆过滤器

在这里插入图片描述

使用布隆过滤器。在缓存之前在加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,存在再查缓存和DB。

布隆过滤器

关于布隆过滤器,如果要讲的话又可以来一篇博客了,但是不了解又不好,这是一篇我之前转的布隆过滤器的文章:传送门,既然讲到这里,那后面我会去在原文基础上再进行修改,地址不会变。

布隆过滤器特性:
如果布隆过滤器判断该元素存在,那么该元素大概率存在,如果布隆过滤器判断该元素不存在,那么该元素则一定不存在。

两种方案比较

第一种方案:容易出现缓存太多空值占用了更多的空间,得不偿失。

对于空数据的key各不相同、key重复请求概率低的场景而言,应该选择第二种方案。而对于空数据的key数量有限、key重复请求概率较高的场景而言,应该选择第一种方案。

简单的说:第二种方案比较应景。


缓存雪崩

通过上文可知,缓存其实扮演了一个保护数据库的角色。它帮数据库抵挡大量的查询请求,从而避免脆弱的数据库受到伤害。

在这里插入图片描述

如果缓存因某种原因发生了宕机,那么原本被缓存抵挡的海量查询请求就会像疯狗一样涌向数据库。此时数据库如果抵挡不了这巨大的压力,它就会崩溃。

这就是缓存雪崩。

雪崩?到点了,键值通通下班了。。。

某一个时间段内,缓存大量失效或者缓存服务器挂掉(重启)时,导致大量请求直接去访问数据库,导致数据库崩溃。

雪崩时,每一片雪花都在想着勇闯天涯! – 看,未来

这就像下班高峰期一样,高速路、大马路、小马路通通堵上了,交通陷入了困境。。。

如何处置乎???

方案一:永不下班(设置永不过期)

开个玩笑啊,这个方法简直是,没话说。咱还没到富得流油的时候,没那么多内存空间啊。

方案二:错峰(随机key值过期时间)

既然让大家“不畏惧上班,不想着下班”是不现实的,又要解决下班高峰期的问题,怎么办?
之前不是有个方法,叫流量错峰嘛,错开高峰期,比方说你六点下班,我五点下班,他九点半,是吧。

key的失效期分散开,不同的key设置不同的有效期,这样就可以有效的避免大量key值下班而导致的窘境了。

方案三:设置二级缓存

加一层本地缓存(例如Guava Cache、ECache等),采用本地缓存+分布式缓存redis的方式。

方案四:redis高可用

主从+集群····

方案五:降级

依赖隔离组件为后端限流并降级。在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

个人认为,二、四比较好一些。


缓存击穿(热点数据集中失效)

其实理解了前面的缓存穿透和缓存雪崩之后,就很好理解缓存击穿了。

如果某一个热点数据失效,那么当再次有该数据的查询请求时就会前往数据库查询。但是,从请求发往数据库,到该数据更新到缓存中的这段时间中,由于缓存中仍然没有该数据,因此这段时间内到达的查询请求都会落到数据库上,这将会对数据库造成巨大的压力。此外,当这些请求查询完成后,都会重复更新缓存。

解决方案

方案一:锁

此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可.

当某一个热点数据失效后,只有第一个数据库查询请求发往数据库,其余所有的查询请求均被阻塞,从而保护了数据库。但是,由于采用了互斥锁,其他请求将会阻塞等待,此时系统的吞吐量将会下降。这需要结合实际的业务考虑是否允许这么做。

互斥锁可以避免某一个热点数据失效导致数据库崩溃的问题,而在实际业务中,往往会存在一批热点数据同时失效的场景。那么,对于这种场景该如何防止数据库过载呢?

参考“雪崩”,错峰。

方案二:永不过期

在处理雪崩问题上这个方法会比较扯,但是在处理热键问题是可以考虑的。

方案比较

互斥锁 (mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。
” 永远不过期 “:这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。


数据一致性

读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存和数据库间的数据一致性问题。不管是先写数据库,再删除缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举个例子:

1、如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
2、如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。如何解决?


我觉得我有必要先说一下为什么要删除缓存,而不是更新缓存,其实应该也能猜到吧,删除都这么麻烦了,还更新呢,更新需要的资源更多,不更麻烦嘛。但是这样说显得我们很土啊,所以换个说法:

很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。

比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。

另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,这个缓存到底会不会被频繁访问到?

其实删除缓存,而不是更新缓存,就是一个 Lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。


解决方案

结合前面例子的两种删除情况,我们就考虑前后双删加懒加载模式。

什么是懒加载?

就是当业务读取数据的时候再从存储层加载的模式,而不是更新后主动刷新。
如果你有伸展树(传送门)的基础,那理解这个“懒加载”就会融会贯通。

在这里插入图片描述

延迟双删

在写库前后都进行redis.del(key)操作,并且第二次删除通过延迟的方式进行。
异步延迟删除:

1)先删除缓存;
2)再写数据库;
3)触发异步写入串行化mq(也可以采取一种key+version的分布式锁);
4)mq接受再次删除缓存。

异步删除对线上业务无影响,串行化处理保障并发情况下正确删除。

为什么要双删?

db更新分为两个阶段,更新前及更新后,更新前的删除很容易理解,在db更新的过程中由于读取的操作存在并发可能,会出现缓存重新写入数据,这时就需要更新后的删除。

双删失败如何处理?

1、设置缓存过期时间
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致。

2、重试方案
重试方案有两种实现,一种在业务层做,另外一种实现中间件负责处理。

业务层实现重试如下:
在这里插入图片描述
然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。

中间件实现重试如下:
在这里插入图片描述


其他

如何发现热key

  1. 预估热key,比如秒杀的商品、火爆的新闻等
  2. 在客户端进行统计,实现简单,加一行代码即可
  3. 如果是Proxy,比如Codis,可以在Proxy端收集
  4. 利用Redis自带的命令,monitor、hotkeys。但是执行缓慢(不要用)
  5. 利用基于大数据领域的流式计算技术来进行实时数据访问次数的统计,比如 Storm、Spark、Streaming、Flink,这些技术都是可以的。发现热点数据后可以写到zookeeper中
解决方案
  1. 变分布式缓存为本地缓存,发现热key后,把缓存数据取出后,直接加载到本地缓存中。可以采用Ehcache、Guava Cache都可以,这样系统在访问热key数据时就可以直接访问自己的缓存了。(数据不要求时时一致)
  2. 在每个Redis主节点上备份热key数据,这样在读取时可以采用随机读取的方式,将访问压力负载到每个Redis上。
  3. 利用对热点数据访问的限流熔断保护措施,每个系统实例每秒最多请求缓存集群读操作不超过 400 次,一超过就可以熔断掉,不让请求缓存集群,直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。(首页不行,系统友好性差)通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群.

如何发现Big key

Big key

大key指的是存储的值(Value)非常大。

   大key会大量占用内存,在集群中无法均衡
   Redis的性能下降,主从复制异常
   在主动删除或过期删除时会操作时间过长而引起服务阻塞
如何发现Big key
redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。但如果Redis 的key比较多,执行该命令会比较慢。
获取生产Redis的rdb文件,通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行分析统计,根据size_in_bytes统计big key
解决方案:
string类型的big key,尽量不要存入Redis中,可以使用文档型数据库MongoDB或缓存到CDN上。如果必须用Redis存储,最好单独存储,不要和其他key一起存储。采用一主一从或多从。
单个简单key存储的value很大,可以尝试将对象分拆成几个key-value, 使用mget获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多次操作中,降低对redis的IO影响。
hash, set,zset,list 中存储过多的元素,可以将这些元素分拆。

先到这儿啦,如果觉得点进来不亏,不妨顺手来个关注收藏。

  • 175
    点赞
  • 296
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 144
    评论
Redis缓存是一种基于内存的高性能键值存储数据库。它常被用作缓存服务器,可以将常用的数据缓存在内存中,以提高应用程序的响应速度。 优点: 1. 快速读取:与传统的磁盘数据库相比,Redis缓存可以更快地读取数据。 2. 高并发:Redis缓存的单线程模型能够避免并发问题,同时支持高并发访问。 3. 数据结构丰富:Redis缓存支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,方便开发人员使用。 4. 分布式Redis缓存可以实现分布式缓存,提高了系统的扩展性和容错性。 缺点: 1. 内存限制:Redis缓存存储的数据量受限于服务器的内存大小。 2. 数据一致性Redis缓存中的数据可能会因为故障等原因丢失,需要进行备份和恢复操作。 3. 高并发写入:当Redis缓存中的数据需要频繁更新时,可能会导致性能下降。 Redis缓存支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等。其中,字符串适用于缓存简单的值或对象,哈希表适用于缓存复杂的对象,列表适用于缓存队列等数据结构,集合适用于缓存无序的元素集合,有序集合适用于缓存有序的元素集合。 Redis缓存分布式实现可以通过一致性哈希算法等方式来实现。一致性哈希算法可以使得数据在多个节点之间均匀分布,提高系统的性能和可靠性。 为了保证Redis缓存的可靠性和数据一致性,可以使用持久化方式来将数据写入到磁盘中,以防止数据丢失。同时,可以设置主从复制,将数据复制到多个节点,提高系统的可靠性。 Redis缓存的过期策略有两种:定时过期和惰性过期。定时过期是指设置一个过期时间,在这个时间之后数据会被自动删除;惰性过期是指在访问数据时检查它是否过期,如果过期则进行删除。可以通过设置过期时间和过期策略来控制Redis缓存数据的有效性。 Redis缓存的持久化方式有两种:RDB和AOF。RDB将内存中的数据周期性地写入到磁盘中,适用于需要快速备份和恢复数据的场景;AOF则将Redis缓存的写操作记录到文件中,适用于需要保证数据一致性和可靠性的场景。 为了优化Redis缓存的性能,可以采用以下方法: 1. 合理使用数据结构,选择适合的数据类型和算法。 2. 设置合理的过期时间和过期策略,避免数据的过期和无效。 3. 使用分布式缓存,将数据分散在多个节点中,提高系统的性能和可靠性。 4. 使用连接池和异步IO等技术,避免因连接和IO造成的性能瓶颈。 为了保证Redis缓存数据库的一致性,可以使用缓存更新策略。当数据库中的数据发生变化时,可以通过订阅数据库更新事件的方式,将更新的数据同步到Redis缓存中,以保证数据一致性。 为了实现Redis缓存的高可用性,可以使用主从复制和哨兵模式。主从复制可以将数据复制到多个节点,提高系统的容错性;哨兵模式则可以监控Redis缓存的状态,当主节点出现故障时,自动选择新的主节点,保证系统的高可用性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

看,未来

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

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

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

打赏作者

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

抵扣说明:

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

余额充值