Redis 缓存概述
# 一、缓存使用场景
# 1.1 DB 缓存,减轻服务器压力
一般情况下数据存在数据库中,应用程序直接操作数据库。
当访问量上万,数据库压力增大
可采取数据库的读写分离、分库分表方案。
当访问量达到十万、百万
需要引入缓存,将已经访问过的数据存储起来,当再次访问时先找缓存,缓存命中返回数据;不命中再找数据库,并回填缓存。
# 1.2 提高系统响应
数据库的数据是存在文件里,也就是硬盘,在读取时需要与内存做交换(swap),由于 MySQL 的 InnoDB 是有行锁的,因此在高并发时,MySQL 单机会因为频繁 IO 而造成无法响应。
Redis 的数据是存储在内存中的,天然支持高并发访问,QPS 可达到 10w 读请求。
# 1.3 做 Session 分离
将登录成功后的 Session 信息,存放在 Redis 中,Redis 作为临时存储,可以在多个服务器间共享 Session 信息。
# 1.4 做分布式锁(Redis)
可用 Redis 的 setNX 命令实现,之后会详细介绍 Redis 分布式锁的使用方法。
# 1.5 做乐观锁(Redis)
同步锁和数据库的行锁、表锁都是悲观锁,悲观锁的性能较低,响应比较差;高性能、高响应的场景(秒杀)应采用乐观锁,Redis 可以用 watch + incr 实现乐观锁。
# 二、缓存分类
# 2.1 客户端缓存
- 传统互联网:页面缓存和浏览器缓存
- 移动互联网:APP 缓存
页面缓存
页面自身对某些元素或全部元素进行存储,并保存成文件。
如 Html5 的 Cookie、WebStorage(SessionStorage 和 LocalStorage)、WebSql、indexDB、Application Cache 等。
浏览器缓存
当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有 “要请求资源” 的副本时,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。
浏览器缓存可分为强制缓存和协商缓存。
强制缓存:使用条件为 Cache-Control 的 max-age 没有过期或者 Expires 的缓存时间没有过期。
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta http-equiv="Expires" content="Mon, 20 Aug 2010 23:00:00 GMT" />
2
协商缓存:服务器资源未修改,使用浏览器的缓存(304);反之,使用服务器资源(200)。
<meta http-equiv="cache-control" content="no-cache">
APP 缓存
原生 APP 中把数据缓存在内存、文件或本地数据库(SQLite)中,比如图片文件。
# 2.2 网络端缓存
通过代理的方式响应客户端请求,对重复的请求返回缓存中的数据资源。
Web 代理缓存
可以缓存原生服务器的静态资源,比如样式、图片等,常见的反向代理服务器如 Nginx。
边缘缓存
边缘缓存中典型的商业化服务就是 CDN 了。
CDN 的全称是 Content Delivery Network,即内容分发网络。它通过部署在各地的边缘服务器,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
CDN 的关键技术主要有内容存储和分发技术,现在一般的公有云服务商都提供 CDN 服务。
# 2.3 服务端缓存
服务器端缓存是整个缓存体系的核心,包括数据库级缓存、平台级缓存和应用级缓存。
数据库级缓存
数据库是用来存储和管理数据的,MySQL 的缓存机制如下:
- Server 层使用查询缓存机制,将查询后的数据缓存起来,K-V 结构,Key 为 select 语句的 hash 值,Value 为查询结果;
- InnoDB 存储引擎中的 buffer-pool 用于缓存 InnoDB 索引及数据块。
平台级缓存
平台级缓存指的是带有缓存特性的应用框架,如 GuavaCache 、EhCache(二级缓存)、OSCache(页面缓存)等。
由于部署在应用服务器上,也称为服务器本地缓存。
应用级缓存(重点)
具有缓存功能的中间件:Redis、Memcached、EVCache(AWS)、Tair(阿里、美团)等。
特点:采用 K-V 形式存储,作为分布式缓存,可利用集群支持高可用、高性能、高并发、高扩展。
# 三、缓存的优势和代价
# 3.1 使用缓存的优势
提升用户体验
缓存的使用可以提升系统的响应能力,大大提升了用户体验。
减轻服务器压力
- 客户端缓存、网络端缓存减轻应用服务器压力。
- 服务端缓存减轻数据库服务器的压力。
提升系统性能
系统性能指标:响应时间、延迟时间、吞吐量、并发用户数和资源利用率等。
缓存技术可以:
- 缩短系统的响应时间
- 减少网络传输时间和应用延迟时间
- 提高系统的吞吐量
- 增加系统的并发用户数
- 提高了数据库资源的利用率
# 3.2 使用缓存的代价
额外的硬件支出
缓存是一种软件系统中以空间换时间的技术,需要额外的磁盘空间和内存空间来存储数据。
高并发缓存失效
在高并发场景下会出现缓存失效(缓存穿透、缓存雪崩、缓存击穿),造成瞬间数据库访问量增大,甚至崩溃。
缓存与数据库同步问题
- 缓存与数据库无法做到数据的时时同步;
- Redis 无法做到主从时时数据同步。
缓存并发竞争
多个 Redis 客户端同时对一个 Key 修改时,由于执行顺序引起的并发问题。
# 四、缓存的读写模式
# 4.1 Cache Aside Pattern(旁路缓存)
Cache Aside Pattern,是最经典的缓存 + 数据库读写模式。
读数据流程:先读缓存,缓存中没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
写数据流程:先更新数据库,然后删除缓存。
为什么删除缓存,而不是更新缓存?
- 缓存的值是一个结构,若使用 Hash、List 等结构存储,更新数据需要遍历后修改,耗时过长。
- 懒加载与异步方式结合使用。
- 懒加载:使用的时候才从 DB 中加载;
- 异步:开启一个线程,定时将 DB 的数据刷到缓存。
高并发脏读的三种情况
先删除缓存,再更新 DB
删除缓存后,更新 DB 之前如果有新的查询请求到来,会将缓存更新为原来的结果,造成不一致。
- C1 删除缓存;
- C2 查询 DB「脏数据」;
- C1 更新 DB,C2 将脏数据写入缓存。
先更新 DB,再更新缓存
在并发更新的场景下,两个客户端的更新 DB 与更新缓存的顺序无法控制,导致数据不一致。
- C1 更新 DB「Data1」;
- C2 更新 DB「Data2」;
- C2 更新缓存「Data2」;
- C1 更新缓存「Data1」。
先更新 DB,再删除缓存(推荐)
- C1 没有命中缓存,查询 DB「Data1」;
- C2 更新 DB「Data2」;
- C2 删除缓存;
- C1 将脏数据写入缓存。
可以采用延时双删策略解决,在之后会详细介绍。
# 4.2 Read/Write Through Pattern(读写穿透)
应用程序只操作缓存,缓存操作数据库。
- Read-Through(穿透读模式/直读模式):应用程序读缓存,缓存没有,由缓存回源到数据库,并写入缓存。
- Write-Through(穿透写模式/直写模式):应用程序写缓存,缓存写数据库。
该模式需要提供数据库的 handler,开发较为复杂。
# 4.3 Write Behind Caching Pattern(异步缓存写入)
应用程序只更新缓存,缓存通过异步的方式将数据批量或合并后更新到 DB 中。
这种方式的特点是效率非常高,但不是强一致,甚至会丢失数据,适用于访问量、点赞数等对于性能要求高,但一致性要求不高的场景。
# 五、缓存架构的设计
# 5.1 多层次缓存
提高缓存系统的可用性,如分布式缓存宕机,本地缓存还可以使用。
# 5.2 数据类型
简单数据类型
当 Value 是字符串、整数或二进制,且 Value 值比较大(大于 100K),只进行 set、get 操作时,可采用 Memcached 存储。
Memcached 是一个多线程的纯内存缓存。
复杂数据类型
当 Value 是 hash、set、list、zset 等,需要存储关系、聚合、计算时,可采用 Redis 存储。
# 5.3 集群
Redis 分布式缓存集群。
- 哨兵 + 主从
- RedisCluster
- Codis
# 5.4 数据结构设计
与数据库表一致
缓存数据库表的部分字段,如用户表、商品表。
与数据库表不一致
存储关系、聚合、计算等。