从0开始回顾Redis---系列八

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 缓存1、缓存穿透?缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。解决方案:1. 接口校验:在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。 2. 缓存空值:当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来

缓存

1、缓存穿透?


缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案

  1. 接口校验在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。
  2. 缓存空值:当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。
  3. 布隆过滤器:使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。 基于布隆过滤器的快速检测特性,即使发生缓存穿透了,大量请求只会查询Redis和布隆过滤器,而不会积压到数据库,也就不会影响数据库的正常运行。  

2、缓存击穿?


缓存击穿是指缓存中没有,但数据库中有的数据(一般是热点数据缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大, 造成过大压力。

解决方案

  1. 加互斥锁在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
  2. 热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。这种方式适用于比较极端的场景,例如流量特别特别大的场景,使用时需要考虑业务能接受数据不一致的时间,还有就是异常情况的处理,不要到时候缓存刷新不上,一直是脏数据,那就凉了。

3、缓存雪崩?


缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 过期时间打散:既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
  2. 热点数据不过期:该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。
  3. 加互斥锁:该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。

4、缓存预热?


缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

解决方案

  1. 数据量不大的时候,工程启动的时候进行加载缓存动作;
  2. 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
  3. 数据量太大的时候,优先保证热点数据进行提前加载到缓存。

5、缓存降级?

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
  • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
  • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
  • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

6、缓存一致性具体是什么意思?

缓存数据的一致性,这里的“一致性”包含了两种情况:  

  • 缓存中有数据,那么,缓存的数据值需要和数据库中的值相同;
  • 缓存中本身没有数据,那么,数据库中的值必须是最新值。

不符合这两种情况的,就属于缓存和数据库的数据不一致问题了。

7、非并发下,数据不一致的发生及解决?

在更新数据库和删除缓存值的过程中,无论这两个操作的执行顺序谁先谁后,只要有一个操作失败了,就会导致客户端读取到旧值。

7.1 先删除缓存值,后更新数据库值

缓存删除成功,但是数据库更新失败,导致请求再次访问缓存时,发现缓存缺失,再读数据库时,从数据库中读到旧值

如下:

应用要把数据X的值从10更新为3,先在Redis缓存中删除了X的缓存值,但是更新数据库却失败了。如果此时有其他并发的请求访问X,会发现Redis中缓存缺失,紧接着,请求就会访问数据库,读到的却是旧值10。

7.2 先更新数据库值,后删除缓存。

数据库更新成功,但是缓存删除失败,导致请求再次访问缓存时,发现缓存命中,并从缓存中读取到旧值。

如下:

应用要把数据X的值从10更新为3,先成功更新了数据库,然后在Redis缓存中删除X的缓存,但是这个操作却失败了,这个时候,数据库中X的新值为3,Redis中的X的缓存值为10,这肯定是不一致的。如果刚好此时有其他客户端也发送请求访问X,会先在Redis中查询,该客户端会发现缓存命中,但是读到的却是旧值10。

总结:

解决:重试机制

  • 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如Kafka消息队列),当应用没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
  • 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作。
  • 否则的话,我们还需要再次进行重试。如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。  

8、并发下,数据不一致问题的发生及解决?

8.1 先删除缓存值,后更新数据库值

缓存删除后,尚未更新数据库,此时有并发读请求,并发请求从数据库读到旧值,并且更新到缓存,导致后续请求都读取旧值。

例如:

解决方案 延迟双删,在线程A更新完数据库值以后,我们可以让它先sleep一小段时间,再进行一次缓存删除操作。

原因分析

  • sheep原因:线程A sleep的这段时间中,线程B能够先从数据库读取数据,再把缺失的数据写入缓存,避免空数据,然后线程A再进行删除。(线程A sleep的时间,需要大于线程B读取数据+写入缓存的时间)
  • 再删缓存原因:  为了避免删除缓存后,还没更新数据库,读请求来读到旧数据存于缓存,导致以后都是旧数据,则在读请求结束后,写请求可以删除读请求造成的旧数据。

8.2 先更新数据库值,后删除缓存值。

数据库更新成功后,尚未删除缓存,此时有并发读请求,并发请求从缓存中读到旧值。

例如:

解决方案:等待缓存删除完成期间会有不一致数据短暂存在。

原因分析:

  • 在这种情况下,如果其他线程并发读缓存的请求不多,那么,就不会有很多请求读取到旧值。
  • 而且线程A一般也会很快删除缓存值,这样一来,其他线程再次读取时,就会发生缓存缺失,进而从数据库中读取最新值。所以,这种情况对业务的影响较小

9、缓存和数据库不一致总结

缓存和数据库的数据不一致一般是由两个原因导致的

  1. 删除缓存值或更新数据库失败而导致数据不一致,可以使用重试机制确保删除或更新操作成功。  
  2. 在删除缓存值、更新数据库的这两步操作中,有其他线程的并发读操作,导致其他线程读取到旧值,应对方案是延迟双删。

在大多数业务场景下,我们会把Redis作为只读缓存使用。针对只读缓存来说,我们既可以先删除缓存值再更新数据库,也可以先更新数据库再删除缓存。建议是,优先使用先更新数据库再删除缓存的方法,原因主要有两个:

  1. 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力;
  2. 如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置。

注意:

使用先更新数据库再删除缓存时,如果业务层要求必须读取一致的数据,那我们可以在更新数据库时,暂缓客户端暂存并发读请求,等数据库更新完、缓存值删除后,再读取数据,从而保证数据一致性。

图表总结:

10、Redis缓存有哪些淘汰策略?  

Redis缓存共存在8种淘汰机制,我们可以按照是否会进行数据淘汰把它们分成两类:

  • 不进行数据淘汰的策略,只有noeviction这一种。
  • 会进行淘汰的7种其他策略。

会进行淘汰的7种策略,我们可以再进一步根据淘汰候选数据集的范围把它们分成两类:  

  • 从设置了过期时间的数据集中选择性移除;
  • 从全局的数据集中选择性移除。

设置了过期时间的数据集中选择性移除

  • volatile-ttl在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
  • volatile-random就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
  • volatile-lru会使用LRU算法筛选设置了过期时间的键值对。
  • volatile-lfu会使用LFU算法选择设置了过期时间的键值对。

从全局的数据集中选择性移除

  • allkeys-random策略,从所有键值对中随机选择并删除数据;
  • allkeys-lru策略,使用LRU算法在所有数据中进行筛选。
  • allkeys-lfu策略,使用LFU算法在所有数据中进行筛选。

如图:

使用建议:

  • 优先使用allkeys-lru策略。这样,可以充分利用LRU这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。因此,如果你的业务数据中有明显的冷热数据区分,我建议你使用allkeys-lru策略。
  • 如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用allkeys-random策略,随机选择淘汰的数据就行。
  • 如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用volatile-lru策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据LRU规则进行筛选。

11、过期键的删除策略

Redis 使用的是惰性删除和定期删除相结合的过期删除策略。

  1. 惰性删除:设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。
  • 优点对CPU友好。
  • 缺点:对内存不友好。
  1. 定时删除:设置某个key的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。
  • 优点:对内存友好。
  • 缺点:对CPU不友好。
  1. 定期删除:  每隔一段时间,就对一些Key进行检查,删除里面过期的key。
  • 优点:限制删除操作执行的时长和频率来减少删除操作对CPU的影响,也能有效释放过期键占用的内存。
  • 缺点:难以确定删除操作执行的时长和频率。

并发

1、项目的并发访问问题

我们在使用Redis时,不可避免地会遇到并发访问的问题,比如说如果多个用户同时下单,就会对缓存在Redis中的商品库存并发更新。一旦有了并发写操作,数据就会被修改,如果我们没有对并发写请求做好控制,就可能导致数据被改错,影响到业务的正常使用(例如库存数据错误,导致下单异常)。

2、如何保证并发访问的正确性?

为了保证并发访问的正确性,Redis提供了两种方法,分别是加锁和原子操作。

  1. 加锁  加锁是一种常用的方法,在读取数据前,客户端需要先获得锁,否则就无法进行操作。当一个客户端获得锁后,就会一直持有这把锁,直到客户端完成数据更新,才释放这把锁。
  • 缺陷
  • 加锁操作多,会降低系统的并发访问性能
  • Redis客户端要加锁时,需要用到分布式锁,而分布式锁实现复杂,需要用额外的存储系统来提供加解锁操作。
  1. 原子操作: 原子操作是指执行过程保持原子性的操作,而且原子操作执行时并不需要再加锁,实现了无锁操作。
  • 优点:能保证并发控制,还能减少对系统并发性能的影响。
  • Redis的原子操作采用了两种方法
  • 把多个操作在Redis中实现成一个操作,也就是单命令操作;
  • 把多个操作写到一个Lua脚本中,以原子性方式执行单个Lua脚本。
相关实践学习
基于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
相关文章
|
NoSQL Redis
31Redis - 如何启动多个Redis
31Redis - 如何启动多个Redis
136 0
|
6月前
|
存储 NoSQL 定位技术
从0开始回顾Redis---系列三
数据结构 1、讲一讲Redis数据类型及底层数据结构? Redis 的五大常用数据类型:String(字符串)、List(列表)、Hash(哈希)、Set(集合)和Sorted Set(有序集合) 1.1 String(SDS) 简介 ● 是 Redis 最基本的数据类型,普通的key- value 存储都可以归为此类。二进制安全的,可以包含任何数据,比如 JPG 图片或者序列化的对象,最大能存储 512 MB。 应用场景:计数的场景,用户的访问次数、热点文章的点赞转发数量。 底层实现:String对象底层的数据结构实现主要是 int 和简单动态字符串 SDS。 struct sdshdr{
|
6月前
|
NoSQL Linux Redis
Redis -- 安装客户端redis-plus-plus
Redis -- 安装客户端redis-plus-plus
353 0
|
6月前
|
负载均衡 NoSQL Redis
从0开始回顾Redis---系列五
主从复制 1、什么是Redis主从复制? ● 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。 ● 默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。 2、主从复制有哪些好处? ● 读写分离:master 写、slave 读,提高服务器的读写负载能力; ● 负载均衡:基于主从结构,配合读写分离,由 slave 分担 master 负载,并根据需求的变化,改变 slave 的数量,通过多个从节点分担
|
6月前
|
存储 NoSQL Redis
从0开始回顾Redis---系列二
Redis单线程 1、单线程Redis为什么这么快? 1. 单线程实现:避免了多线程编程模式面临的共享资源的并发访问控制问题,比如线程切换和锁资源争用的开销。 2. 内存存储:Redis是使用内存存储,没有磁盘IO上的开销。 3. 高效的数据结构: 采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。 4. 采用多路复用机制:使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。 2、基于多路复用的高性能I/O模型 多路复用机制是指一个线程处理多个IO流,就是我们经常听到的select/epoll机制。简单来说,在Redis只运行单线程的情况下,该机制允许内
|
6月前
|
运维 监控 NoSQL
从0开始回顾Redis---系列六
哨兵机制 1、什么是哨兵,哨兵的作用是什么? 哨兵其实就是一个运行在特殊模式下的Redis进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。 ● 监控:哨兵进程在运行时,周期性地给所有的主从库发送PING命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的PING命令,哨兵就会把它标记为“下线状态”;同样,如果主库也没有在规定时间内响应哨兵的PING命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。 ● 选主:主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选择一个从库实例,把它作为新的主库。这一步完成后,现在的集群里
|
6月前
|
缓存 NoSQL 关系型数据库
从0开始回顾Redis---系列一
基础 1、Redis是什么?简述它的优缺点? Redis是用 C 语言开发的一个开源的高性能键值对(key-value)内存数据库。经常被用来做缓存,消息队列,分布式锁。 Redis 提供了多种数据类型来支持不同的业务场景,如 字符串(strings),散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets)与范围查询。 Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。 优点: ● 读写性能极高, Redis能读的速度是110000次/s,写的速度是81000次/s。 ● 支持数据持久化,支持AOF和RDB两种持久化方式。 ●
|
6月前
|
SQL 监控 NoSQL
从0开始回顾Redis---系列九
事务 1、Redis能实现ACID属性吗? ACID 1. 原子性: 一个事务中的多个操作必须都完成,或者都不完成。 2. 一致性: 数据库中的数据在事务执行前后是一致的。 3. 隔离性: 数据库在执行一个事务时,其它操作无法存取到正在执行事务访问的数据。 4. 持久性: 数据库执行事务后,数据的修改要被持久化保存下来。当数据库重启后,数据的值需要是被修改后的值。 Redis的事务机制可以保证一致性和隔离性,但是无法保证持久性。不过,因为Redis本身是内存数据库,持久性并不是一个必须的属性,我们更加关注的还是原子性、一致性和隔离性这三个属性。 原子性的情况比较复杂,只有当事务中使
|
6月前
|
NoSQL 算法 Redis
从0开始回顾Redis---系列七
切片集群 1、为什么要集群? 在实际应用Redis时,随着用户或业务规模的扩展,保存大量数据的情况通常是无法避免的。 我们可以用两种方案: 1. 纵向扩展:升级单个Redis实例的资源配置,包括增加内存容量、增加磁盘容量、使用更高配置的CPU。 2. 横向扩展:横向增加当前Redis实例的个数 。 那么,这两种方式的优缺点分别是什么呢? 1. 纵向扩展: ● 优点:实施起来简单、直接。 ● 缺点: ○ 当使用RDB对数据进行持久化时,如果数据量增加,需要的内存也会增加,主线程fork子进程时就可能会阻塞(比如刚刚的例子中的情况) ○ 纵向扩展会受到硬件和成本的限制。 2.
|
6月前
|
存储 NoSQL 定位技术
从0开始回顾Redis---系列四
数据结构 1、讲一讲Redis数据类型及底层数据结构? Redis 的五大常用数据类型:String(字符串)、List(列表)、Hash(哈希)、Set(集合)和Sorted Set(有序集合) 1.1 String(SDS) 简介 ● 是 Redis 最基本的数据类型,普通的key- value 存储都可以归为此类。二进制安全的,可以包含任何数据,比如 JPG 图片或者序列化的对象,最大能存储 512 MB。 应用场景:计数的场景,用户的访问次数、热点文章的点赞转发数量。 底层实现:String对象底层的数据结构实现主要是 int 和简单动态字符串 SDS。 struct sdshdr{