如何使用 Redis 缓存
如何使用 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 的重试机制,就能进行重试操作,直到成功。当然如果,重试多次还是失败,我们需要记录错误原因,然后通知业务方。
那到底应该先删除缓存还是先修改数据库呢?这里我们再探讨一下
1、先删除缓存后修改数据库
先删除缓存,然后修改数据库
如果数据库的更新有延迟,那么这时候一个线程过来查询该数据,因为缓存中已经删除了,这时候发生了缓存的缺失,然后就回去数据库中查询,数据库可能还没有更新成功,就可能获取到旧值。
如何解决呢
使用 延迟双删 策略
当数据库被修改之后,线程 sleep 一段时间,然后再次删除缓存,然缓存发生一次缺失,这样下次的请求,就能把数据库中最新的数据加载到缓存中。
比如上面的这种情况,因为数据库的更新可能存在延迟,所以时候线程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核心技术与实战】https://time.geekbang.org/column/intro/100056701
【Redis设计与实现】https://book.douban.com/subject/25900156/
【什么是缓存雪崩、缓存击穿、缓存穿透】https://zhuanlan.zhihu.com/p/346651831
【详解布隆过滤器的原理,使用场景和注意事项】https://zhuanlan.zhihu.com/p/43263751
【Redis 学习笔记】https://github.com/boilingfrog/Go-POINT/tree/master/redis
【如何使用Redis缓存】https://boilingfrog.github.io/2022/04/20/Redis中缓存如何使用/
如何使用 Redis 缓存的更多相关文章
- 缓存工厂之Redis缓存
这几天没有按照计划分享技术博文,主要是去医院了,这里一想到在医院经历的种种,我真的有话要说:医院里的医务人员曾经被吹捧为美丽+和蔼+可亲的天使,在经受5天左右相互接触后不得不让感慨:遇见的有些人员在挂 ...
- Windows下Redis缓存服务器的使用 .NET StackExchange.Redis Redis Desktop Manager
Redis缓存服务器是一款key/value数据库,读110000次/s,写81000次/s,因为是内存操作所以速度飞快,常见用法是存用户token.短信验证码等 官网显示Redis本身并没有Wind ...
- 总结:如何使用redis缓存加索引处理数据库百万级并发
前言:事先说明:在实际应用中这种做法设计需要各位读者自己设计,本文只提供一种思想.准备工作:安装后本地数redis服务器,使用mysql数据库,事先插入1000万条数据,可以参考我之前的文章插入数据, ...
- .NET基于Redis缓存实现单点登录SSO的解决方案[转]
一.基本概念 最近公司的多个业务系统要统一整合使用同一个登录,这就是我们耳熟能详的单点登录,现在就NET基于Redis缓存实现单点登录做一个简单的分享. 单点登录(Single Sign On),简称 ...
- Redis缓存连接池管理
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.util.Assert;import ...
- ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存
基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...
- Windows Azure Redis 缓存服务
8月20日,Windows Azure (中国版)开始提供Redis缓存服务,比较国际版的Microsoft Azure晚了差不多一年的时间.说实话,微软真不应该将这个重要的功能delay这么长时间, ...
- .NET基于Redis缓存实现单点登录SSO的解决方案
一.基本概念 最近公司的多个业务系统要统一整合使用同一个登录,这就是我们耳熟能详的单点登录,现在就NET基于Redis缓存实现单点登录做一个简单的分享. 单点登录(Single Sign On),简称 ...
- spring boot redis缓存JedisPool使用
spring boot redis缓存JedisPool使用 添加依赖pom.xml中添加如下依赖 <!-- Spring Boot Redis --> <dependency> ...
- spring aop搭建redis缓存
SpringAOP与Redis搭建缓存 近期项目查询数据库太慢,持久层也没有开启二级缓存,现希望采用Redis作为缓存.为了不改写原来代码,在此采用AOP+Redis实现. 目前由于项目需要,只需要做 ...
随机推荐
- TCP和UDP协议?
TCP(Transmission Control Protocol:传输控制协议:面向连接,可靠传输 UDP(User Datagram Protocol):用户数据报协议:面向无连接,不可靠传输
- String s = new String("xyz");创建了几个String Object?二者之间有什么区别?
两个或一个,"xyz"对应一个对象,这个对象放在字符串常量缓冲区,常量"xyz"不管出现多少遍,都是缓冲区中的那一个.New String每写一遍,就创建一个新 ...
- 初识Spring(为什么要使用Spring?)
Spring,英文翻译是春天的意思,而在Java中,是一个开放源代码的设计层面框架(手动滑稽,程序员的春天),他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.S ...
- 客户端回调 Watcher?
客户端 SendThread 线程接收事件通知,交由 EventThread 线程回调 Watcher. 客户端的 Watcher 机制同样是一次性的,一旦被触发后,该 Watcher 就失效了.
- linux java7升级到java8
转自:https://blog.csdn.net/u010199866/article/details/81744382 linux java7升级到java8 版权 1.第一步先卸载所有老的jd ...
- 学习zabbix(八)
一,Zabbix架构 zabbix 是一个基于 WEB 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案.zabbix 能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制 ...
- 学习Docker(一)
一.docker介绍 docker是半虚拟化,比完全虚拟化性能高,可以使用物理机性能100% Docker 镜像(Images): 用于创建 Docker 容器的模板 Docker 容器(Contai ...
- 学习ELK日志平台(三)
ELK(elasticsearch.logstash.kibana) Elastic Stack是原ELK Stack在5.0版本加入Beats套件后的新称呼 解决痛点: 开发人员不能登录线上serv ...
- 学习openstack(五)
OpenStackOpenStack介绍OpenStack是一种免费的开源平台,帮助服务提供商实现类似于亚马逊EC2和S3的基础设施服务.OpenStack当前有三个核心项目:计算(Nova),对象存 ...
- mybatis-03-一对多关系映射(附源码)
sb_mybatis /* Navicat MySQL Data Transfer Source Server : 阿里云 Source Server Version : 50724 Source H ...