# 使用缓存的常见问题

## 缓存一致性

### 出现场景

更新缓存的时候，如果是 **先删除缓存，再更新数据库**，则可能会导致缓存数据和源数据不一致的。

出现问题的场景如下：

```plantuml
@startuml

Application -[#F00]> Cache: 【更新】删除缓存

Application -[#00F]> Cache: 【查询】读取缓存

note right of Application: 更新操作和查询操作是两个并发请求

Cache -[#F00]> Cache: 【更新】删除缓存成功，但尚未更新数据库

Cache -[#00F]> Database: 【查询】没有命中缓存，读取数据库中的旧数据

Database -[#00F]> Cache: 【查询】更新旧数据至缓存

Cache -[#00F]> Application: 【查询】返回旧数据

Cache -[#F00]> Database: 【更新】更新数据库

Database -[#F00]> Database: 【更新】更新数据成功

Database -[#F00]> Cache: 【更新】没有更新新数据至缓存中

Cache -[#F00]> Application: 【更新】更新数据完成

note right of Cache: 此时缓存中的数据是旧数据

== 后续操作 ==

Application -[#00F]> Cache: 【查询】读取缓存

Cache -[#00F]> Application: 【查询】命中缓存，返回旧数据

@enduml
```

### 解决方案

使用缓存时，遵循 Cache Aside Pattern 模式，其具体逻辑如下：

* 失效：应用程序先从 Cache 获取数据，没有命中时从 Database 获取数据，并且在获取数据成功之后，保存至 Cache 中；
* 命中：应用程序从 Cache 获取数据，获取数据成功之后，直接返回；
* 更新：先更新数据至数据库中，更新成功之后，再删除 Cache 中的数据。

使用 Cache Aside Pattern 时，上述场景变更为：

```plantuml
@startuml

Application -[#F00]> Database: 【更新】更新数据库

Application -[#00F]> Cache: 【查询】读取缓存

note right of Application: 更新操作和查询操作是两个并发请求

Cache -[#00F]> Application: 【查询】命中缓存，返回缓存中的旧数据

Database -[#F00]> Database: 【更新】更新数据成功

Database -[#F00]> Cache: 【更新】删除缓存

Cache -[#F00]> Cache: 【更新】删除缓存成功

Cache -[#F00]> Application: 【更新】更新数据完成

note right of Cache: 此时缓存中的没有数据

== 后续操作 ==

Application -[#00F]> Cache: 【查询】读取缓存

Cache -[#00F]> Database: 【查询】没有命中缓存，读取数据库中的新数据

Database -[#00F]> Cache: 【查询】更新新数据至缓存

Cache -[#00F]> Application: 【查询】返回新数据

@enduml
```

### 参考资料

* [缓存更新的套路](https://coolshell.cn/articles/17416.html)

## 缓存穿透（不存在 key）

### 出现场景

如果应用程序一直查询未命中缓存的 key，那么查询请求便会一直访问数据库。

### 解决方案

从数据库中查询数据失败时，返回一个默认值，并设置相对短暂的失效时间，这样下次请求就会直接从缓存中获取这个默认值。

## 缓存雪崩（多个 key 失效）

### 出现场景

如果多个缓存的 key 同时失效，这样便会出现大量的未命中缓存的请求访问数据库。

### 解决方案

合理分布 key 的失效时间，例如在原有的失效时间上增加一个随机值。

## 缓存击穿（热点 key 失效）

### 出现场景

在高并发场景下，如果一个热点 key 失效，则会出现大量的并发请求访问数据库。

### 解决方案

#### 方案一

定期从数据库查询数据，并更新到缓存中。但是，对于某些动态拼接的 key，这种方案是无效的。

#### 方案二

对于热点 key，设置两个失效时间：一个是缓存即将失效的时间，另一个是真正的缓存失效时间。在缓存即将失效时，应用程序主动从数据库查询数据，并更新到缓存中。

值得注意的是，在缓存即将失效时，应用程序需要先加锁再访问数据库，避免出现并发访问数据库的场景。

对于热点 key 设置两个失效时间的方式如下：

1. 通过在设置 value 时追加当前时间戳。缺点：需要同步缓存服务器和应用服务器的时间、失效时间与 value 耦合（可能导致取数据的时候需要额外的反序列化）；
2. 通过设置两个 key 的方式。缺点：缓存中 key 的数量翻倍。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gitbook.fantasticmao.cn/tech/shu-ju-ku/redis/shi-yong-huan-cun-de-chang-jian-wen-ti.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
