使用 redis 缓存的经验

本贴最后更新于 2204 天前,其中的信息可能已经沧海桑田

问题:怎么保持缓存与数据库一致?

要解答这个问题,我们首先来看不一致的几种情况。我将不一致分为三种情况:

1. 数据库有数据,缓存没有数据;

2. 数据库有数据,缓存也有数据,数据不相等;

3. 数据库没有数据,缓存有数据。

在讨论这三种情况之前,先说明一下我使用缓存的策略,也是大多数人使用的策略,叫做 Cache Aside Pattern。酷壳里的 缓存更新的套路 一文,很值得一读,我的策略也是从他那学来的。

简而言之,就是

1. 首先尝试从缓存读取,读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回。

2. 需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。

读的逻辑大家都很容易理解,谈谈更新。如果不采取我提到的这种更新方法,你还能想到什么更新方法呢?大概会是:先删除缓存,然后再更新数据库。这么做引发的问题是,如果 A,B 两个线程同时要更新数据,并且 A,B 已经都做完了删除缓存这一步,接下来,A 先更新了数据库,C 线程读取数据,由于缓存没有,则查数据库,并把 A 更新的数据,写入了缓存,最后 B 更新数据库。那么缓存和数据库的值就不一致了。

另外有人会问,如果采用你提到的方法,为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里。这么做引发的问题是,如果 A,B 两个线程同时做数据更新,A 先更新了数据库,B 后更新数据库,则此时数据库里存的是 B 的数据。而更新缓存的时候,是 B 先更新了缓存,而 A 后更新了缓存,则缓存里是 A 的数据。这样缓存和数据库的数据也不一致。

按照我提到的这种更新缓存的策略,理论上也是有不一致的风险的,酷壳的文章有提到,只不过概率很小,我们暂时可以不考虑,后面我们有其他手段来补救。

讨论完使用缓存的策略,我们再来看这三种不一致的情况。

1. 对于第一种,在读数据的时候,会自动把数据库的数据写到缓存,因此不一致自动消除

2. 对于第二种,数据最终变成了不相等,但他们之前在某一个时间点一定是相等的(不管你使用懒加载还是预加载的方式,在缓存加载的那一刻,它一定和数据库一致)。这种不一致,一定是由于你更新数据所引发的。前面我们讲了更新数据的策略,先更新数据库,然后删除缓存。因此,不一致的原因,一定是数据库更新了,但是删除缓存失败了。

3. 对于第三种,情况和第二种类似,你把数据库的数据删了,但是删除缓存的时候失败了。

因此,最终的结论是,需要解决的不一致,产生的原因是更新数据库成功,但是删除缓存失败。

我想出的解决方案大概有以下几种:

1. 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。

2. 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。

3. 给所有的缓存一个失效期。

第三种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。

哪些数据需要放在缓存中?

首先,缓存的对象有三种:

1. 数据库中单条的的数据(以表名跟 id 作为 key 永久保存到 redis),在有更新的地方都要更新缓存(不适用于需要经常更新的数据);

2. 对于一些不分页,不需要实时(需要多表查询)的列表,我们可以将列表结果缓存到 redis 中,设定一定缓存时间作为该数据的存活时间。用获取该列表的方法名作为 key,列表结果为 value;这种情况只试用于不经常更新且不需要实时的情况下。

3. 不需要实时的,需要分页的列表:可以把分页的结果列表放到一个 map(key 为分页标识,value 为分页结果)中,然后将该 map 存到 redis 的 list 中(用该方法名为 key)。然后给该 list 设置一个缓存存活时间(用 expire)。这样通过方法名 lrange 出来就能获取存有分页列表的数据,遍历该 list,通过遍历 list 中 map 的 key 判断该分页数据是否在缓存内,是则返回,不存在则 rpush 进去。这种做法能解决比如 1-5 页的数据已经重新加载,而 6-10 页的数据依然是缓存的数据而导致脏数据的情况。

本人走过的一些弯路:

1. 对于数据缓存不是所有东西都缓存到 redis 就是好的,而是要针对一些改动不大或者访问率大的数据进行缓存来减少关系型数据库的压力。

2. 不要试图在拦截器或者过滤器中判断是否有缓存的存在,因为每个请求(不管该请求对应的方法是否做了缓存)它都会去 redis 中请求数据并判断,这样会浪费一定的内存资源跟响应时间。所以应该针对需要缓存的方法进行判断。

3. 一个方法中使用多个 get 或者 set 的方法,我们需要尽可能的减少去 jedispool 中获取 jedis 对象,所以在一个方法中应该只获取一次 jedis 对象,在方法结束的时候把该对象 return 还给连接池,这样才能做到尽可能的高效。

4. 在设置连接池中参数的时候要考虑到自身系统需求,不然会经常出现连接池中无可用对象获取,spring 时不时发起连接请求到 redis 等不必要的错误和资源浪费。

  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    284 引用 • 247 回帖 • 169 关注
  • 经验
    25 引用 • 157 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • dafsic

    A,B 两个线程同时更新数据,数据库里最终应该是 A 的数据还是 B 的,还是看 cpu 对谁好?而且我数据库读写分离,主从同步,这缓存又要怎么弄?

  • 其他回帖
  • hacpaixixi

    另外有人会问,如果采用你提到的方法,为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里。这么做引发的问题是,如果 A,B 两个线程同时做数据更新,A 先更新了数据库,B 后更新数据库,则此时数据库里存的是 B 的数据。而更新缓存的时候,是 B 先更新了缓存,而 A 后更新了缓存,则缓存里是 A 的数据。这样缓存和数据库的数据也不一致。

    不是很明白:为什么不是 a 先更新缓存,a 不是先更新的数据库么

Changer0914
做才是得到,苦才是人生,残酷才是青春。 杭州

推荐标签 标签

  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 612 关注
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 284 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 48 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    60 引用 • 287 回帖
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    40 引用 • 24 回帖
  • OnlyOffice
    4 引用 • 17 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    126 引用 • 1699 回帖
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 8 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    181 引用 • 448 回帖
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    25 引用 • 191 回帖 • 19 关注
  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖 • 2 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    85 引用 • 895 回帖
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 235 关注
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 20 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 154 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    14 引用 • 7 回帖 • 1 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 1 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖 • 4 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 13 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    103 引用 • 294 回帖 • 1 关注
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    90 引用 • 59 回帖 • 1 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖
  • 外包

    有空闲时间是接外包好呢还是学习好呢?

    26 引用 • 232 回帖 • 9 关注
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    536 引用 • 672 回帖 • 1 关注
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 410 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 394 关注