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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介:
愿打开此篇能对你有帮助。

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 中存储过多的元素,可以将这些元素分拆。

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
16天前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
159 85
|
14天前
|
缓存 监控 NoSQL
Redis经典问题:缓存穿透
本文详细探讨了分布式系统和缓存应用中的经典问题——缓存穿透。缓存穿透是指用户请求的数据在缓存和数据库中都不存在,导致大量请求直接落到数据库上,可能引发数据库崩溃或性能下降。文章介绍了几种有效的解决方案,包括接口层增加校验、缓存空值、使用布隆过滤器、优化数据库查询以及加强监控报警机制。通过这些方法,可以有效缓解缓存穿透对系统的影响,提升系统的稳定性和性能。
|
2月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
14天前
|
存储 消息中间件 设计模式
缓存数据一致性策略如何分类?
数据库与缓存数据一致性问题的解决方案主要分为强一致性和最终一致性。强一致性通过分布式锁或分布式事务确保每次写入后数据立即一致,适合高要求场景,但性能开销大。最终一致性允许短暂延迟,常用方案包括Cache-Aside(先更新DB再删缓存)、Read/Write-Through(读写穿透)和Write-Behind(异步写入)。延时双删策略通过两次删除缓存确保数据最终一致,适用于复杂业务场景。选择方案需根据系统复杂度和一致性要求权衡。
42 0
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
46 5
|
3月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
117 1
|
3月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
54 2
数据的存储--Redis缓存存储(二)
|
3月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
85 6
|
2月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
2月前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
343 22