27.【学习心得】学习心得-如何更新redis缓存

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【学习心得】学习心得-如何更新redis缓存

文档参考:书名:《从程序员到架构师:大数据量、缓存、高并发、微服务、多团队协同等核心场景实战》-王伟杰

image.png

前文如下:


23.【学习心得】学习心得-冷热分离概述

24.【学习心得】学习心得-如何分离冷热数据

25.【学习心得】学习心得-基于MySQL的分表分库

26.【学习心得】学习心得-读缓存


1.如何更新缓存


更新缓存的步骤特别简单,共两步:更新数据库和更新缓存。但这简单的两步中需要考虑很多问题。

1)先更新数据库还是先更新缓存?更新缓存时先删除还是直接更新?

2)假设第一步成功了,第二步失败了怎么办?

3)假设两个线程同时更新同一个数据,A线程先完成第一步,B线程先完成第二步怎么办?


1.1 组合1:先更新缓存,再更新数据库


对于这个组合,会遇到这种情况:假设第二步更新数据库失败了,要求回滚缓存的更新,这时该怎么办呢?Redis不支持事务回滚,除非采用手工回滚的方式,先保存原有数据,然后再将缓存更新回原来的数据,这种解决方案有些缺陷。


1.1.1 缺陷


1)原来缓存中的值是a,两个线程同时更新库存。

2)线程A将缓存中的值更新成b,且保存了原来的值a,然后更新数据库。

3)线程B将缓存中的值更新成c,且保存了原来的值b,然后更新数据库。

4)线程A更新数据库时失败了,它必须回滚,那现在缓存中的值更新成什么呢?理论上应该更新成c,因为数据库中的值是c,但是,线程A里面无从获得c这个值。

image.png

缓存=a,数据库=a

线程A修改缓存为b,修改成功,缓存=b,本地原值=a,数据库=a

线程B修改缓存为c,修改成功,缓存=c,本地原值=b,数据库=a

线程B将数据库修改为c,修改成功,缓存=c,本地原值=b,数据库=c

线程A将数据库修改为b,修改失败,数据库回滚,数据库=c,本地原值=a,缓存回滚=原值a


1.1.2 总结


线程A更新,B线程更新:A线程先修改缓存,然后B线程快,把数据库和缓存都更新了;A更新数据库失败回滚,数据库回滚到B线程修改的值,缓存回滚到A线程原值,导致数据不一致


1.2 组合2:先删除缓存,再更新数据库


使用这种方案,即使更新数据库失败了也不需要回滚缓存。这种做法虽然巧妙规避了失败回滚的问题,却引出了两个更大的问题。


1.2.1 缺陷

1)假设线程A先删除缓存,再更新数据库。在线程A完成更新数据库之前,后执行的线程B反而超前完成了操作,读取Key发现没有数据后,将数据库中的旧值存放到了缓存中。线程A在线程B都完成后再更新数据库,这样就会出现缓存(旧值)与数据库的值(新值)不一致的问题

2)为了解决一致性问题,可以让线程A给Key加锁,也就是删除redis和更新库放在同一个事务操作里面,因为写操作特别耗时,这种处理方法会导致大量的读请求卡在锁中。

image.png


缓存=a,数据库=a

线程A删除缓存,删除成功,缓存=null,数据库=a

线程B读取缓存为null,读取数据库值=a更新到缓存,缓存=a,数据库=a

线程A将数据库修改为b,修改成功,缓存=a,数据库=b


1.2.2 总结


线程A更新,线程B读取:A线程删完后,数据库还没来得及更新;B线程又把数据库旧值更新到redis里,然后A线程更新数据库成功,导致这样缓存(旧值)与数据库的值(新值)不一致的问题


1.3 组合3:先更新数据库,再更新缓存


对于组合3,同样需要考虑两个问题。


1.3.1 缺陷


1)假设第一步(更新数据库)成功,第二步(更新缓存)失败了怎么办?因为缓存不是主流程,数据库才是,所以不会因为更新缓存失败而回滚第一步对数据库的更新。此时一般采取的做法是重试机制,但重试机制如果存在延时还是会出现数据库与缓存不一致的情况,不好处理。


image.png


缓存=a,数据库=a

线程A修改数据库为b,修改成功,缓存=a,数据库=b

线程A将缓存修改为b,修改失败,缓存=a,数据库=b

2)假设两个线程同时更新同一个数据,线程A先完成了第一步,线程B先完成了第二步怎么办?线程A把值更新成a,线程B把值更新成b,此时数据库中的最新值是b,因为线程A先完成了第一步,后完成第二步,所以缓存中的最新值是a,数据库与缓存的值还是不一致,这个逻辑还是有问题的。因此,也不建议采用这个组合。


image.png


缓存=a,数据库=a

线程A修改数据库为b,修改成功,缓存=a,数据库=b

线程B修改数据库为c,修改成功,缓存=a,数据库=c

线程B将缓存修改为c,修改成功,缓存=c,数据库=c

线程B将缓存修改为b,修改成功,缓存=b,数据库=c


1.3.1 总结


场景1:线程A更新,B线程读取:A线程数据库更新成功,缓存更新失败,数据不一致,可以增加重试。但是重试过程中线程B读到旧的缓存数据

场景2:线程A更新,B线程更新:A线程数据库更新成功,然后B线程数据库和缓存先更新完;然后A线程才更新成功缓存;导致缓存存的A线程修改值,数据库存的B线程修改值,数据不一致


1.4 组合4:先更新数据库,再删除缓存


针对组合4,先看看它能不能解决组合3的第二个问题。假设两个线程同时更新同一个数据,线程A先完成第一步,线程B先完成第二步怎么办?线程A把值更新成b,线程B把值更新成c,此时数据库中的最新值是c,因为线程A先完成了第一步,所以第二步谁先完成已经不重要了,因为都是直接删除缓存数据。这个问题解决了。


image.png


缓存=a,数据库=a

线程A修改数据库为b,修改成功,缓存=a,数据库=b

线程B修改数据库为c,修改成功,缓存=a,数据库=c

线程B将缓存删除,删除成功,缓存=null,数据库=c

线程A将缓存删除,删除成功,缓存=null,数据库=c


那么,它能解决组合3的第一个问题吗?假设第一步成功,第二步失败了怎么办?这种情况的出现概率与组合3相比明显低不少,因为删除比更新容易多了。虽然这个组合方案不完美,但出现一致性问题的概率较低。除了组合3会碰到的问题,组合4还会碰到别的问题吗?


是的。假设线程A要更新数据,先完成第一步更新数据库,在线程A删除缓存之前,线程B要访问缓存,那么取得的就是旧数据。这是一个小小的缺陷。


image.png


缓存=a,数据库=a

线程A修改数据库为b,修改成功,缓存=a,数据库=b

线程B读取缓存,读到缓存=a,数据库=b

线程A将缓存删除,删除成功,缓存=null,数据库=b


1.4.2 总结


场景1:线程A更新,B线程读取:A线程数据库更新成功,缓存删除不是同一个事务中(失败概率较低),B线程会读数据库最新的值到缓存;但是A线程数据库更新和缓存删除不是同一个事务中,B线程读到的缓存和数据库不一致,此场景概率较小


场景2:线程A更新,B线程更新:最终缓存都是null,其他线程读取都得读一次库再更新到缓存。

那么,以上问题有办法解决吗?


1.5 组合5:先删除缓存,更新数据库,再删除缓存(延迟双删)


还有一个方案,就是先删除缓存,再更新数据库,再删除缓存。这个方案其实和先更新数据库,再删除缓存差不多,因为还是会出现类似的问题:假设线程A要更新数据库,先删除了缓存,这一瞬间线程B要读缓存,先把数据迁移到缓存;然后线程A完成了更新数据库的操作,这一瞬间线程C也要访问缓存,此时它访问到的就是线程C放到缓存里面的旧数据


image.png



缓存=a,数据库=a

线程A删除缓存,删除成功,缓存=null,数据库=a

线程B读取缓存为null,读取数据库值=a更新到缓存,缓存=a,数据库=a

线程A将数据库修改为b,修改成功,缓存=a,数据库=b

线程C读取缓存,读取成功,缓存=a,数据库=b


不过组合5出现类似问题的概率更低,因为要刚好有3个线程配合才会出现问题(比先更新数据库,再删除缓存的方案多了一个需要配合的线程)。但是相比于组合4,组合5规避了第二步删除缓存失败的问题——组合5是先删除缓存,再更新数据库,假设它的第三步“再删除缓存”失败了,也没关系,因为缓存已经删除了。 其实没有一个组合是完美的,它们都有读到脏数据(这里指旧数据)的可能性,只不过概率不同。根据以上分析,组合5相对来说是比较好的选择。


1.5.1 缺陷


不过这个组合也有一些问题要考虑,具体如下。


1)删除缓存数据后变相出现缓存击穿,此时该怎么办?

2)删除缓存失败如何重试?这个重试可以做得复杂一点,也可以做得简单一点。简单一点就是使用try…catch…,假设删除缓存失败了,在catch里面重试一次即可;复杂一点就是使用一个异步线程不断重试,甚至用到MQ。不过这里没有必要大动干戈。而且异步重试的延时大,会带来更多的读脏数据的可能性。所以仅仅同步重试一次就可以了。

3)不可避免的脏数据问题。虽然这个问题在组合5中出现的概率已经大大降低了,但是还是有的。关于这一点就需要与业务沟通,毕竟这种情况比较少见,可以根据实际业务情况判断是否需要解决这个瑕疵。


1.5.2 总结


延时双删,有等待环节,如果系统要求低延时,这种场景就不合适了。 延时双删,不适合“秒杀”这种频繁修改数据和要求数据强一致的场景。 延时双删,延时时间是一个预估值,不能确保 mysql 和 redis 数据在这个时间段内都实时同步或持久化成功了。



相关实践学习
基于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
相关文章
|
11天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
12天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
5天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
22 5
|
20天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
111 22
|
19天前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
115 7
|
23天前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
59 10
|
存储 缓存 NoSQL
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
快速学习 Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
|
缓存 NoSQL 安全
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
快速学习6.0Spring Boot 2.0实战 Redis 分布式缓存6.0。
331 0
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
|
缓存 NoSQL Redis
首页数据显示-添加 redis 缓存(3)| 学习笔记
快速学习 首页数据显示-添加 redis 缓存(3)
151 0
首页数据显示-添加 redis 缓存(3)| 学习笔记
|
缓存 NoSQL Java
首页数据显示-添加 redis 缓存(1) | 学习笔记
快速学习 首页数据显示-添加 redis 缓存(1)
236 0
首页数据显示-添加 redis 缓存(1) | 学习笔记

热门文章

最新文章

下一篇
无影云桌面