Skip to content

Commit

Permalink
redis
Browse files Browse the repository at this point in the history
  • Loading branch information
muluoleiguo committed Jul 21, 2021
1 parent bd723fd commit 7ce8c78
Show file tree
Hide file tree
Showing 22 changed files with 756 additions and 55 deletions.
25 changes: 25 additions & 0 deletions 面试/数据库/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,28 @@





## NoSQL非关系型数据库——Redis

1. [Redis是什么](Redis/1.md)
2. [Redis的底层数据结构](Redis/2.md)
3. [Redis常见数据结构以及使用场景分别是什么?](Redis/3.md)
4. [有MySQL不就够用了吗?为什么要用Redis这种新的数据库?](Redis/4.md)
5. [C++中的Map也是一种缓存型数据结构,为什么不用Map,而选择Redis做缓存?](Redis/5.md)
6. [Memcached与Redis的区别都有哪些?](Redis/6.md)
* [请你回答一下mongodb和redis的区别](Redis/6-1.md)
7. [缓存中常说的热点数据和冷数据是什么?](Redis/7.md)
8. [Redis 为什么是单线程的而不采用多线程方案?](Redis/8.md)
9. [了解Redis的线程模型吗?可以大致说说吗?](Redis/9.md)
10. [Redis设置过期时间的两种方案是什么?](Redis/10.md)
11. [缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存击穿、缓存降级](Redis/11.md)
12. [Redis持久化机制可以说一说吗?](Redis/12.md)
13. [AOF重写了解吗?可以简单说说吗?](Redis/13.md)
14. [是否使用Redis集群,集群的原理是什么](Redis/14.md)
15. [如何解决Redis的并发竞争Key问题](Redis/15.md)
16. [如何保证缓存与数据库双写时的数据一致性](Redis/16.md)
17. [数据为什么会出现不一致的情况?](Redis/17.md)
18. [常见的数据优化方案你了解吗?](Redis/18.md)
19. [请你来说一说Redis的定时机制怎么实现的](Redis/19.md)

22 changes: 22 additions & 0 deletions 面试/数据库/Redis/1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#### 听说过Redis吗?它是什么?

Redis是一个**数据库**,不过与传统数据库不同的是Redis的数据库是存在**内存**中,所以**读写速度非常快**,因此 Redis被广泛应用于**缓存**方向。

除此之外,Redis也经常用来做分布式锁,Redis提供了多种数据类型来支持不同的业务场景。除此之外,Redis 支持事务持久化、LUA脚本、LRU驱动事件、多种集群方案。



#### 使用Redis的好处有哪些?

1、访问速度快,因为数据存在内存中,类似于Java中的HashMap或者C++中的哈希表(如unordered_map/unordered_set),这两者的优势就是查找和操作的时间复杂度都是O(1)

2、数据类型丰富,支持String,list,set,sorted set,hash这五种数据结构

3、支持事务,Redis中的操作都是原子性,换句话说就是对数据的更改要么全部执行,要么全部不执行,这就是原子性的定义

4、特性丰富:Redis可用于缓存,消息,按key设置过期时间,过期后将会自动删除。

<p id="与的区别都有哪些"></p>


####
51 changes: 51 additions & 0 deletions 面试/数据库/Redis/10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#### Redis设置过期时间的两种方案是什么?

Redis中有个设置时间过期的功能,即对存储在 Redis 数据库中的值可以设置一个过期时间。

作为一个缓存数据库, 这是非常实用的,比如一些 token 或者登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。

我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间,主要可采用**定期删除和惰性删除**两种方案。

- 定期删除:Redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删 除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 Redis 存了几十万个 key ,每隔100ms就遍历所 有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
- 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。它是指某个键值过期后,此键值不会马上被删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除,惰性删除的缺点很明显是浪费内存。 除非你的系统去查一下那个 key,才会被Redis给删除掉。这就是所谓的惰性删除!





#### 定期和惰性一定能保证删除数据吗?如果不能,Redis会有什么应对措施?

并不能保证一定删除,Redsi有一个Redis 内存淘汰机制来确保数据一定会被删除。

首先介一下定期删除和惰性删除的工作流程:

1、定期删除,Redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,Redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,Redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

2、于是,惰性删除派上用场。也就是说在你获取某个key的时候,Redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

3、采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,Redis

4、内存会越来越高。那么就应该采用内存淘汰机制。



在Redis.conf中有一行配置:maxmemory-policy volatile-lru

该配置就是配**内存淘汰策略**的,主要有以下六种方案:
**volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
**volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
**volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
**allkeys-lru**:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
**allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰
**no-enviction**(驱逐):禁止驱逐数据,新写入操作会报错
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。





#### 假如MySQL有1000万数据,采用Redis作为中间缓存,取其中的10万,如何保证Redis中的数据都是热点数据?

可以使用Redis的**数据淘汰策略**,Redis 内存数据集大小上升到一定大小的时候,就会施行这种策略。

107 changes: 107 additions & 0 deletions 面试/数据库/Redis/11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#### 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存击穿、缓存降级全搞定!

**缓存雪崩**

缓存雪崩指的是缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

看不懂?那我说人话。

我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。

**解决办法**

- 事前:尽量保证整个 Redis 集群的高可用性,发现机器宕机尽快补上,选择合适的内存淘汰策略。
- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉, 通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 事后:利用 Redis 持久化机制保存的数据尽快恢复缓存



**缓存穿透**

一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量 请求而崩掉。

这也看不懂?那我再换个说法好了。

缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,接着查询数据库也无法查询出结果,因此也不会写入到缓存中,这将会导致每个查询都会去请求数据库,造成缓存穿透。

**解决办法**

1、**布隆过滤器**

这是最常见的一种解决方法了,它是将所有可能存在的数据哈希到一个足够大的**bitmap**中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压 力。

对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

这里稍微科普一下布隆过滤器。

> 布隆过滤器是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
> 它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
>
> 该算法的**核心思想**就是利用多个不同的Hash函数来解决“冲突”。Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是布隆过滤器的基本思想,一般用于在大数据量的集合中判定某元素是否存在。


2、**缓存空对象**

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;如果一个**查询返回的数据为空**(不管是数据不存 在,还是系统故障),我们仍然把这个空结果进行缓存,但它的**过期时间会很短**,最长不超过五分钟。

但是这种方法会存在两个问题:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;

2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。



我们可以从适用场景和维护成本两方面对这两汇总方法进行一个**简单比较**

**适用场景**:缓存空对象适用于1、数据命中不高 2、数据频繁变化且实时性较高 ;而布隆过滤器适用1、数据命中不高 2、数据相对固定即实时性较低

**维护成本**:缓存空对象的方法适合1、代码维护简单 2、需要较多的缓存空间 3、数据会出现不一致的现象;布隆过滤器适合 1、代码维护较复杂 2、缓存空间要少一些





**缓存预热**

缓存预热是指系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户会直接查询事先被预热的缓存数据!

**解决思路**
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存;



**缓存更新**

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;**定时删除和惰性删除**
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。



**缓存击穿**

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

比如常见的电商项目中,某些货物成为“爆款”了,可以对一些主打商品的缓存直接设置为永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了。mutex key互斥锁基本上是用不上的,有个词叫做大道至简。





**缓存降级**

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

48 changes: 48 additions & 0 deletions 面试/数据库/Redis/12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#### Redis持久化机制可以说一说吗?

Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。

很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机 器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。

实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。

以下有两种持久化机制

**以下有两种持久化机制**

**快照(snapshotting)持久化(RDB持久化)**

Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。

快照持久化是Redis默认采用的持久化方式,在Redis.conf配置文件中默认有此下配置:

```shell
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令
创建快照。

save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
```

**AOF(append-only file)持久化**

与快照持久化相比,AOF**持久化的实时性更好**,因此已成为主流的持久化方案。默认情况下Redis没有开启 AOF(append only file)方式的持久化,可以通过appendonly参数开启:`appendonly yes`

开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的 保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。

在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

```shell
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
```

为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能 几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

**Redis 4.0 对于持久化机制的优化**

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
12 changes: 12 additions & 0 deletions 面试/数据库/Redis/13.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#### AOF重写了解吗?可以简单说说吗?

AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,**但体积更小**

AOF重写是一个有歧义的名字,该功能是通过读取数据库中的**键值**对来实现的,程序无须对现有AOF文件进行任伺读 入、分析或者写入操作。

在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF **重写缓冲区**,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。**当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容 追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致**。最后,服务器用新的AOF文件替换旧的 AOF文件,以此来完成AOF文件重写操作。

<p id="是否使用集群集群的原理是什么"></p>


####
7 changes: 7 additions & 0 deletions 面试/数据库/Redis/14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#### 是否使用Redis集群,集群的原理是什么

Redis Sentinel(哨兵)着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从服务器中选举出新的主服务器。

Redis Cluster(集群)着眼于扩展性,在单个Redis内存不足时,使用Cluster进行分片存储。
10 changes: 10 additions & 0 deletions 面试/数据库/Redis/15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#### 如何解决Redis的并发竞争Key问题

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!

推荐一种方案:分布式锁(zookeeper 和 Redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问 题,不要使用分布式锁,这样会影响性能)

基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的 与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有 序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁 无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。

在实践中,当然是从以可靠性为主。所以首推Zookeeper。

Loading

0 comments on commit 7ce8c78

Please sign in to comment.