iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Redis作为缓存应用的情形详细分析
  • 741
分享到

Redis作为缓存应用的情形详细分析

Redis作为缓存Redis作为缓存应用Redis缓存 2023-01-28 06:01:43 741人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录为什么使用缓存应用场景使用缓存的收益和成本缓存不一致业务场景先更新数据库值再更新缓存值删除缓存值再更新数据库值先更新数据库值在删除缓存值方案的详细设计订阅binlog总结缓存问题

为什么使用缓存

Redis是一个内存型数据库,也就是说,所有的数据都会存在与内存中,基于Redis的高性能特性,我们将Redis用在缓存场景非常广泛。使用起来方便,响应也是远超关系型数据库。

应用场景

Redis的应用场景非常广泛。虽然Redis是一个key-value的内存数据库,但在实际场景中,Redis经常被作为缓存来使用,如面对数据高并发的读写、海量数据的读写等。

举个例子,A网站首页一天有100万人访问,其中有一个“积分商城”的板块,要直接从数据库查询,那么一天就要多消耗100万次数据库请求。如果将这些数据储存到Redis(内存)中,要用的时候,直接从内存调取,不仅可以大大节省系统直接读取磁盘来获得数据的io开销,提高服务器的资源利用率,还能极大地提升速度。

比如很多大型电商网站、视频网站和游戏应用等,存在大规模数据访问,对数据查询效率要求高。Redis服务可实现页面缓存、应用缓存、状态缓存、事件并行处理,能够有效减少数据库磁盘IO,提高数据查询效率,减轻管理维护工作量,降低数据库存储成本。对传统磁盘数据库是一个重要的补充,成为了互联网应用,尤其是支持高并发访问的互联网应用必不可少的基础服务之一。

具体而言,分布式缓存Redis可用于以下场景:

1、页面缓存

Redis可将WEB页面的内容片段,包括htmlCSS和图片等静态数据,缓存到Redis实例,提高网站的访问性能。

比如在电商类应用中,热销商品展示、秒杀推荐等数据面临高并发读的压力,分布式缓存Redis的高并发及灵活扩展,可轻松支持此类应用。

2、状态缓存

Redis可将Session会话状态及应用横向扩展时的状态数据等缓存到DCS实例,实现状态数据共享。在应对游戏应用中爆发式增长的玩家数据存储和读写请求时,使用分布式缓存Redis可通过将热点数据放入缓存,加快用户端访问速度,提升用户体验。

3、应用对象缓存

Redis可作为服务层的二级缓存对外提供服务,减轻数据库的负载压力,加速应用访问。

4、事件缓存

Redis可提供针对事件流的连续查询(continuous query)处理技术,满足实时性需求。

使用缓存的收益和成本

如图左侧为客户端直接调用存储层的架构,右侧为比较典型的缓存层+存储层架构,下面分析一下缓存加入后带来的收益和成本。

收益:

l 加速读写:因为缓存通常都是全内存的,而存储层通常读写性能不够强悍(例如Mysql),通过缓存的使用可以有效地加速读写,优化用户体验。

l 降低后端负载:帮助后端减少访问量和复杂计算(例如很复杂的sql语句),在很大程度降低了后端的负载。

成本:

l 数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。

l 代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。

l 运维成本:以Redis Cluster为例,加入后无形中增加了运维成本。

缓存不一致

一致性

1、强一致性

如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。

2、弱一致性

这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态

3**、最终一致性**

最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。一般情况下,高可用只确保最终一致性,不确保强一致性。

强一致性,读请求和写请求会串行化,串到一个内存队列里去,这样会大大增加系统的处理效率,吞吐量也会大大降低。

业务场景

在绝大多数的系统中数据库往往是用户并发访问最薄弱的地方,并且在高并发下的读多写少的情况下,我们往往会借助一些中间键,来解决数据访问过大时造成的数据库宕机情况,例如我们可以使用Redis来作为缓存,让请求先访问到Redis,而不是直接访问数据库。而在这种业务场景下,可能会出现缓存和数据库数据不一致性的问题。

问题产生的原因

一般来说读取缓存步骤是不会有什么问题的,但是一旦涉及到数据更新,也就是数据库和缓存都操作,就容易出现缓存(Redis)和数据库(mysql)间的数据一致性问题。

在数据更新时,我们需要做以下两步:

  • 操作MySQL
  • 操作缓存

但是无论是先执行步骤1还是先执行步骤2,都有可能出现数据不一致的情况,主要是因为读写是并发的,我们无法保证他们的先后顺序。

相关策略

先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案(如果要求强一致性的话,我认为没有必要添加缓存了,直接走数据库)。这种前提下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。

给出了三种更新策略:

  • 先更新数据库,再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,在删除缓存

先更新数据库值再更新缓存值

最不可能选择的策略,原因是此种策略可能会在线程安全的角度和业务场景角度生成脏数据和性能问题。

原因一:线程安全的角度

同时有请求A和请求B进行更新操作,那么就会出现

  • 请求A更新数据库
  • 请求B更新数据库
  • 请求B更新缓存
  • 请求A更新缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B比A更早更新了缓存。这就导致了脏数据,因此不考虑。

业务场景角度

(1)如果是写数据库场景比较多,而读数据场景比较少的业务需求,那么采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能,缓存此类数据,没有很大的意义。

(2)如果是写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

后面两种策略都是对缓存进行删除,这里先做一个解释。

例子:数据库在1小时内更新1000次那么缓存也更新1000次,但是这个缓存可能在1小时内只被读了1次,那么就没有必要更新1000次了。反过来,如果是删除的话,那么也只是做了1次删除操作,当缓存真正被读取的时候才去更新。

删除缓存值再更新数据库值

  • 请求A进行更新操作,首先删除缓存
  • 请求B查询发现缓存不存在
  • 请求B去数据库查询得到旧值
  • 请求B将旧值写入缓存
  • 请求A将新值写入数据库

上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。我们可以采用延迟双删策略,来解决这个问题。

相对应的步骤:

  • 先淘汰缓存
  • 再写数据库
  • 休眠t秒,再次淘汰缓存

这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

// 伪代码
public void updateDb(String key,Object data) {
    redis.delkey(key);
    db.updateData(data);    
    Thread.sleep(t);
    redis.delKey(key);
}

如果系统中MySQL使用了读写分离模式,那么有可能会出现在主从同步没有完成时,读请求就去读取数据了,这时候就会读取到旧值,这里我们可以延长睡眠时间,让主从同步完成后在进行一次删除(如果不考虑主从的情况下,采用双删不用加延时时间也是可以保证一直性的)。

先更新数据库值在删除缓存值

假设有两个请求,请求A进行更新操作,请求B进行查询操作。

那么会出现如下情形:

  • 请求A进行更新操作,首先更新数据库
  • 请求B进行查询操作,击中缓存,得到旧值
  • 请求A进行删除缓存操作

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

无论是策略2还是策略3都有可能会出现这种情况:删除缓存失败,这时我们可以采用重试机制来保证数据的一致性。

方案的详细设计

在相关策略的调用中,虽然提出了一些简单解决方案,但是没有考虑到列如 缓存删除失败,数据库更新失败等情况,因此需要增加重试策略,但是还是可能会出现比较不一致的问题,此处详细介绍几种方案。

流程如下:

  • 更新数据库数据;
  • 缓存因为种种问题删除失败
  • 将需要删除的key发送至消息队列
  • 自己消费消息,获得需要删除的key
  • 继续重试删除操作,直到成功

如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了。否则的话,我们还需要再次进行重试。如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。

// 伪代码
public void updateDb(String key,Object data){
    db.updateData(data);
    if (!redis.delKey(key)) {
        MQ.send(key);
        new Thread(() -> asyncDel()).start();
    }    
}
// 异步重试
private void asyncDel() {
    int count = 0;
    String key = mq.get();
    while(!redis.delKey(key)) {
        count++;
        if (count > 5) {
            throw new DelFailException();        
        }
    }
    mq.remove(key);
}

这种虽然可以解决,但是会对业务代码造成侵入,而且还需要去维护消息队列,如果可以容忍的话,我觉得是可选的方案之一。

注意 需要使用有序的消息队列,保证消息的有序性。重试删除

订阅binlog

业务代码只会操作数据库,不操作缓存。同时启动一个订阅binlog的程序去监听删除操作,然后投递到消息队列中。再启动一个消费者,根据消息去删除缓存。

canal是用来模拟MySQL slave,来订阅MySQL master 的binlog。

异步重试

总结

对于读多写少的数据,请使用缓存。

为了保持数据库和缓存的一致性,会导致系统吞吐量的下降。

为了保持数据库和缓存的一致性,会导致业务代码逻辑复杂。

缓存做不到绝对一致性,但可以做到最终一致性。

对于需要保证缓存数据库数据一致的情况,请尽量考虑对一致性到底有多高要求,选定合适的方案,避免过度设计。

缓存问题

缓存穿透

问题描述

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层,如下图所示

整个过程分为如下3步:

缓存层不命中。存储层不命中,不将空结果写回缓存。返回空结果。

缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。

缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现了缓存穿透问题。

解决方案

造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题,第二,一些恶意攻击、爬虫等造成大量空命中。下面我们来看一下如何解决缓存穿透问题。

缓存空对象

如图所示,当第2步存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。

缓存空对象会有两个问题:第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

布隆过滤器拦截

布隆过滤器:实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。可以告诉你某样东西一定不存在或者可能存在。

如图所示,在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。例如:一个推荐系统有4亿个用户id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层。

两种方案比对

缓存雪崩

如图描述了什么是缓存雪崩:由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。

预防和解决缓存雪崩问题,可以从以下三个方面进行着手。

(1) 保证缓存层服务高可用性。如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的Redis Sentinel和Redis Cluster都实现了高可用。

(2) 依赖隔离组件为后端限流并降级。无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部阻塞在这个资源上,造成整个系统不可用。降级机制在高并发系统中是非常普遍的。实际项目中,我们需要对重要的资源(例如Redis、MySQL、HBase、外部接口)都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管理,比如如何关闭资源池、开启资源池、资源池阀值管理,这些做起来还是相当复杂的。这里推荐使用Java依赖隔离工具Hystrix,他是解决依赖隔离的利器。

(3) 提前演练。在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定。

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

问题描述

当一个key是热点key,并发量很大,而且重建缓存不能在短时间完成,在缓存失效的一瞬间,就会有大量的线程来重建缓存,造成后端负载加大,甚至让应用崩溃,这就叫缓存击穿。如下图:

解决方案

互斥

此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可,整个过程如图所示。

永远不过期

“永远不过期”包含两层意思:

l 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。

l 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。

整个过程如图所示:

此方法有效杜绝了热点key产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。

两种方案对比

到此这篇关于Redis作为缓存应用的情形详细分析的文章就介绍到这了,更多相关Redis作为缓存内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Redis作为缓存应用的情形详细分析

本文链接: https://www.lsjlt.com/news/178191.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Redis作为缓存应用的情形详细分析
    目录为什么使用缓存应用场景使用缓存的收益和成本缓存不一致业务场景先更新数据库值再更新缓存值删除缓存值再更新数据库值先更新数据库值在删除缓存值方案的详细设计订阅binlog总结缓存问题...
    99+
    2023-01-28
    Redis作为缓存 Redis作为缓存应用 Redis缓存
  • Nginx+PHP的缓存详细分析
    本篇内容介绍了“Nginx+PHP的缓存详细分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!以下是对Nginx中的PHP缓存进行了详细的分...
    99+
    2023-06-05
  • 以Redis为例,详谈分布式系统缓存的细枝末节
    前言:在分布式Web程序设计中,解决高并发以及内部解耦的关键技术离不开缓存和队列,而缓存角色类似计算机硬件中CPU的各级缓存。如今的业务规模稍大的互联网项目,即使在最初beta版的开发上,都会进行预留设计。但是在诸多应用场景里,也带来了某些...
    99+
    2023-06-02
  • springboot使用shiro-整合redis作为缓存的操作
    说在前面 本来的整合过程是顺着博客的顺序来的,越往下,集成的越多,由于之前是使用ehcache缓存,现在改为redis,限制登录人数 以及 限制登录次数等 都需要改动,本篇为了简单,...
    99+
    2024-04-02
  • Redis整合Spring及结合使用缓存的示例分析
    Redis整合Spring及结合使用缓存的示例分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、Redis介绍什么是Red...
    99+
    2024-04-02
  • PHP开发缓存的实际应用案例分析
    PHP开发缓存的实际应用案例分析引言:随着互联网的快速发展,网站的访问量大幅增加。为了提高网站的性能和响应速度,开发人员需要使用缓存来减少数据库查询,加快数据访问速度。本文将重点介绍PHP中缓存的实际应用案例,包括数据缓存和页面缓存,并提供...
    99+
    2023-11-07
    缓存 开发 PHP 关键词:
  • Web应用的缓存设计模式举例分析
    本篇内容介绍了“Web应用的缓存设计模式举例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!ORM缓存引...
    99+
    2024-04-02
  • Java详细分析讲解自动装箱自动拆箱与Integer缓存的使用
    目录1. 前言2. 包装类3. 自动装箱与自动拆箱4. Interger缓存5. 回答题目1. 前言 自动装箱和自动拆箱是什么?Integer缓存是什么?它们之间有什么关系? 先来看...
    99+
    2024-04-02
  • vue中详情跳转至列表页实现列表页缓存的示例分析
    小编给大家分享一下vue中详情跳转至列表页实现列表页缓存的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!提了一个需求,希...
    99+
    2024-04-02
  • 缓存技术在PHP中的应用:分布式接口的优劣分析
    随着互联网的不断发展,分布式架构已经成为了现代应用程序的主流。分布式架构可以将应用程序的不同部分放置在不同的服务器上,从而提高了应用程序的可伸缩性、可用性和可靠性。在分布式架构中,接口是应用程序的重要组成部分。接口的性能和可用性对于整个应...
    99+
    2023-09-06
    缓存 分布式 接口
  • 使用NodeJS 5分钟 连接 Redis 读写操作的详细过程
    目录本文简介Redis 基础安装写入读取查看所有key删除NodeJS 操作 Redis初始化项目安装 Redis 依赖连接写入数据读取数据删除断开连接本文简介 本文主要讲解使用 N...
    99+
    2024-04-02
  • 如何进行MySQL查询缓存的实际应用代码分析
    这篇文章给大家介绍如何进行MySQL查询缓存的实际应用代码分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。以下主要介绍的是MySQL 查询缓存的实际应用代码以及查看MySQL 查询缓...
    99+
    2024-04-02
  • 您的应用程序需要缓存和分布式吗?Java 缓存分布式教程可以为您提供帮助!
    当我们使用应用程序时,通常会遇到两个问题:应用程序的性能和可扩展性。缓存和分布式是解决这些问题的两个重要工具。在本文中,我们将介绍Java缓存和分布式的基础知识,并演示如何使用Java缓存和分布式来提高应用程序的性能和可扩展性。 一、Ja...
    99+
    2023-09-30
    缓存 分布式 教程
  • Nginx+Redis+Ehcache中大型高并发与高可用的三层缓存架构分析
    这篇文章主要为大家展示了“Nginx+Redis+Ehcache中大型高并发与高可用的三层缓存架构分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Nginx+Redis+Ehcache中大型高...
    99+
    2023-06-02
  • C语言详细分析讲解关键字goto与void的作用
    目录一、关于goto二、void 的意义三、小结一、关于goto 高手潜规则:禁用 goto项目经验:程序质量与 goto 的出现次数成反比最后的判决:将 goto 打入冷宫 下面看...
    99+
    2024-04-02
  • C语言详细分析讲解内存管理mallocreallocfreecalloc函数的使用
    目录C语言内存管理一、动态空间申请二、动态空间的扩容三、释放内存C语言内存管理 malloc && realloc && free &&...
    99+
    2024-04-02
  • C语言详细分析宏定义与预处理命令的应用
    目录宏定义与预处理命令预处理命令 - 宏定义定义符号常量定义傻瓜表达式定义代码段预定义的宏函数 VS 宏定义预处理命令 - 条件式编译示例宏定义与预处理命令 预处理阶段:处理宏定义与...
    99+
    2024-04-02
  • Java 缓存分布式教程:为什么它对您的应用程序至关重要?
    随着互联网的发展,应用程序的负载越来越大,许多应用程序需要处理大量的请求。这种情况下,缓存就成为了应用程序的必要组件之一。缓存可以帮助应用程序提高性能,减轻服务器的负担。在本教程中,我们将介绍缓存的概念以及如何使用 Java 缓存分布式技...
    99+
    2023-09-30
    缓存 分布式 教程
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作