# Questions

## 原理

**Redis 为什么快**

* 基于内存
* 数据结构简单、高效
* 利用 IO多路复用监听事件、事件驱动

**为什么采用单线程？**

* 首先必须明确，`redis` 单线程指的是网络请求模块使用了一个线程，其他模块有的用的是多线程，并不是一个线程完成了所有功能。
* 原理上，其采用了利用 `epoll` 的多路复用特性，因此可以采用单线程处理其网络请求。
* `6.0` 以后，使用了多线程并行处理IO读写事件（网络连接仍然用一个线程处理）。
  * 这种做法和 `Go` 的 `http` 库异曲同工，`netpoll` 里只用一个协程处理网络连接，但对连接里请求的读写，仍然是每个连接起两个协程分别负责其读写。

**什么情况下使用 redis**

1. 针对热点数据进行缓存
2. 对于特定限时数据的存放
3. 针对带热点权值数据的排序
4. 分布式锁

**与memcache的区别**

1. `redis` 处理网络请求采用单线程模型，而 `memcache` 采用多线程的方式
2. `redis` 支持数据持久化，`memcache` 不支持
3. `redis` 支持的数据格式比 `memcache` 更多（一般选型都是这个原因）

## 穿透、击穿、雪崩

**缓存穿透**

缓存穿透指缓存和数据库均没有需要查询的数据，查询穿透到DB，使数据库压力过大。

**解决方法**

1. 前置过滤
   1. 在数据库操作访问前进行校验，对不合法请求直接返回
   2. 每天定时使用布隆过滤器重置所有 `key` 的集合，先判断 `key` 在不在，再查 `redis / DB`
2. 对于经常被访问的，并且数据库没有的 `key`，缓存该 `key` 的值为 `null`。不过这个得设较短的过期时间，防止数据长时间不一致。

**缓存击穿**

高频访问的key过期失效，导致缓存瞬间被击穿，打到数据库

**解决方法**

1. 设置热点数据永远不过期。
2. `singleflight`：从数据库加载到缓存时加锁。

**缓存雪崩**

缓存雪崩指缓存中一大批数据同时到过期时间。导致大量请求打到数据库

**解决方法**

1. 缓存数据设置随机过期时间，防止同一时间大量数据过期。
2. 设置热点数据永远不过期。

**脑裂**

是指因为网络问题，导致 `master` 节点未宕机的情况下，`sentinel / 其余master` 因为连接不上 `master`，所以将 `slave` 提升为 `master` 。此时存在两个不同的 `master` 节点。

如果客户端还在基于原来的 `master` 继续写入数据，那么新的 `master` 节点将无法同步这些数据，当网络问题解决之后，原先的 `master` 被降为 `slave` ，此时再从新的 `master` 中同步数据，将会造成大量的数据丢失。

**解决方法**

设置 `min-slaves` 参数，这样向旧的主节点写数据时会因无法同步给从节点而失败。客户端收到错误时应重新查找主节点

**对比 memcache**

1. 数据类型更多：Redis支持多种数据类型，如字符串、列表、哈希、集合、有序集合等，而Memcached只支持简单的键值对。
2. 持久化：Redis支持将数据持久化到磁盘，即使在服务器重启后也可以恢复数据，而Memcached不支持数据持久化。
3. 高可用：Redis支持主从复制和集群模式，可以提供更高的可用性和可扩展性。
4. 更丰富的功能：Redis支持事务、发布/订阅、Lua脚本等更多的功能，可以满足更多的应用场景。
5. 性能差不多（因为都是读写内存，一般瓶颈在网络）

**redis里时间复杂度最高的是哪个命令？**

在 Redis 中，时间复杂度最高的命令是 `KEYS` 命令。 `KEYS` 命令是用来查找与指定模式匹配的键名。它的时间复杂度为 O(N)，其中 N 是键空间的大小。因为它需要遍历整个键空间来查找符合条件的键名，所以在非常大的键空间中使用 `KEYS` 命令可能会导致 Redis 阻塞一段时间，从而降低 Redis 的性能。

如果想要查找符合某种模式的键名，建议使用更高效的命令，如 `SCAN` 命令。`SCAN` 命令可以在时间和空间上更高效地获取符合指定模式的键名。使用 `SCAN` 命令时，可以通过游标（cursor）来逐步遍历整个键空间，避免一次性遍历导致的性能问题。

其他命令一般都是O(1)、O(lgN) 的时间复杂度

**参考**

> [后端技术小牛说](https://mp.weixin.qq.com/s/paHphwGFE9AsJFWkayZkCg)
