1 Star 0 Fork 51

petter / java-legendary

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
超神之路-Redis.md 36.56 KB
一键复制 编辑 原始数据 按行查看 历史
编程界的小学生 提交于 2021-02-22 15:37 . add redis

[TOC]

author:编程界的小学生

date:2021/02/21

一、基础

1、简单介绍下Redis

C写的开源高性能非关系型键值对数据库。底层采取epoll读写速度非常快,大多用于缓存,也提供了事务、持久化、集群以及多种数据类型的功能。

2、你认为Redis有哪些有缺点?

优点:

  • 读写快,读10w+/s,写8w+/s
  • 支持持久化
  • 支持事务
  • 数据结构丰富
  • 支持主从,数据分片

缺点:

  • 不具备自动恢复功能
  • 较难支持在线动态扩容

3、说说Redis的几种数据类型

  • string:字符串、整数或者浮点数
  • list:列表可重复
  • hash:包含键值对的无序散列表
  • set:无序集合不可重复
  • zset:有序集合不可重复
  • geo:地理位置
  • bitmap:二进制位

4、为什么要用Redis而不是guava/静态全局map?

guava/map是基于jvm内存的本地缓存,生命周期会随着jvm的结束而停止,也不存在持久化的特性。Redis是分布式缓存,可以进行持久化。

5、列举几个Redis应用场景

  • 计数器
  • 数据缓存
  • 页面缓存
  • 消息队列(blpop)
  • 分布式锁
  • 好友关系(set交并差集)
  • 排行榜(zset的score)

6、Redis和memcached的区别?

  • Redis比memcached数据类型丰富。
  • Redis支持批量操作,事务操作,持久化,发布订阅,memcached不支持。
  • Redis效率比memcached高,Redis采取epoll模型。

二、持久化

1、什么是持久化?Redis为什么需要持久化?

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

2、Redis有哪几种持久化方式?优缺点是什么?

rdb(默认) 和aof两种。

rdb优点:

  • 二进制存储节省空间,只有一个dump.rdb文件。
  • 灾难恢复较快。
  • 性能最大化(主进程处理命令的效率,而不是持久化的效率),采取的是fork()+copyonwrite技术。

rdb缺点:

  • 安全性相对较低,因为rdb是每隔一段时间进行持久化,数据丢失率较高。
  • 持久化速度相对aof较低,因为aof直接append追加,rdb是全量。

aof优点:

  • 数据安全,可以配置always,也就是每进行一次命令操作就记录到aof文件中一次。
  • 持久化速度较快,每次都只是追加一个语句到文件。
  • 带rewrite机制。

aof缺点:

  • AOF 文件比 RDB 文件大,所以灾难性恢复速度慢。
  • 会对主进程对外提供请求的效率造成影响,接收请求、处理请求、写aof文件这三步是串行原子执行的。而非异步多线程执行的。Redis单线程!Redis6.0就多线程了。

3、两种持久化方式如何做选择?

  • 如果数据非常敏感,尽量做到不丢失,那么选择aof,反之rdb。
  • 如果追求容灾恢复速度,那么建议rdb,因为他是二进制的,文件很小,恢复速度快。
  • 如果追求读写速度,那么建议rdb,因为他是fork出来子进程配合copyonwrite技术来持久化,不会影响主进程的读写。
  • Redis4.0后支持混合持久化,也就是rdb+aof,建议开启。

4、RDB持久化的原理是怎样的?

fork子进程+copyonwrite技术。

4.1、什么是fork?

fork()是unix和linux这种操作系统的一个api,而不是Redis的api。fork()用于创建一个子进程,注意是子进程,不是子线程。fork()出来的进程共享其父类的内存数据。仅仅是共享fork()出子进程的那一刻的内存数据,后期主进程修改数据对子进程不可见,同理,子进程修改的数据对主进程也不可见。比如:A进程fork()了一个子进程B,那么A进程就称之为主进程,这时候主进程A和子进程B所指向的内存空间是同一个,所以他们的数据一致。但是A修改了内存上的一条数据,这时候B是看不到的,A新增一条数据,删除一条数据,B都是看不到的。而且子进程B出问题了,对我主进程A完全没影响,我依然可以对外提供服务,但是主进程挂了,子进程也必须跟随一起挂。这一点有点像守护线程的概念。Redis正是巧妙的运用了fork()这个牛逼的api来完成RDB的持久化操作。

4.1.1、Redis中的fork()

Redis巧妙的运用了fork()。当bgsave执行时,Redis主进程会判断当前是否有fork()出来的子进程,若有则忽略,若没有则会fork()出一个子进程来执行rdb文件持久化的工作,子进程与Redis主进程共享同一份内存空间,所以子进程可以搞他的rdb文件持久化工作,主进程又能继续他的对外提供服务,二者互不影响。我们说了他们之后的修改内存数据对彼此不可见,但是明明指向的都是同一块内存空间,这是咋搞得?肯定不可能是fork()出来子进程后顺带复制了一份数据出来,如果是这样的话比如我有4g内存,那么其实最大有限空间是2g,我要给rdb留出一半空间来,扯淡一样!那他咋做的?采取了copyonwrite技术。

4.2、什么是copyonwrite?

主进程fork()子进程之后,内核把主进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向主进程。这也就是共享了主进程的内存,当其中某个进程写内存时(这里肯定是主进程写,因为子进程只负责rdb文件持久化工作,不参与客户端的请求),CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入内核的一个中断例程。中断例程中,内核就会把触发的异常的页复制一份(这里仅仅复制异常页,也就是所修改的那个数据页,而不是内存中的全部数据),于是主子进程各自持有独立的一份。

数据修改之前的样子

1

数据修改之后的样子

1

其实就是更改数据的之前进行copy一份更改数据的数据页出来,比如主进程收到了set k 1请求(之前k的值是2),然后这同时又有子进程在rdb持久化,那么主进程就会把k这个key的数据页拷贝一份,并且主进程中k这个指针指向新拷贝出来的数据页地址上,然后进行更改值为1的操作,这个主进程k元素地址引用的新拷贝出来的地址,而子进程引用的内存数据k还是修改之前的。

4.2.1、一句话总结copyonwrite

copyonwritefork()出来的子进程共享主进程的物理空间,当主子进程有内存写入操作时,read-only内存页发生中断,将触发的异常的内存页复制一份(其余的页还是共享主进程的)。

4.3、为什么要用copyonwrite?

1.假设是全量复制,那么内存空间直接减半,浪费资源不说,数据量10g,全量复制这10g的时间也够长的。这谁顶得住?

2.如果不全量复制,会是怎样?相当于我一边复制,你一边写数据,看着貌似问题不大,其实不然。比如现在Redis里有k1的值是1,k2的值是2,比如bgsave了,这时候rdb写入了k1的值,在写k2的值之前时,有个客户端请求

set k1 11 
set k2 22

那么持久化进去的是k2 22,但是k1的值还是1,而不是最新的11,所以会造成数据问题,所以采取了copyonwrite技术来保证触发bgsave请求的时候无论你怎么更改,都对我rdb文件的数据持久化不会造成任何影响。

5、AOF持久化的原理是怎样的?

就是每次都在aof文件后面追加命令。他与主进程收到请求、处理请求是串行化的,而非异步并行的。图示如下:

3

所以aof的频率高的话绝逼会对Redis带来性能影响,因为每次都是刷盘操作。跟mysql一样了。Redis每次都是先将命令放到缓冲区,然后根据具体策略(每秒/每条指令/缓冲区满)进行刷盘操作。如果配置的always,那么就是典型阻塞,如果是sec,每秒的话,那么会开一个同步线程去每秒进行刷盘操作,对主线程影响稍小。

5.1、AOF有哪几种刷盘策略?

其实Redis每次在写入AOF缓冲区之前,他都会调用flushAppendOnlyFile(),判断是否需要将AOF缓冲区的内容写入和同步到AOF文件中。这个决策是由配置文件的三个策略来控制的

  • always
  • everysec
  • no

6、RDB和AOF哪个快?

1、分析

这道题不严谨,因为不知道他问的是持久化过程哪个快还是说Redis主进程对外提供请求哪个快?(也就是说哪个持久化方式会对主进程影响较大)

2、持久化过程哪个快

那肯定AOF,因为AOF每次只追加命令,而RDB每次都是全量覆盖。

3、哪个持久化方式会对主进程影响较大

那肯定是AOF效率低,因为AOF是串行的,相当于每次处理完命令都要同步的写入磁盘(当然也看具体策略),而bgsave模式的RDB则是开启子进程并行的处理这件事,不影响主进程对外提供请求,即使AOF策略开到最容易丢失数据的那种,那也是不定期磁盘操作,这是毫秒级别的。而RDB持久化即使发生CopyOnWrite也只是寻址操作,纳秒级别的。

7、能同时存在几个fork?为什么?

只能同时存在一个,原因如下:

  • bgsave命令执行期间,client发送的save/bgsave命令会被服务器拒绝,这么做是因为如果产生多个子进程同时进行rdb持久化的工作的话会产生竞争条件,造成数据问题已经服务器压力也会某些条件下过大。
  • bgrewriteaof和bgsave能不能同时执行?

不能!

1.如果bgsave正在执行,那么client发送的bgrewriteaof命令会被推迟到bgsave结束后才得到执行。 2.如果bgrewriteaof正在执行,那么client发送的bgsave命令将会被服务器拒绝

8、RBD和AOF的文件格式有什么区别?

RDB:

  • 以.rdb后缀结尾
  • 以REDIS为魔数的二进制

AOF:

  • 以.aof后缀结尾
  • 记录具体操作的命令
  • rewrite后也变成二进制

9、聊聊AOF的rewrite机制?

9.1、为什么要rewrite?

比如我有业务很简单,就来回delete set 同一个key。就这个业务运行了10年,那么aof文件将记录无数个delete k1, set k1 xxx。其实都是重复的,但是我aof每次都追加,文件变成了1T大小。这时候Redis宕机了,要恢复,你想想1TB大小的aof文件去恢复,累死了。最主要的是1TB大小只记录了两个命令,所以压缩其实就是来处理这件事的。

9.2、rewrite后的数据长什么样?

  • 4.0版本之前

Redis4.0之前就是将aof文件中重复的命令给去掉。保留最新的命令。进而减少aof文件大小。

  • 4.0版本以及之后

4.0之前的做法效率很是低下,需要逐条命令对比。4.0开始的rewrite支持混合模式(也是就是rdb和aof一起用),直接将rdb持久化的方式来操作将二进制内容覆盖到aof文件中(rdb是二进制,所以很小),然后再有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次rewrite(还是按照rdb持久化的方式将内容覆盖到aof中)。但是这种模式也是配置的,默认是开,也可以关闭。

9.3、什么时候会触发rewrite?

  • 手动触发

执行bgrewriteaof命令。

  • 自动触发

通过以下两个配置协作触发

  • auto-aof-rewrite-min-size

AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0默认配置64mb。

  • auto-aof-rewrite-percentage

当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。

9.4、rewrite原理

4

  • aof_rewrite_buf:rewrite(重写)缓冲区、aof_buf:写命令存放的缓冲区
  • 开始bgrewriteaof的时候,判断当前有没有bgsave/bgrewriteaof在执行,若有,则不执行
  • 主进程fork()出子进程,在执行fork()这个方法的时候是阻塞的,子进程创建完毕后就不阻塞了
  • 主进程fork完子进程后,主进程能继续接收客户端的请求,所有写命令依然是写入AOF文件缓冲区并根据配置文件的策略同步到磁盘的。
  • 因为fork的子进程仅仅共享主进程fork()时的内存,后期主进程在更改内存数据,子进程是不可见的。因此Redis采取重写缓冲区(aof_rewite_buf)保存fork之后的客户端请求。防止新AOF文件生成期间丢失主进程执行的新命令所生成的数据。所以此时客户端的写请求不仅仅写入原来的aof_buf缓冲区,还写入了重写缓冲区。这就是我为什么用深蓝色的框给他两框到一起的原因。
  • 子进程通过内存快照的形式,开始生成新的aof文件。
  • 新aof文件生成完后,子进程向主进程发信号。
  • 主进程收到信号后,会把重写缓冲区(aof_rewite_buf)中的数据写入到新的AOF文件(主要是避免这部分数据丢失)
  • 使用新的AOF文件覆盖旧的AOF文件,且标记AOF重写完成。

10、聊聊Redis的混合持久化方式?

10.1、优点

混合持久化结合了RDB持久化 和 AOF 持久化的优点,采取了rdb的文件小易于灾难恢复,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。

10.2、缺点

兼容性差,一旦开启了混合持久化,在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,需要专业的工具来阅读,因为是二进制,所以阅读性较差。

10.3、总结

Redis4.0以及以后新增的内容,很强大。默认开启,也建议开启。但具体还要看业务,比如业务就允许数据丢失,那直接RDB就完事了,不需要开AOF,这样效率还高。如果一点数据都不允许丢失,那只能RDB+always策略的AOF,换言之,不管怎样,都建议开启RDB,RDB可以应对灾难性快速恢复。混合持久化也不用过于担心文件大小问题,4.0开始的rewrite支持混合模式直接按rdb持久化的方式来操作将二进制内容覆盖到aof文件中(rdb是二进制,所以很小),然后再有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次rewrite(还是按照rdb持久化的方式将内容覆盖到aof中),堪称完美!

10.4、数据恢复

会优先看是否存在aof文件,若存在则先按照aof文件恢复,因为aof毕竟比rdb全。若aof不存在,则才会查找rdb是否存在。这是默认的机制。毕竟aof文件也rewrite成rdb二进制格式,文件小,易于回复。所以redis会优先采取aof。

11、如何手动进行rdb持久化?

  • save

同步、阻塞。

致命的问题,持久化的时候redis服务阻塞(准确的说会阻塞当前执行save命令的线程,但是redis是单线程的,所以整个服务会阻塞),不能继对外提供请求,GG!数据量小的话肯定影响不大,数据量大呢?每次复制需要1小时,那就相当于停机一小时。

  • bgsave

异步、非阻塞。

采取fork() + copyonwrite的方式,他可以一边进行持久化,一边对外提供读写服务,互不影响,新写的数据对我持久化不会造成数据影响,你持久化的过程中报错或者耗时太久都对我当前对外提供请求的服务不会产生任何影响。持久化完会将新的rdb文件覆盖之前的。

三、过期淘汰策略

1、Redis的过期key删除策略有哪些?也是expire原理

如果假设你设置一个一批key只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的? 答案是:定期删除+惰性删除

  • 定期删除

定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。

  • 惰性删除

定期删除可能会导致很多过期key到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下

通过上述两种手段结合起来,保证过期的key一定会被干掉

  • 总结

很简单,就是说,你的过期key,靠定期删除没有被删除掉,还停留在内存里,占用着你的内存呢,除非你的系统去查一下那个key,才会被redis给删除掉

但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,咋整?

答案是:走内存淘汰机制。

2、Redis淘汰策略有哪些?

如果redis的内存占用过多的时候,此时会进行内存淘汰策略: 假设如下场景:

redis里有10000个key,现在已经满了,redis需要触发内存淘汰策略删除key
1个key,最近1min被查了2w次
1个key,最近10min被查了100次
1个key,最近1小时被查了10次
  • noeviction:当内存不足以写新数据的时候,那么写操作会报错。(一般没人用,毕竟只是缓存,又不是mysql这种ACID的关系型数据库)
  • allkeys-lru:当内存不足以写新数据的时候,在key空间中,移除最近最少使用的key(这个是最常用的),如果只移除一个的话,那么最后一个key肯定被移除
  • allkeys-random:当内存不足以写新数据的时候,在key空间中,随机移除某个key(一般没人用,万一把我活跃的key移除了咋办)
  • volatile-lru:当内存不足以写新数据的时候,在设置了过期时间的key中,移除最近最少使用的key(这个也还凑合,但是也不咋地,都设置过期时间了,你还给我移除干嘛)
  • volatile-random:当内存不足以写新数据的时候,在设置了过期时间的键空间中,随机移除某个key(极其不合适,还不如allkeys-random)
  • volatile-ttl:当内存不足以写新数据的时候,在设置了过期时间的键空间中,有更早过期时间的key优先移除(也不咋合适,不一定更早过期的就是不活跃key,而且都快过期了你还给我移除干嘛,自生自灭)

总结:Redis的内存淘汰策略的选取并不会影响过期的key的处理,而是用于处理内存不足时需要申请额外空间的数据,过期策略用于处理过期的缓存数据。

四、事务

1、Redis的事务概念

Redis的事务并不像Mysql那么灵活,有隔离级别,出问题后还能回滚数据等高级操作。Redis毕竟是非关系型数据库,他目前事务回滚机制是不执行命令,也就是可以采取watch命令模拟乐观锁,进行监听数据,发现数据不是事务开始时候的样子了,那么我这个事务里的命令就不会得到执行。

2、Redis事务的相关命令

  • MULTI:开始事务。
  • EXEC:执行事务,也就是说只有EXEC命令执行的时候,这个事务内的语句才会真正的得到执行。
  • WATCH:监听数据变化,在开始事务之前执行。

3、Redis事务支持隔离性吗?

Redis是单进程的且它保证在执行事务时不会对事务进行中断,事务可以从运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。

4、Redis事务保证原子性吗,支持回滚吗?

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。他目前事务回滚机制是不执行命令,也就是可以采取watch命令模拟乐观锁,进行监听数据,发现数据不是事务开始时候的样子了,那么我这个事务里的命令就不会得到执行。

5、Redis事务的其他实现方式

lua脚本。

五、主从集群

六、场景问题

1、如何保证Redis里都是热点数据?

redis内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略,热点数据不会被淘汰掉。

Redis淘汰策略有哪些?

2、Redis内存快用完了会怎样?

会进行内存淘汰机制,当Redis达到内存上限时会淘汰掉不活跃的数据。如果还是不够,内存还是满了的情况下Redis的写命令会返回错误信息,读命令还可以正常返回。

Redis淘汰策略有哪些?

3、如何优化Redis内存?

  • 选择合适的数据类型
  • 大value选择gzip压缩
  • 避免使用大key
  • 批处理采取Pipeline

4、Redis为什么这么快?

  • 纯内存操作,且时间复杂度都是O(1)
  • 数据结构底层存储的设计比较节省空间
  • 采取单线程避免了不必要的上下文切换,Redis6.0开始多线程。
  • 采取I/O多路复用,epoll模型,非阻塞IO。

什么是epoll?这篇文章解释的淋漓尽致:

https://blog.csdn.net/ctwctw/article/details/105024324

5、Redis怎么做扩容?

6、如何解决Redis的并发竞争key的问题?

分布式锁。

7、如何用Redis实现分布式锁?

  • setnx
  • lua
  • Redisson
  • RedLock

8、分布式Redis是前期做还是后期数据量上来了后在做?为什么?

建议提前做好数据量预估然后选择合适的Redis集群数,避免后期在新增服务器进行迁移重新分片。

9、什么是RedLock?

10、什么是缓存雪崩?如何解决?

情况一

大批量热点数据过期后,高并发请求,直接请求到db,引起db压力造成查询阻塞甚至宕机。

解决方案

  • 简单粗暴,设置热点数据永不过期(具体还看业务是否允许)
  • 将缓存失效的时间分散开,比如每个热点key的过期时间是随机的,而不是每个key都一样是固定的。这样不会遇到同时大量热点数据过期的情况,分批过期,将高并发大流量变成一部分一部分的小流量打给数据库,这样就没问题了。

情况二

Redis挂了。导致请求都到db了。假设高并发5000个请求,本来缓存再高峰期可以抗住每秒4000个请求,但是Redis挂了,5000个请求直接干到了数据库层,这可能又把数据库给打死了。

解决方案

  • 加一层本地ehcache缓存。也就是请求进来先查ehcache缓存,ehcache没有的话去查redis,redis也没有再去db。这时候redis挂了,但是我们本地ehcache还有,所以不会打到db层。
  • 限流组件,可以设置每秒最大请求数,超过最大请求数的那些请求怎么办?走降级!可以返回一些默认值,或者友情提示啥的。

限流的好处是数据库绝对不会死,因为限流组件保证了有多少请求能进来。只要数据库不死,就能对外继续提供请求。虽然有部分用户请求走降级,但不是全部,会有大部分请求得到执行。

11、什么是缓存穿透?如何解决?

比如电商网站,有以下商品在redis里和mysql里。且Redis里只存放热点商品, 而不是全部。 苹果、香蕉、鸭梨等,但是用户搜了个我这电商网站里没有卖的商品,这时候redis里肯定没有搜的数据,就去请求db了。db也不一定有(有可能有有可能没有),如果没有的话那白白浪费性能了。这就是缓存穿透。量少的话就别说了,量少都没必要redis缓存。大量的话会很恐怖,比如淘宝,他无所不卖,万一真找到一个不卖的东西去搜,然后redis没有,结果都去请求mysql了。那不GG了嘛?

解决方案

布隆过滤器。原理就是利用bitmap来解决缓存穿透的一种技术手段。实现步骤如下:

  • 容器启动从数据库里读出全部商品,通过hash计算得到一个下标作为bitmap的位,将其置为1。
  • 用户搜商品的时候先经过hash计算得到位下标,然后去布隆过滤器里查看这个位下标的值是不是1,是1代表有商品,再去mysql查。若是0则直接返回没商品即可。(位的运算极其的快,性能消耗几乎忽略不计)
  • 可能多个商品通过hash计算得到的值一样,这就发生了hash碰撞,这也意味着布隆过滤器不是100%靠谱,可能漏掉某些商品,所以具体应用场景还得看业务需求是否允许。所以即使高并发场景也足够了。减少hash碰撞的方式可以采取不同算法对key进行多次hash。

12、什么是缓存击穿?如何解决?

某个 key 非常非常热点,访问非常的频繁,高并发访问的情况下,当这个 key在失效(可能expire过期了,也可能LRU淘汰了)的瞬间,大量的请求进来,这时候就击穿了缓存,直接请求到了数据库,一下子来这么多,数据库肯定受不了,这就叫缓存击穿。某个key突然失效,然后这时候高并发来访问这个key,结果缓存里没有,都跑到db了。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  • 热点数据永不过期(看业务需求。不过很少有这种场景,一般都是比如搞活动或者大促的时候才会存这些热点数据,活动过后就需要到期,不会设置永不过期。),此方法简单粗暴,不过还看具体需求。
  • 分布式锁。来个请求后先锁住,然后去db查,查到后再将数据set到redis里。只有当redis里getKey没拿到数据需要请求db的时候才加锁。不影响Redis里有数据的时候处理高并发请求,也能保证db不会被打垮。

13、什么是缓存预热?如何实现?

缓存预热就是系统启动的时候就查询数据库将相关的数据直接缓存到Redis中。这样可以避免用户查询的时候先判断是否有缓存,没的话在查db进行缓存等一系列操作了。

实现方案

  • 如果数据量不大的话,可以在项目启动的时候进行缓存。

  • 如果数据量很大的话可以写个简易的页面,上线时手工点一下进行缓存。

  • 定时任务定时刷新缓存。

14、什么是缓存降级?如何实现?

就是当缓存出故障的时候,别影响主业务系统,让他可以继续对外提供服务。

实现方案

很简单,在catch里去查db,而不是抛出异常。

15、

16、什么是热点key?如何安全的缓存热点key?

热点key就是频繁被访问的key,为了防止频繁访问数据库,所以需要将这些数据缓存起来,这些数据就称为热点数据。

一般采取分布式锁来安全的缓存热点key,也就是对查询缓存加锁,即如果key不存在就加锁,然后查数据库set到缓存,然后解锁。其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。

17、如何保证Redis和数据库的双写数据一致性?

18、遇到过Redis常见性能问题吗?怎么解决的?

  • for循环操作redis,几万个。换成Pipeline分批处理。
  • key过大,采取缩写。
  • value是个大json,用户信息。去除不必要的字段,并且采取gzip压缩成二进制存储。
  • 单节点扛不住并发量,采取集群数据分片。
  • 用户签到采取的string类型,换成bitmap。
  • 禁用一些耗费性能的命令,比如:keys等。
  • 持久化工作最好交给slave节点来做。

19、Redis如何做大数据量插入?

Redis的Pipeline类型,他会将一组命令用一次客户端服务端的请求来完成。

不适用场景:

  • 要求可靠性高,每次都需要实时知道这次操作是否成功,数据是否写入redis了等对实时性的这种需求都不适合。

适用场景:

  • 对实时性要求不高的
  • 批量的将数据写入 redis,允许一定比例的写入失败

比如群发短信、邮件这种,失败了我补偿,我不需要及时知道发送结果的,这种用pipeline合适 。

20、假如Redis里面有一亿个key,其中10w个key是以某个固定字符串开头的,如何将这10w个key找出来?

  • keys命令:不靠谱,因为会阻塞Redis主进程,可能导致Redis短时间内无法读写。
  • scan命令:存在重复的概率,客户端做去重就行了。效率高。推荐。
  • 网上有别人写好的开源脚本。

21、用Redis如何实现mq?

采取list数据类型,rpush生产消息,lpop消费消息,阻塞式的可以用blpop,在没有信息的时候,会一直阻塞,直到信息的到来。

22、用Redis如何实现延迟队列?

采取zset数据类型,使用时间戳做score,然后用zadd来生产消息,消费组使用zrangbyscore获取x秒之前的数据做轮询处理。

23、Redis如何实现类似微博那种互相关注、共同好友?

采取set数据类型的交集SINTER/SINTERSTORE

24、10万用户一年365天的登录情况如何用redis存储,并快速检索任意时间窗内的活跃用户?

采取Redis的bitmap数据类型来存储,然后用Redis提供的bitmap命令进行位运算计算出连续登录天数,有多少用户活跃等。

不懂的看这篇:https://blog.csdn.net/ctwctw/article/details/105013817

25、Redis为什么是key,value的,为什么不是支持SQL的?

选择key-value的原因:key-value简单粗暴,使用方便,效率更佳。 为什么不支持sql:因为redis的内存模型是一个hashtable,不使用表来存储数据,也不会预定义或强制要求用户对redis储存的不同数据进行关联。

26、Redis到底是单线程的还是多线程的?

仅针对Redis6.0之前的答案,因为Redis6.0之后处理命令也是多线程了。

redis中io多路复用器模块是单线程执行,事件处理器也是单线程执行,两个线程不一样。所以实际redis应该是单进程多线程,只是不同的模块都用的单线程实现。 两个维度来举例: (1)若是client发送命令到server的话,server处理命令是单线程逐条进行的。 (2)server内部可以是多线程的,比如aof持久化,假设策略每秒,那就是再单独开启一个线程去执行aof文件持久化操作,这就是多线程了。

27、Redis的持久化开启了RDB和AOF下重启服务是如何加载的?

数据恢复

28、Redis生产环境该如何规划?

参考redis各种部署方式的优缺点来决定。

  • 如果希望快速部署,那么可以考虑单节点部署方式。
  • 如果只需要考虑可靠性,那么可以考虑主从复制模式。
  • 如果想要保证高可用,不需要考虑储存成本可以考虑哨兵模式。
  • 如果想提高集群的扩展性和可用性,不要求保证数据的强一致性,且没有批量操作,那么可以考虑集群模式。

29、五种数据类型的底层存储原理是什么?

  • string:int、raw、embstr
  • list:ziplist、linkedlist
  • hash:ziplist、hashtable
  • set:intset、hashtable
  • sorted set:ziplist、skiplist+dict

30、100万并发4G数据,10万并发400G数据,如何设计Redis存储方式?

前者用主从+哨兵进行高可用,加快读请求的速度,减轻单节点的压力。

后者用集群来均分这400G数据。

31、Redis如何实现附近的人功能?

可以采取geo命令。3.2版本新增加的命令。主要原理是根据经纬度来计算距离。

32、聊聊Redis的慢查询

  • slowlog-max-len

存放慢查询语句的是一个先进先出的队列,他有固定长度,slowlog-max-len就是控制慢查询语句数量的,存放到内存中的,默认128,超过128的话会舍弃最先进入队列的命令。所以一般可以搞大点,因为他是存放到内存的,重启就没了。

  • slowlog-log-slower-than

慢查询阈值(单位:微妙,默认是10000,也就是10毫秒)。默认是超过10ms命令没执行完就扔到慢查询队列里(上面参数控制队列大小)。Redis号称QPS万级别,所以建议设置成1000,也就是1ms。

建议:

  • slowlog-max-len:不要设置过大,默认10ms,建议设置1ms。
  • slowlog-log-slower-than:不要设置过小,建议设置1000左右
  • 定期持久化慢查询。因为slowlog-max-len这个队列是放到内存里的,重启就没了,而且有长度限制,所以我们要查的慢命令多的话根本不显示,会丢失命令。所以可以用第三方插件持久化到mysql或者自己写个脚本持久化到mysql或者文件。

33、你们生产环境的数据备份和恢复方案是怎样的?

备份方案:

我们需要定时备份rdb文件来做冷备,为什么?不是有aof和rbd了吗为什么还要单独写定时任务去备份?因为Redis的aof和rdb是仅仅有一个最新的,比如谁手贱再Redis宕机的时候执行rm -rf aof/rdb了,那不就GG了吗?或者rdb/aof文件损坏了等不可预期的情况。所以我们需要单独备份rdb文件以防万一。

为什么不定时备份aof而是rdb?定时备份aof没意义呀,定时本身就是冷备份,不是实时的,rdb文件又小恢复又快,她哪里不香?

  • RBD和AOF建议同时打开(Redis4.0之后支持)
  • RDB做冷备,AOF做数据恢复(数据更可靠)
  • RDB采取默认配置即可,AOF采取everysec每秒策略
  • 写crontab定时调度脚本去做数据备份。
  • 每小时都copy一份redis的rdb文件到一个其他目录中,这个目录里的rdb文件仅仅保留48小时内的。也就是每小时都做备份,保留2天内的rdb,只保留48个rdb。
  • 每天0点0分copy一份redis的rdb文件到一个其他目录中,这个保留一个月的。也就是按天备份。
  • 每天半夜找个时间将当前服务上的所有rdb备份都上传到云服务上。

恢复方案:

  • 如果仅仅是redis进程挂了,那么直接重启redis进程即可,Redis会按照持久化配置直接基于持久化文件进行恢复数据。
  • 如果持久化文件(rdb/aof)损坏了,或者直接丢失了。不慌,我们云上有,download下来然后重点如下:

问题一:直接把备份的rdb扔到redis持久化目录下然后重启redis不行的原因在于:redis是按照先aof后rdb进行恢复的,所以都是开启aof的,redis启动后会重新生成新的aof文件,里面是空的。所以不会进行任何数据恢复,也就是说虽然你把rdb丢给redis了,但是redis会按照aof来恢复,而aof是redis启动的时候新生成的空文件,所以不会有任何数据进行恢复。

问题二:那么我们把rdb文件丢给redis后,先将redis的aof关闭再启动redis进程不就能按照rdb来进行恢复了吗?是这样的,没毛病!但是新的问题来了,我们aof肯定要开的,aof对数据保障更可靠。那什么我们按照rdb文件恢复完后再修改redis配置文件开启aof然后重启redis进程不就得了嘛?大哥…你打开aof然后重启redis,这时候redis又会生成一个空的aof文件,这时候恢复的时候又是啥数据都没了。因为数据是存到内存里,你重启后肯定没了,需要持久化文件来恢复。这时候aof是空的,我恢复个鸡毛啊。

具体方案:

我不管你是持久化文件丢了还是坏了,我都先rm -rf * 给他删了。

  • 停止redis进程
  • 删除坏掉的rdb和aof持久化文件。
  • 修改配置文件关闭redis的aof持久化。
  • 找到最新备份的rdb文件扔到redis的持久化目录里。(这里最新的肯定是按照小时备份的最后一个)
  • 启动Redis进程
  • 执行set appendonly yes动态打开aof持久化。

也就是说打开aof的操作不是修改配置文件然后重启,而是先热修改让他生成aof,这次生成肯定是会带着内存中完整的数据的。然后再修改配置文件重启。

  • 等aof文件生成后再修改redis配置文件打开aof。
  • 重启redis进程。
  • 完美收官。

34、我往Redis里写的数据怎么没了?偶尔会丢失部分数据

Redis淘汰策略有哪些?

35、我的数据明明都过期了,怎么还占用着内存?

1、Redis的过期key删除策略有哪些?也是expire原理

xx、聊聊Redis6.0的多线程

七、劝退Redis文章目录

https://blog.csdn.net/ctwctw/article/details/105265689

Java
1
https://gitee.com/zhupy/java-legendary.git
git@gitee.com:zhupy/java-legendary.git
zhupy
java-legendary
java-legendary
master

搜索帮助