redis缓存 订阅
Redis缓存是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 展开全文
Redis缓存是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
信息
语    言
多种
本    质
API
中文名
Redis缓存
开发主持方
VMware Pivotal
Redis缓存简介
从2010年3月15日起,Redis缓存的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助
收起全文
精华内容
下载资源
问答
  • redis缓存
    万次阅读
    2021-12-11 17:12:55


    前言

    应用 Redis 缓存时,如果能缓存会被反复访问的数据,那就能加速业务应用的访问。 如果发生了缓存污染,缓存对业务应用的加速作用就减少了。

    在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间。这种就是缓存污染。

    当缓存污染不严重时,只有少量数据占据缓存空间,对缓存系统的影响不大。但是缓存污染变得严重后,就会有大量不再访问的数据滞留在缓存中。如果这时数据占满了缓存空间,再往缓存中写入新数据时,就需要先把这些数据逐步淘汰出缓存, 会引入额外的操作时间开销,进而会影响应用的性能。

    一、如何解决缓存污染问题

    要解决缓存污染,把不会再被访问的数据筛选出来并淘汰掉。这样就不用等到缓存被写满以后,再逐一淘汰旧数据之后,才能写入新数据了。而哪些数据能留存在缓存中,是由缓存的淘汰策略决定的。

    8 种数据淘汰策略: noeviction、volatile-random、volatile-ttl、volatile-lru、volatile-lfu、allkeys-lru、 allkeys-random 和 allkeys-lfu 策略。noeviction 策略是不会进行数据淘汰的。所以它不能用来解决缓存污染问题。其他的 7 种策略,都会按照一定的规则来淘汰数据。

    使用 LRU 算法和 LFU 算法的策略各有两种(volatile-lru 和 allkeys-lru,以及 volatile-lfu 和 allkeys-lfu),接下来统一把它们叫 作 LRU 策略和 LFU 策略。

    volatile-random 和 allkeys-random 策略都是采用随机挑选数据的方式,来筛选即将被淘汰的数据。 既然是随机挑选,Redis 就不会根据数据的访问情况来筛选数据。如果被淘汰的数据又被访问了,就会发生缓存缺失,应用需要到后端数据库中访问这些数据,降低了应用的请求响应速度。所以 volatile-random 和 allkeys-random 策略,在避免缓存污染这个问题上的效果非常有限。

    假设我们配置 Redis 缓存使用 allkeys-random 淘汰策略,当缓存写满时,allkeys-random 策略随机选择了数据 20 进行淘汰。不巧的是数据 20 紧接着又被访问了,此时 Redis 就会发生了缓存缺失。
    在这里插入图片描述

    volatile-ttl 针对的是设置了过期时间的数据,把这些数据中剩余存活时间最短的筛选出来并淘汰掉。 虽然 volatile-ttl 策略不再是随机选择淘汰数据了,但是剩余存活时间并不能直接反映数据再次访问的情况。所以按照 volatile-ttl 策略淘汰数据,和按随机方式淘汰数据类似,也可能出现数据被淘汰后,被再次访问导致的缓存缺失问题。

    一种例外的情况:业务应用在给数据设置过期时间的时候,就明确知道数据被再次访问的情况,并根据访问情况设置过期时间。此时 Redis 按照数据的剩余最短存活时间进行筛选,是可以把不会再被访问的数据筛选出来的,进而避免缓存污染。 例如,业务部门知道数据被访问的时长就是一个小时,并把数据的过期时间设置为一个小时后。被淘汰的数据的确是不会再被访问了。

    小结:除了在明确知道数据被再次访问的情况下,volatile-ttl 可以有效避免缓存污染。在其他情况下,volatile-random、allkeys-random、volatile-ttl 这三种策略并不能应对缓存污染问题。

    二、LRU 缓存策略

    LRU 策略:如果一个数据刚刚被访问,那么这个数据肯定是热数据,还会被再次访问。Redis 中的 LRU 策略,会在每个数据对应的 RedisObject 结构体中设置一个 lru 字段,用来记录数据的访问时间戳。在进行数据淘汰时,LRU 策略会在候选数据集中淘汰掉 lru 字段值最小的数据(也就是访问时间最久的数据)。

    在数据被频繁访问的业务场景中,LRU 策略的确能有效留存访问时间最近的数据。 而且因为留存的这些数据还会被再次访问,所以又可以提升业务应用的访问速度。

    但也正是因为只看数据的访问时间,使用 LRU 策略在处理扫描式单次查询操作时,无法解决缓存污染。扫描式单次查询操作是指应用对大量的数据进行一次全体读取,每个数据都会被读取,而且只会被读取一次。因为这些被查询的数据刚刚被访问过,所以 lru 字段值都很大。

    在使用 LRU 策略淘汰数据时,这些数据会留存在缓存中很长一段时间,造成缓存污染。如果查询的数据量很大,这些数据占满了缓存空间,却又不会服务新的缓存请求,再有新数据要写入缓存的话,还是需要先把这些旧数据替换出缓存才行,这会影响缓存的性能。

    如下图,数据 6 被访问后,被写入 Redis 缓存。 但是在此之后数据 6 一直没有被再次访问,导致数据 6 滞留在缓存中,造成了污染。
    在这里插入图片描述

    三、LFU 缓存策略的优化

    LRU 策略的 Redis 缓存的扫描式单次查询会造成缓存污染。为了应对这类缓存污染问题,Redis 从 4.0 版本开始增加了 LFU 淘汰策略。

    与 LRU 策略相比,LFU 策略中会从两个维度来筛选并淘汰数据:

    1. 数据访问的时效性 (访问时间离当前时间的远近);
    2. 数据的被访问次数。

    LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。

    和那些被频繁访问的数据相比,扫描式单次查询的数据因为不会被再次访问,所以它们的访问次数不会再增加。因此 LFU 策略会优先把这些访问次数低的数据淘汰出缓存。 LFU 策略就可以避免这些数据对缓存造成污染了。

    为了避免操作链表的开销,Redis 在实现 LRU 策略时使用了两个近似方法:

    1. Redis 是用 RedisObject 结构来保存数据的,RedisObject 结构中设置了一个 lru 字段,用来记录数据的访问时间戳;
    2. Redis 并没有为所有的数据维护一个全局的链表,而是通过随机采样方式,选取一定数量(例如 10 个)的数据放入候选集合,后续在候选集合中根据 lru 字段值的大小进行筛选。

    在此基础上,Redis 在实现 LFU 策略的时候,只是把原来 24bit 大小的 lru 字段,又进一步拆分成了两部分:

    1. ldt 值:lru 字段的前 16bit,表示数据的访问时间戳;
    2. counter 值:lru 字段的后 8bit,表示数据的访问次数。

    总结一下:当 LFU 策略筛选数据时,Redis 会在候选集合中,根据数据 lru 字段的后 8bit 选择访问次数最少的数据进行淘汰。当访问次数相同时,再根据 lru 字段的前 16bit 值大小,选择访问时间最久远的数据进行淘汰。

    Redis 只使用了 8bit 记录数据的访问次数,而 8bit 记录的最大值是 255,在实际应用中,一个数据可能会被访问成千上万次。如果每被访问一次,counter 值就加 1 的话,只要访问次数超过了 255,数据的 counter 值就一样了。在进行数据淘汰时,LFU 策略就无法很好地区分并筛选这些数据,反而还可能会把不怎么访问的数据留存在了缓存中。

    假设第一个数据 A 的累计访问次数是 256,访问时间戳是 202010010909,所以它的 counter 值为 255,而第二个数据 B 的累计访问次数是 1024,访问时间戳是 202010010810。如果 counter 值只能记录到 255,那么数据 B 的 counter 值也是 255。 此时缓存写满了,Redis 使用 LFU 策略进行淘汰。数据 A 和 B 的 counter 值都是 255,LFU 策略再比较 A 和 B 的访问时间戳,发现数据 B 的上一次访问时间早于 A,就会把 B 淘汰掉。但其实数据 B 的访问次数远大于数据 A,很可能会被再次访问。使用 LFU 策略来淘汰数据就不合适了。

    Redis 在实现 LFU 策略时,Redis 并没有采用数据每被访问一次,就给对应的 counter 值加 1 的计数规则。LFU 策略实现的计数规则是:每当数据被访问一次时,用计数器当前的值乘以配置项 lfu_log_factor 再加 1,再取其倒数得到一个 p 值;把这个 p 值和一个取值范围在(0,1)间的随机数 r 值比大小,只有 p 值大于 r 值时,计数器才加 1。

    下面这段 Redis 的部分源码,显示了 LFU 策略增加计数器值的计算逻辑。baseval 是计数器当前的值。计数器的初始值默认是 5(由代码中的 LFU_INIT_VAL 常量设置), 而不是 0,这样可以避免数据刚被写入缓存,就因为访问次数少而被立即淘汰。

    double r = (double)rand()/RAND_MAX;
    ... 
    double p = 1.0/(baseval*server.lfu_log_factor+1); 
    if (r < p) counter++;  
    

    使用了这种计算规则可以通过设置不同的 lfu_log_factor 配置项,来控制计数器值增加的速度,避免 counter 值很快就到 255 了。

    这是 Redis 官网上提供的一张表,它记录了当 lfu_log_factor 取不同值时,在不同的实际访问次数情况下,计数器的值是如何变化的。
    在这里插入图片描述
    当 lfu_log_factor 取值为 1 时,实际访问次数为 100K 后,counter 值就达到 255 了,无法再区分实际访问次数更多的数据了。当 lfu_log_factor 取值为 100 时,当实际访问次数为 10M 时,counter 值才达到 255,实际访问次数小于 10M 的不同数据都可以通过 counter 值区分出来。

    正是因为使用了非线性递增的计数器方法,即使缓存数据的访问次数成千上万,LFU 策略也可以有效地区分不同的访问次数,从而进行合理的数据筛选。表中当 lfu_log_factor 取值为 10 时,百、千、十万级别的访问次数对应的 counter 值已经有明显的区分了,所以在应用 LFU 策略时一般可以将 lfu_log_factor 取值为 10。

    应用负载的情况是很复杂的。在一些场景下,有些数据在短时间内被大量访问后就不会再被访问了。那么再按照访问次数来筛选的话,这些数据会被留存在缓存中,但不会提升缓存命中率。为此 Redis 在实现 LFU 策略时,还设计了一个 counter 值的衰减机制。

    LFU 策略使用衰减因子配置项 lfu_decay_time 来控制访问次数的衰减。LFU 策略会计算当前时间和数据最近一次访问时间的差值,并把这个差值换算成以分钟为单位。 然后 LFU 策略再把这个差值除以 lfu_decay_time 值,所得的结果就是数据 counter 要衰减的值。

    假设 lfu_decay_time 取值为 1,如果数据在 N 分钟内没有被访问,它的访问次数就要减 N。如果 lfu_decay_time 取值更大,那么相应的衰减值会变小,衰减效果也会减弱。所以如果业务应用中有短时高频访问的数据的话,建议把 fu_decay_time 值设置为 1,LFU 策略在它们不再被访问后,会较快地衰减它们的访问次数,尽早把它们从缓存中淘汰出去,避免缓存污染。

    总结

    缓存污染问题指的是留存在缓存中的数据,实际不会被再次访问了,但是又占据了缓存空间。如果这样的数据体量很大,甚至占满了缓存,每次有新数据写入缓存时,还需要把这些数据逐步淘汰出缓存,就会增加缓存操作的时间开销。

    因此要解决缓存污染问题,最关键的技术点就是能识别出这些只访问一次或是访问次数很少的数据,在淘汰数据时,优先把它们筛选出来并淘汰掉。因为 noviction 策略不涉及数据淘汰,不作考虑,从能否有效解决缓存污染这个维度,分析了 Redis 的其他 7 种数据淘汰策略。

    volatile-random 和 allkeys-random 是随机选择数据进行淘汰,无法把不再访问的数据筛选出来,可能会造成缓存污染。

    如果业务层明确知道数据的访问时长,可以给数据设置合理的过期时间,再设置 Redis 缓存使用 volatile-ttl 策略。当缓存写满时,剩余存活时间最短的数据就会被淘汰出缓存,避免滞留在缓存中造成污染。

    使用 LRU 策略时,由于 LRU 策略只考虑数据的访问时效,对于只访问一次的数据来说,LRU 策略无法很快将其筛选出来。而 LFU 策略在 LRU 策略基础上进行了优化,在筛选数据时,首先会筛选并淘汰访问次数少的数据,然后针对访问次数相同的数据,再筛选并淘汰访问时间最久远的数据。

    在具体实现上,相对于 LRU 策略,Redis 只是把原来 24bit 大小的 lru 字段,又进一步拆分成了 16bit 的 ldt 和 8bit 的 counter,分别用来表示数据的访问时间戳和访问次数。为了避开 8bit 最大只能记录 255 的限制,LFU 策略设计使用非线性增长的计数器来表示数据的访问次数。

    在实际业务应用中,LRU 和 LFU 两个策略都有应用。LRU 和 LFU 两个策略关注的数据访问特征各有侧重,LRU 策略更加关注数据的时效性,而 LFU 策略更加关注数据的访问频次。通常情况下,实际应用的负载具有较好的时间局部性,所以 LRU 策略的应用会更加广泛。但是在扫描式查询的应用场景中,LFU 策略就可以很好地应对缓存污染问题了,建议优先使用。

    此外如果业务应用中有短时高频访问的数据,除了 LFU 策略本身会对数据的访问次数进行自动衰减以外,还可以优先使用 volatile-lfu 策略,并根据这些数据的访问时限设置它们的过期时间,以免它们留存在缓存中造成污染。

    更多相关内容
  • 在本篇文章中小编给大家分享了关于window环境手动操作清理redis缓存的方法和技巧,有兴趣的朋友们可以跟着学习下。
  • 使用.Net 技术操作Redis缓存技术,对Redis缓存进行存储增删改查等相关操作
  • 什么是redis缓存穿透 雪崩 ,如何应对解决 redis缓存穿透 雪崩 的解决办法 redis缓存穿透的解决办法 redis雪崩的解决办法
  • redis缓存技术

    2018-12-14 10:40:07
    这是一份关于redis缓存技术的一些资料,有需要的可以下载看看。
  • * Redis缓存操作 * @author hxm * @version 1.0 * @since 2015.05.04 */ class RCache extends Object implements CacheFace { private $redis = null; //redis对象 private $sId = 1; //servier服务ID ...
  • 零基础学会REDIS缓存数据库在JAVA中的使用。从REDIS的安装和使用开始,到Java程序与REDIS数据库进行连接互动,简单明了。
  • redis缓存使用Redis缓存进行微服务创建Go微服务
  • Redis缓存管理工具:Redis-Desktop
  • REDIS缓存数据库在JAVA中的使用 这个是PPT的版本,介绍了如何在JAVA中使用Redis缓存数据库。
  • SpringBoot整合Redis缓存

    千次阅读 2022-02-16 19:07:21
    SpringBoot整合Redis缓存一、缓存概念知识1、是什么缓存2、缓存的优缺点3、为什么使用缓存二、Redis概念知识1、Redis简介2、为什么用Redis作为缓存3、Redis 支持的数据类型4、Redis缓存常见问题1. 缓存穿透2. 缓存...

    一、缓存概念知识

    1、是什么缓存

    日常生活中经常会听到缓存这个词,那到底什么是缓存呢?其实缓存就是数据交换的缓冲区(称作Cache),是临时存贮数据(使用频繁的数据)的地方。当用户查询数据,首先在缓存中寻找,如果找到了则直接执行;如果找不到则去数据库中查找。

    缓存的本质就是用空间换时间,牺牲数据的实时性,以服务器内存中的数据暂时代替从数据库读取最新的数据,减少数据库IO,减轻服务器压力,减少网络延迟,加快页面打开速度。


    2、缓存的优缺点

    优点:

    • 加快了响应速度
    • 减少了对数据库的读操作,数据库的压力降低

    缺点:

    • 内存容量相对硬盘小

    • 缓存中的数据可能与数据库中数据不一致

    • 内存断电就会清空数据,造成数据丢失

    3、为什么使用缓存

    一般在远端服务器上,考虑到客户端请求量多,某些数据请求量大,这些热点数据要频繁的从数据库中读取,给数据库造成压力,导致响应客户端较慢。所以,在一些不考虑实时性的数据中,经常将这些数据存在内存中,当请求时候,能够直接读取内存中的数据及时响应。

    缓存主要有解决高性能与高并发与减少数据库压力。缓存本质就是将数据存储在内存中,当数据没有发生本质变化的时候,应尽量避免直接连接数据库进行查询,因为并发高时很可能会将数据库压塌,而是应去缓存中读取数据,只有缓存中未查找到时再去数据库中查询,这样就大大降低了数据库的读写次数,增加系统的性能和能提供的并发量。


    二、Redis概念知识

    1、Redis简介

    Redis 是一个高性能的 Key-Value 开源数据库, 是一个非关系型的数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案。但它不能替代关系型数据库,只能作为特定环境下的扩充。


    2、为什么用Redis作为缓存

    • 支持高可用: Redis 支持 master\slave 主\从机制、sentinal 哨兵模式、cluster 集群模式,大大保证了 Redis 运行的稳定和高可用性。
    • 支持多种数据结构: Redis 不仅支持简单的 Key/Value 类型的数据,同时还提供 list、set、zset、hash 等数据结构的存储。
    • 支持数据持久化: 可以将内存中的数据持久化在磁盘中,当宕机或者故障重启时,可以再次加载进如 Redis,从而不会或减少数据的丢失。
    • 生态完善: Redis 已成为业界内缓存的首选目标,所以很多语言和工具对其支持。

    3、Redis 支持的数据类型

    Redis 支持的数据结构类型包括:

    • 字符串(string)
    • 哈希表(hash)
    • 列表(list)
    • 集合(set)
    • 有序集合(zset)

    为了保证读取效率,Redis 把数据对象存储在内存中,并支持周期性的把更新的数据写入磁盘文件中。而且它还提供了交集和并集,以及一些不同方式排序的操作。

    redis的特性决定了它的功能,它可以用来做以下这些事情!

    1. 排行榜,利用zset可以方便的实现排序功能
    2. 计数器,利用redis中原子性的自增操作,可以统计到阅读量,点赞量等功能
    3. 简单消息队列,list存储结构,满足先进先出的原则,可以使用lpush/rpop或rpush/lpop实现简单消息队列
    4. session共享,分布式系统中,可以利用redis实现session共享。spring官方提供的分布式解决方案Spring Session就是利用redis 实现的

    4、Redis缓存常见问题

    1. 缓存穿透

    在这里插入图片描述

    缓存穿透: 指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

    几种解决办法:

    • 缓存空值,在从 DB 查询对象为空时,也要将空值存入缓存,具体的值需要使用特殊的标识, 能和真正缓存的数据区分开,另外将其过期时间设为较短时间。
    • 使用布隆过滤器,布隆过滤器能判断一个 key 一定不存在(不保证一定存在,因为布隆过滤器结构原因,不能删除,但是旧值可能被新值替换,而将旧值删除后它可能依旧判断其可能存在),在缓存的基础上,构建布隆过滤器数据结构,在布隆过滤器中存储对应的 key,如果存在,则说明 key 对应的值为空。

    2. 缓存击穿

    在这里插入图片描述

    缓存击穿: 某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

    几种解决办法:

    • 设置二级缓存,或者设置热点缓存永不过期,需要根据实际情况进行配置。
    • 使用互斥锁,在执行过程中,如果缓存过期,那么先获取分布式锁,在执行从数据库中加载数据,如果找到数据就存入缓存,没有就继续该有的动作,在这个过程中能保证只有一个线程操作数据库,避免了对数据库的大量请求。

    3. 缓存雪崩

    在这里插入图片描述

    缓存雪崩: 当缓存服务器重启、或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,造成数据库后端故障,从而引起应用服务器雪崩。

    几种解决办法:

    • 缓存组件设计高可用,缓存高可用是指,存储缓存的组件的高可用,能够防止单点故障、机器故障、机房宕机等一系列问题。例如 Redis sentinel 和 Redis Cluster,都实现了高可用。
    • 请求限流与服务熔断降级机制,限制服务请求次数,当服务不可用时快速熔断降级。
    • 设置缓存过期时间一定的随机分布,避免集中在同一时间缓存失效。
    • 定时更新缓存策略,对于实时性要求不高的数据,定时进行更新。

    4. 缓存一致性

    使用缓存很大可能导致数据不一致问题,如下:

    • 更熟数据库成功 -> 更新缓存失败 -> 数据不一致
    • 更新缓存成功 -> 更新数据库失败 -> 数据不一致
    • 更新数据库成功 -> 淘汰缓存失败 -> 数据不一致
    • 淘汰缓存成功 -> 更新数据库失败 -> 查询缓存mis

    所以使用缓存时候,应该结合实际情况,考虑缓存的数据是否有一致性需求。


    三、SpringBoot整合redis

    1、使用redis缓存

    1. 引入redis依赖

    在pom.xml文件中引入Redis依赖,如下

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    

    2. 修改项目启动类

    增加注解@EnableCaching,开启缓存功能,如下:

    package com.demo;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cache.annotation.EnableCaching;
    
    @SpringBootApplication
    @MapperScan("com.demo")
    @EnableCaching
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    }
    

    3. 配置redis数据库

    在application.properties中配置Redis连接信息,如下:

    spring:
      redis:
        # redis库
        database: 0
        # redis 服务器地址
        host: localhost
        # redis 端口号
        port: 6379
        # redis 密码
        password:
        # 连接超时时间(毫秒)
        timeout: 1000
        lettuce:
          pool:
            # 连接池最大链接数(负数表示没有限制)
            max-active: 8
            # 连接池最大阻塞等待时间(负数表示没有限制)
            max-wait: -1
            # 连接池最大空闲连接数
            max-idle: 8
            # 连接池最小空闲连接数
            min-idle: 0
    

    4. 创建redis配置类

    新建Redis缓存配置类,如下:

    import org.springframework.cache.CacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.serializer.*;
    
    import java.time.Duration;
    
    /**
     * Redis 配置类
     */
    @Configuration
    public class RedisConfig {
    
        /**
         * 配置缓存管理器
         * @param factory Redis 线程安全连接工厂
         * @return 缓存管理器
         */
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory factory) {
            // 生成两套默认配置,通过 Config 对象即可对缓存进行自定义配置
            RedisCacheConfiguration cacheConfig1 = RedisCacheConfiguration.defaultCacheConfig()
                    // 设置过期时间 10 分钟
                    .entryTtl(Duration.ofMinutes(10))
                    // 设置缓存前缀
                    .prefixKeysWith("cache:user:")
                    // 禁止缓存 null 值
                    .disableCachingNullValues()
                    // 设置 key 序列化
                    .serializeKeysWith(keyPair())
                    // 设置 value 序列化
                    .serializeValuesWith(valuePair());
    
            RedisCacheConfiguration cacheConfig2 = RedisCacheConfiguration.defaultCacheConfig()
                    // 设置过期时间 30 秒
                    .entryTtl(Duration.ofSeconds(30))
                    .prefixKeysWith("cache:admin:")
                    .disableCachingNullValues()
                    .serializeKeysWith(keyPair())
                    .serializeValuesWith(valuePair());
    
            // 返回 Redis 缓存管理器
            return RedisCacheManager.builder(factory)
                    .withCacheConfiguration("user", cacheConfig1)
                    .withCacheConfiguration("admin", cacheConfig2)
                    .build();
        }
    
        /**
         * 配置键序列化
         * @return StringRedisSerializer
         */
        private RedisSerializationContext.SerializationPair<String> keyPair() {
            return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
        }
    
        /**
         * 配置值序列化,使用 GenericJackson2JsonRedisSerializer 替换默认序列化
         * @return GenericJackson2JsonRedisSerializer
         */
        private RedisSerializationContext.SerializationPair<Object> valuePair() {
            return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
        }
    
    }
    

    5. 操作redis

    SpringBoot提供了两个bean来操作redis,分别是RedisTemplateStringRedisTemplate,这两者的主要区别如下:

    RedisTemplate使用的是JdkSerializationRedisSerializer ,存入数据会将数据先序列化成字节数组然后在存入Redis数据库;

    StringRedisTemplate使用的是StringRedisSerializer。

    示例如下:

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userServer;
        
        @Autowired
        StringRedisTemplate stringRedisTemplate;
    
        
        /**
         * 查询所有课程
         */
        @RequestMapping("/allCourses")
        public String findAll() {
            List<Courses> courses = userServer.findAll();
            
            // 将查询结果写入redis缓存
            stringRedisTemplate.opsForValue().set("hot", String.valueOf(courses));
            
            // 读取redis缓存
            System.out.println(stringRedisTemplate.opsForValue().get("courses"));
    
            return "ok";
        }
    }
    

    2、使用 SpringCache 的注解

    1. 注解说明

    • @CacheConfig: 一般配置在类上,指定缓存名称,这个名称是和上面“置缓存管理器”中缓存名称的一致。
    • @Cacheable: 用于对方法返回结果进行缓存,如果已经存在该缓存,则直接从缓存中获取,缓存的key可以从入参中指定,缓存的 value 为方法返回值。
    • @CachePut: 无论是否存在该缓存,每次都会重新添加缓存,缓存的key可以从入参中指定,缓存的value为方法返回值,常用作于更新。
    • @CacheEvict: 用于清除缓存
    • @Caching: 用于一次性设置多个缓存。

    2. 常用注解配置参数

    • value: 缓存管理器中配置的缓存的名称,这里可以理解为一个组的概念,缓存管理器中可以有多套缓存配置,每套都有一个名称,类似于组名,这个可以配置这个值,选择使用哪个缓存的名称,配置后就会应用那个缓存名称对应的配置。
    • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
    • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。
    • unless: 不缓存的条件,和 condition 一样,也是 SpEL 编写,返回 true 或者 false,为 true 时则不进行缓存。

    3. 自动缓存

    @Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。

    如果添加了@Cacheable注解,那么方法被调用后,值会被存入redis,下次再调用的时候会直接从redis中取值返回。

    @Service
    @CacheConfig(cacheNames = "user")
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
        
        // 获取全部用户
        @Cacheable(key = "'allUsers'", unless = "#result==null")
        public List<Courses> findAll() {
            return userMapper.allUsers();
        }
    }
    

    四、案例说明

    1、创建实体类(model层)

    public class User {
        private String username;
        private String password;    
        
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
    

    2、创建接口(mapper层)

    @Component
    public interface UserMapper {
    
        // 查询所有用户
        @Select("select * from user_tbl")
        List<Courses> allUsers();
        
        // 更新用户信息
        @Update("update user_tbl set password=#{password} where username=#{username};")
        boolean updateUser(String username, String password);
    
        // 删除用户
        @Select("delete from user_tbl where username = #{username};")
        Integer delUser(String username);
    }
    
    

    3、创建服务类(service层)

    @Service
    @CacheConfig(cacheNames = "user")
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        // 查询全部用户
        @Cacheable(key = "'allUsers'", unless = "#result==null")
        public List<Courses> allUsers() {
            return userMapper.allUsers();
        }
            
        // 更新用户信息
        @CachePut(key = "#user.username")
        public void updateUser(String username, String password) {
            userMapper.updateUser(username, password);
        }
        
        // 删除用户
        @CacheEvict(key = "#username")
        public void delUser(String username) {
            userMapper.delUser(username);
        }
    }
    
    

    4、创建控制器(controller层)

    @RestController
    public class UserController {
    
        @Autowired
        private UserService userServer;
        
        @Autowired
        StringRedisTemplate stringRedisTemplate;
    
    
        /**
         * 查询所有用户
         */
        @RequestMapping("/allUsers")
        public String allUsers() {
            userServer.allUsers();
            return "ok";
        }
        
        
        /**
         * 更新用户信息
         */
        @RequestMapping("/updateUser")
        public String updateUser() {
            String username = "tom";
            String password = "abc123";
            userServer.updateUser(username,password);
            return "ok";
        }
        
        
        /**
         * 删除用户
         */
        @RequestMapping("/delUser")
        public String delUser() {
            String username = "tom"; 
            userServer.delUser(username);
            return "ok";
        }
        
    }
    
    
    展开全文
  • wondows系统下redis缓存数据库
  • Redis缓存

    千次阅读 多人点赞 2020-08-01 17:06:46
    1. Redis缓存 2. 为什么需要使用缓存 说明:使用缓存可以有效的降低用户访问物理设备的频次,有效的减少并发的压力。保护后端真实的服务器。 3. 缓存应该如何设计 4.

    1. Redis缓存

    Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
    它是基于高性能的Key-Value、并提供多种语言的 API的非关系型数据库。不过与传统数据库不同的是 redis 的数据是存在内存中的,所以存写速度非常快。
    它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets)

    2. 为什么需要使用缓存

    说明:使用缓存可以有效的降低用户访问物理设备的频次,有效的减少并发的压力。保护后端真实的服务器。
    主要从“高性能”和“高并发”两点来看:

    2.1高性能

    在这里插入图片描述

    2.2高并发

    直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
    在这里插入图片描述

    3.Redis的架构模式

    3.1 主从模式

    Redis的复制功能允许用户根据一个Redis服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
    特点:
    1、master/slave 角色
    2、master/slave 数据相同
    3、降低 master 读压力在转交从库

    问题:
    1、无法保证高可用
    2、没有解决 master 写的压力
    在这里插入图片描述

    3.2 哨兵模式

    Redis sentinel 是一个分布式系统中监控Redis主从服务器,并在主服务器下线时自动进行故障转移。
    它具备三个特征:监控、提醒、自动故障迁移。
    特点:
    1、保证高可用
    2、监控各个节点
    3、自动故障迁移

    缺点:
    1、主从模式,切换需要时间丢数据
    2、没有解决 master 写的压力
    在这里插入图片描述

    3.3 分片模式

    虽然redis可以扩展内存空间的大小。但是如果需要存储海量的数据一味的扩大内存,其实效率不高。
    分片介绍: 准备多台redis,共同为用户提供缓存服务。在保证效率的前提下,实现了内存的扩容。用户在使用分片机制时,将多台redis当做1台使用。
    **1.那么数据是依据什么算法存储到Redis分片缓存中的呢?
    一致性哈希算法(下文介绍)
    **2.关于redis分片总结
    1)当redis节点宕机之后,用户访问必然受到影响,该节点中的数据可能丢失
    2)redis分片可以实现内存数据的扩容
    3)redis分片机制中hash运算发生在业务服务器中,redis只负责存取,不负责计算,所以效率高。

    3.4 集群模式

    从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
    特点:
    1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
    2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
    3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
    4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
    5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。

    缺点:
    1、资源隔离性较差,容易出现相互影响的情况。
    2、数据通过异步复制,不保证数据的强一致性
    在这里插入图片描述

    4.一致性Hash算法介绍

    一致性哈希算法在1997年由麻省理工学院提出,是一种特殊的哈希算法,目的是解决分布式缓存的问题。在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系。一致性哈希解决了简单哈希算法在分布式哈希表( Distributed Hash Table,DHT) 中存在的动态伸缩等问题 。

    4.1 一致性Hash原理

    目的:解决数据如何在分布式环境下进行存储
    hash取值区间:8位16进制数 共有2^32种可能性
    1)数据如何存储
    在这里插入图片描述
    2)当节点发生变化时,节点中的对应的数据可以动态的迁移。
    原则:当发生了节点变化时,应该尽可能小的影响其他节点。
    在这里插入图片描述

    4.2 一致性Hash特征

    一致性哈希算法是在哈希算法基础上提出的,在动态变化的分布式环境中,哈希算法应该满足的几个条件:
    平衡性(均衡性)是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题。利用虚拟节点实现数据平衡(相对而已的平均)
    单调性是指在新增或者删除节点时,不影响系统正常运行,可以实现动态的数据迁移。
    分散性是指数据应该分散地存放在分布式集群中的各个节点,不必每个节点都存储所有数据。

    5.Hash槽算法介绍

    RedisCluster采用此分区,所有的键根据哈希函数(CRC16[key]%16384)映射到0 - 16383槽内,工16384个槽位,每个节点维护部分槽及槽所映射的键值数据。根据主节点的个数,均衡划分区间。
    哈希函数:Hash()=CRC16[key]%16384
    如图所示:
    在这里插入图片描述
    当redis集群中插入数据时,首先将key进行计算,之后将计算结果匹配到具体的某一个槽的区间内,之后再将数据set到管理该槽的节点中。
    如图所示:
    在这里插入图片描述

    6.Redis的持久化策略

    Redis的数据都保存在内存中,如果断电或者宕机,则内存数据将擦除,导致数据的丢失。为了防止数据丢失,Redis内部有持久化机制。
    当第一次Redis服务启动时,根据配置文件中的持久化要求,进行持久化操作。如果不是第一次启动,则在服务启动时会根据持久化文件的配置,读取指定的持久化文件,实现内存数据的恢复。

    6.1 RDB模式

    特点:
    1.rdb模式是redis中默认的持久化策略。
    2.rdb模式定期持久化。保存的是Redis中的内存数据快照,持久化文件占用空间较小。
    3.rdb模式可能导致内存数据丢失

    前提:需要在redis的客户端中执行.
    1.save 命令 立即持久化,会导致其他操作陷入阻塞.
    2.bgsave 命令 开启后台运行。 以异步的方式进行持久化,不会造成其他操作的阻塞。

    6.2 AOF模式

    特点:
    1.AOF模式默认条件下是关闭状态.。如果需要开启则需要修改配置文件。
    2.AOF模式可以实现数据的实时持久化操作,AOF模式记录的是用户的操作过程
    3.只要开启了AOF模式,则持久化方式以AOF模式为主。

    6.3 持久化总结

    1.如果用户允许少量的数据丢失,则可以选用RDB模式. 效率更高
    2.如果用户不允许数据丢失,则选用AOF模式.
    3.可以2种方式都选, 需要搭建组从结构 , 主机选用RDB模式, 从机选用AOF模式,可以保证业务允许.

    7.Redis的内存策略

    redis服务器运行在内存中,数据也在内存中保存。 如果一直往里存,总有一天内存资源不够用,所以需要研究如何优化内存。

    7.1 LRU算法

    维度:时间
    LRU(Least Recently Used)即最近最少使用,是一种常见的页面(数据)置换算法,选择最近最久未使用的页面(数据)予以淘汰。该算法赋予每个页面(数据)一个访问字段,用来记录一个页面(数据)自上次被访问以来所经历的时间t,当必须淘汰一个页面(数据)时,选择现有页面(数据)中其t值最大的,即最近最少使用的页面(数据)予以淘汰。

    7.2 LFU算法

    维度:引用次数
    LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。

    7.3 随机算法

    灭霸响指,随机删除页面(数据)

    7.4 TTL算法

    将设定了超时时间的数据提前删除

    7.5 Redis中内存优化策略

    • volatile-lru 设定超时时间的数据采用lru算法
    • allkeys-lru .所有的数据采用lru算法
    • volatile-lfu设定超时时间的数据采用LFU算法 .
    • allkeys-lfu 所有的数据才能lfu算法
    • volatile-random设定了超时时间的数据采用随机算法
    • allkeys-random 所有数据采用随机算法
    • volatile-ttl设定超时时间的数据采用TTL算法
    • noeviction 该配置为模式配置 表示内存满时 只报错,不删除数据.

    8.Redis的数据类型及其应用场景

    一)String 常用命令: set,get,decr,incr,mget 等。
    最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。

    二)hash 常用命令: hget,hset,hgetall 等。
    这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。

    三)list 常用命令: lpush,rpush,lpop,rpop,lrange等
    使用list的数据结构,可以做简单的消息队列的功能。另外一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。

    四)set 常用命令: sadd,spop,smembers,sunion 等
    set堆放的是一堆不重复值的集合,所以可以做全局去重的功能。

    五)sorted set 常用命令: zadd,zrange,zrem,zcard等
    多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOPN操作。

    9. Redis为什么是单线程的

    官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)Redis利用队列技术将并发访问变为串行访问

    单线程的redis为什么这么快?
    1)绝大部分请求是纯粹的内存操作(非常快速)
    2)采用单线程,避免了不必要的上下文切换和竞争条件
    3)非阻塞IO优点:

    • 1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
    • 2.支持丰富数据类型,支持string,list,set,sorted set,hash
    • 3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
    • 4.丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发竞争key问题
    展开全文
  • mybatis+redis缓存配置

    2016-09-22 14:52:25
    springmvc整合Mybatis,Redis;实现将查询的数据进行二级缓存处理
  • 如何使用 Redis 缓存

    千次阅读 2022-04-20 21:39:05
    对于 Redis 来讲,作为缓存使用,是我们在业务中经常使用的,这里总结下,Redis 作为缓存在业务中的使用。 旁路缓存 Cache Aside(旁路缓存)策略以数据库中的数据为准,缓存中的数据是按需加载的。它可以分为读...

    编辑

    添加图片注释,不超过 140 字(可选)

    如何使用 Redis 缓存

    前言

    对于 Redis 来讲,作为缓存使用,是我们在业务中经常使用的,这里总结下,Redis 作为缓存在业务中的使用。

    旁路缓存

    Cache Aside(旁路缓存)策略以数据库中的数据为准,缓存中的数据是按需加载的。它可以分为读策略和写策略。

    只读缓存

    只读缓存 从缓存中读取数据;如果缓存命中,则直接返回数据;如果缓存不命中,则从数据库中查询数据;查询到数据后,将数据写入到缓存中,并且返回给用户。

    如果需要对数据进行修改的时候,直接修改数据库中的数据,然后删除缓存中的旧数据。

    只读缓存的优点:

    所有最新的数据都在数据库中,数据不存在丢失的风险。

    缺点:

    每次修改数据,都会删除缓冲,之后的请求会发生一次缓存缺失。

    读写缓存

    除了进行读操作外,数据的修改操作也会发送到缓存中,直接在缓存中对数据进行修改。此时,得益于Redis的高性能访问特性,数据的增删改操作可以在缓存中快速完成,处理结果也会快速返回给业务应用,这就可以提升业务应用的响应速度。

    当然 Redis 是内存数据库,一旦掉电或宕机,内存中的数据就有可能存在丢失。

    针对这种情况,一般会有两种回写策略:

    • 1、同步回写;

    写请求发给缓存的同时,也会发给后端数据库进行处理,等到缓存和数据库都写完数据,才给客户端返回。这样,即使缓存宕机或发生故障,最新的数据仍然保存在数据库中,这就提供了数据可靠性保证。

    不过,同步直写会降低缓存的访问性能。这是因为缓存中处理写请求的速度是很快的,而数据库处理写请求的速度较慢。即使缓存很快地处理了写请求,也需要等待数据库处理完所有的写请求,才能给应用返回结果,这就增加了缓存的响应延迟。

    • 2、异步回写。

    所有写请求都先在缓存中处理。可以定时将缓存写入到内存中,然后等到这些增改的数据要被从缓存中淘汰出来时,再次将它们写回后端数据库。这样一来,处理这些数据的操作是在缓存中进行的,很快就能完成。只不过,如果发生了掉电,而它们还没有被写回数据库,就会有丢失的风险了。

    优点:

    被修改的数据永远在缓存中,不会发生缓存缺失,下次可以直接访问,不在需要向数据库中进行一次查询。

    缺点:

    数据可能存在丢失的风险。

    设置多大的缓存合适

    缓存能够提高响应速度,但是缓存的数量也不是越多越好?

    1、大容量缓存是能带来性能加速的收益,但是成本也会更高;

    2、在一些场景中,比如秒杀,少量的缓存承担的就是绝大部分的流量访问。

    系统的设计选择是一个权衡的过程:大容量缓存是能带来性能加速的收益,但是成本也会更高,而小容量缓存不一定就起不到加速访问的效果。一般来说,建议把缓存容量设置为总数据量的15%到30%,兼顾访问性能和内存空间开销。

    内存被写满了如何处理

    Redis 中的内存被写满了,就会触发内存淘汰机制了

    具体参加内存淘汰机制

    缓存经常遇到的问题

    Redis 作为缓存,经常遇到的几种情况:缓存中的数据和数据库中的不一致;缓存雪崩;缓存击穿和缓存穿透。

    下面一一来探讨下

    1、缓存中的数据和数据库中的不一致

    数据一致性,通俗的理解就是,数据库中的数据和缓冲中的数据完全一致就满足一致性。不过对于只读缓存,如果缓冲中没有就去数据库中查询,这样如果缓存中没有数据,但是数据库中的数据是最新的,最终也能满足数据一致性。

    所以总结下,一致性大致分成下面的两种情况:

    1、缓存中有数据,缓存中的数据和数据库中的数据一样;

    2、缓存中没有数据,数据库中记录了最新的数据。

    下面分析下只读缓存和读写缓存中的数据不一致情况

    读写缓存

    读写缓存有同步写回和异步写回两种策略

    同步写回:缓存在新增修改的时候,也会同步数据到数据库中,这样总能保持缓存中的数据和数据库中的一致;

    异步写回:缓存新增修改时候,先不写回到数据库中,定时或者缓存中数据淘汰的时候,再写回到数据库中。这种,如果 Redis 故障宕机了,没有及时写回数据到数据库中,就会造成数据的不一致。

    对于读写缓存,使用同步写回的策略,能保证数据数据的一致性。不过,需要在业务应用中使用事务机制,来保证缓存和数据库的更新具有原子性,也就是说,两者要不一起更新,要不都不更新,返回错误信息,进行重试。否则,我们就无法实现同步直写。

    如果系统没宕机,redis 系统正常的情况下,因为读写缓存,缓存中的数据是一直存在的,所以当修改数据的时候先修改缓存中的数据,这样就算并发很大的情况下,因为缓存中的数据都是最新的,并且一直存在,这样数据总能读取到最新的数据。

    只读缓存

    只读缓存,如果数据新增,直接写入到数据库中,如果有数据修改删除,也是直接操作数据库不过缓存中的数据不会更新,而是直接删除缓存中的数据。

    这样数据的更新操作之后,数据库中的数据总是最新的,缓存中就会发生缓存缺失,此时就会从数据库中读取数据,然后再加载到缓存中,这样缓存中的数据总能和数据库中的数据一致。

    只读缓存在数据新增的时候,缓存中是没有数据的,所以肯定是要从数据库中加载,这种情况不存在数据不一致的情况。

    在只读缓存中,数据不一致的情况,发生在数据的更新删除操作中,下面来一一分析下

    删改操作既要修改数据库,同时还要删除对应的缓存,如果这两个操作的原子性无法得到保证,(一起操作成功,或者一起操作失败),那么数据的一致性就得不到保证了。

    来个异常的栗子

    1、先修改数据库,然后删除缓存,但是删除缓存失败了;

    删除缓存失败了,那么缓存中存在的就是旧值,这时候用户的请求过来了,首先去缓存中查询,这时候拿到的就是老旧的数据。

    2、先删除缓存,在修改数据库,修改数据库失败了;

    缓存删除成功,数据库修改失败了,那么数据库中存在的就是旧值,因为缓存已经被删除了,这时候去缓存中查询,发生了缓存的缺失,数据就会从数据库中加载到缓存中,这时候读取到也是老旧的数据。

    针对这种问题如何解决呢?

    上面出现异常的两种场景,归根到底,就是两者操作的原子性没有得到保证,所以可以借助于消息队列实现最终的一致性。

    使用 mq 解决分布式事务可参见分布式事务

    这里的操作场景相对简单一点,只要借助于 mq 的重试机制,保证第二步的操成功就可以了。

    栗如:

    1、先修改数据库;

    2、发送删除缓存的消息到 mq 中;

    3、下游收到删除的消息,操作删除缓存,如果失败,借助于 mq 的重试机制,就能进行重试操作,直到成功。当然如果,重试多次还是失败,我们需要记录错误原因,然后通知业务方。

    编辑

    添加图片注释,不超过 140 字(可选)

    那到底应该先删除缓存还是先修改数据库呢?这里我们再探讨一下

    1、先删除缓存后修改数据库

    先删除缓存,然后修改数据库

    如果数据库的更新有延迟,那么这时候一个线程过来查询该数据,因为缓存中已经删除了,这时候发生了缓存的缺失,然后就回去数据库中查询,数据库可能还没有更新成功,就可能获取到旧值。

    如何解决呢

    使用 延迟双删 策略

    当数据库被修改之后,线程 sleep 一段时间,然后再次删除缓存,然缓存发生一次缺失,这样下次的请求,就能把数据库中最新的数据加载到缓存中。

    编辑

    添加图片注释,不超过 140 字(可选)

    比如上面的这种情况,因为数据库的更新可能存在延迟,所以时候线程2读取到了数据库的旧值,然后加载到了缓存中,这样接下来的所有的查询就都会读取旧值

    所以 线程1,通过延迟双删来处理这种情况

    线程1,在 sleep 一段时间之后,删除缓存,这样就能使后续的缓存缺失,后续的查询就能加载数据库中最新的数据到缓存中。

    不过 sleep 的时间需要大于,线程2,读数据并且写入数据到内存的时间,如果 sleep 时间过小,这时候线程2,的旧值还没有写入到缓存中,线程1,已经再次删除了缓存,然后这时候线程2把旧值写入,导致缓存中依然是旧数据。

     
    
     
    

    redis.delKey(X) db.update(X) Thread.sleep(N) redis.delKey(X)

    当然,这在 sleep 的时间内,还是有一部分请求会读取到旧值

    2、先修改数据库然后删除缓存

    先修改数据库,然后删除缓存

    如果缓存删除有延迟,那么这时候过来的请求,就会读取到缓存中老旧的数据,不过缓存会马上被删除,只会有少部分的数据读取到老旧的数据,对业务影响比较小。

    经过对比,发现先修改数据库然后在删除缓存,对我们业务的影响比较小,同时也跟容易处理。

    只读缓存和读写缓存如何选择

    读写缓存对比只读缓存

    优点:缓存中一直会有数据,如果更新操作后会立即再次访问,可以直接命中缓存,能够降低读请求对于数据库的压力。

    缺点:如果更新后的数据,之后很少再被访问到,会导致缓存中保留的不是最热的数据,缓存利用率不高(只读缓存中保留的都是热数据)。

    所以读写缓存比较适合用于读写相当的业务场景。

    2、缓存雪崩

    什么是缓存雪崩

    缓存雪崩是指大量的应用请求无法在Redis缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。

    缓存雪崩有两种场景

    1、大量缓存同时过期

    如果有大量的缓存 key 设置了同样的过期时间,如果这些缓存 key 过期了,同时有大量的请求,进来了,这些请求就会直接打到数据库中,数据库可能因为这些请求,导致数据库压力增大,严重的时候数据库宕机。

    如何解决呢?

    1、避免给大量的过期键设置相同的过期时间,设计过期时间的时候,可以考虑加入一个业务上允许的过期随机值;

    2、服务降级,只有部分核心业务的请求,才会流转到数据库中,数据库的压力就会被大大减轻了;

    • 当业务应用访问的是非核心数据(例如电商商品属性)时,暂时停止从缓存中查询这些数据,而是直接返回预定义信息、空值或是错误信息;

    • 当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。

    2、Redis 实例发生宕机

    Redis 实例的宕机,缓存层就不能处理数据,最总流量都会流入到数据库中

    如何解决呢?

    1、业务中实现服务熔断或者请求限流机制;

    • 服务熔断:如果监听到发生了缓存雪崩,直接暂停对缓存服务的请求,但是这种对业务的影响比较大;

    • 服务限流:可以在入口做限流,不要让所有的请求都流入到后端的服务中;

    2、提前预防,搭建 Redis 的高可用集群;

    • 尝试构建 Redis 的高可用集群,比如当某主节点挂掉了,集群能够马上重新选出新的主节点。例如哨兵机制

    3、缓存击穿

    其实跟缓存雪崩有点类似,缓存雪崩是大规模的key失效,而缓存击穿是一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增。这种现象就叫做缓存击穿。

    如何解决?

    对于热点 key 可以不设置过期时间,或者设置一个超过使用周期的过期时间,保证这个 key 在业务使用期间永远存在。

    4、缓存穿透

    如果业务请求的缓存,既不在缓存中,也不再数据库中,那么缓存将没有用,所有的请求都会流入到数据库中。

    那么,缓存穿透会发生在什么时候呢?一般来说,有两种情况。

    1、业务层误操作:缓存中的数据和数据库中的数据被误删除了,所以缓存和数据库中都没有数据;

    2、恶意攻击:专门访问数据库中没有的数据。

    如何解决?

    1、缓存空值或缺省值;

    一旦发生缓存穿透,在缓存中写入一个业务中允许的空值,这样缓存中有数据了,就避免了缓存穿透。

    2、使用布隆过滤器;

    使用布隆过滤器判断下数据是否存在,数据如果不存在,就不向数据库发起请求了。

    布隆过滤器

    3、在请求入口的前端进行请求检测;

    缓存穿透的一个原因是有大量的恶意请求访问不存在的数据,所以,一个有效的应对方案是在请求入口前端,对业务系统接收到的请求进行合法性检测,把恶意的请求(例如请求参数不合理、请求参数是非法值、请求字段不存在)直接过滤掉,不让它们访问后端缓存和数据库。这样一来,也就不会出现缓存穿透问题了。

    缓存中的 hot key 和 big key

    这两种的处理方式可参见之前发的文章。

    总结

    对于缓存的使用,我们经常用到的有两种1、只读缓存;2、读写缓存;

    只读缓存,对比读写缓存

    优点:缓存中一直会有数据,如果更新操作后会立即再次访问,可以直接命中缓存,能够降低读请求对于数据库的压力。

    缺点:如果更新后的数据,之后很少再被访问到,会导致缓存中保留的不是最热的数据,缓存利用率不高(只读缓存中保留的都是热数据)。

    所以读写缓存比较适合用于读写相当的业务场景。

    缓存在使用的过程中,会面临缓存中的数据和数据库中的不一致;缓存雪崩;缓存击穿和缓存穿透,这些我们需要弄明白这些情况发生的额场景,然后再业务中一一去避免。
     

    获取方式:需要这 《阿里内部Redis学习笔记》 请关注 后点击这获取完整下载路径方式

    展开全文
  • redis缓存java类使用实例,针对map.list,set等使用详细的实例!
  • SpringBoot配置Redis缓存

    千次阅读 2022-04-29 13:00:46
    SpringBoot配置Redis缓存
  • redis缓存雪崩,redis缓存穿透,redis缓存击穿 redis应对高并发造成的雪崩、穿透、击穿
  • redis 缓存穿透,缓存击穿,缓存雪崩

    千次阅读 多人点赞 2022-04-19 15:55:51
    缓存穿透,缓存击穿,缓存雪崩
  • 添加redis缓存2.1 缓存商铺信息3. 缓存更新策略3.1 缓存更新策略方法3.2 主动更新策略3.3 缓存更新策略总结4. 对商铺查询的缓存添加超时剔除和主动更新4.1 超时剔除4.2 主动更新 1. 什么是缓存 缓存就是数据交换的...
  • Redis缓存穿透

    千次阅读 2022-05-18 19:47:57
    (1)正常的缓存穿透使用场景是,所有的查询请求先经过缓存,当缓存命中后,直接返回缓存中的数据;在缓存未命中的清空下,去数据库查询数据,并写入缓存缓存的目的是为了尽可能将请求在缓存层处理,避免大量的请求进入缓存...
  • redis缓存

    2018-12-26 17:08:33
    redis
  • SpringBoot 使用Redis缓存

    千次阅读 2021-09-09 16:02:39
    SpringBoot 使用Redis缓存 1. 添加依赖 <!-- redis依赖包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</...
  • Spring自定义注解实现redis缓存

    万次阅读 2021-10-13 09:56:09
    从而酿成事故,因此成熟的公司都会通过封装基础组件,实现通过注解自动添加redis缓存,本文会从原理出发,带领大家亲自实现自定义注解,完成redis缓存的开发,学会了,你可以在同事面前秀一把了。 二、自定义注解的...
  • redis缓存实例

    2017-04-13 10:39:12
    Spring集成Jedis缓存数据库及jedisUtils工具类,带有windows客户端工具+linux部署,可视化key-value缓存数据库(入门特别合适)
  • 缓存就是数据交换的缓冲区(称作Cache),是存贮数据的临时地方,一般读写性能较高。
  • SpringBoot使用Redis缓存MySql

    千次阅读 2022-02-21 10:40:49
    文章目录1 项目组成2 运行springboot3 访问mysql4 设置redis缓存 1 项目组成 应用:springboot rest api 数据库:mysql jdbc框架:jpa 缓存中间件:redis 2 运行springboot 2.1 官网download最基本的restful应用...
  • redis 缓存测试

    2021-12-24 11:22:34
    redis 缓存测试redis 缓存测试redis介绍redis测试点redis查看工具redis...使用redis做缓存中间件,来对redis缓存做增删改查 redis测试点 对数据缓存进行增删改查 redis数据生效,增删改查是否正常 做增删改查操作时,
  • VB6.0也能轻松访问Redis缓存了。 由fj543原创基于mswinsck控件封装成类模块。简单方便地实现Redis的变量读写、队列读写等操作。
  • redis缓存demo

    2017-02-06 16:59:41
    redis缓存demo

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 334,501
精华内容 133,800
关键字:

redis缓存

友情链接: fpc.c.2.html.zip