为什么使用Redis
-
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
-
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
使用redis的好处?
速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
支持丰富数据类型,支持string,list,set,sorted set,hash
支持事务 :redis对事务是部分支持的,如果是在入队时报错,那么都不会执行;在非入队时报错,那么成功的就会成功执行。 redis监控:锁的介绍
丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除。
分布式缓存和本地缓存有啥区别?让你自己设计本地缓存怎么设计?如何解决缓存过期问题?如何解决内存溢出问题?
分布式缓存和本地缓存的区别
分布式缓存一致性更好一点,本地缓存 每个实例都有自己的缓存,可能会存在不一致的情况。
本地缓存会占用堆内存,影响垃圾回收、影响系统性能。分布式缓存两大开销会导致其慢于本地缓存,网络延迟和对象序列化
进程内缓存适用于较小且频率可见的访问场景,尤其适用于不变对象,对于较大且不可预见的访问,最好采用分布式缓存。
如何解决缓存过期问题
缓存失效:
引起这个原因的主要因素是高并发下,我们一般设定一个缓存的过期时间时,可能有一些会设置5分钟啊,10分钟这些;并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间在同一时刻,这个时候就可能引发——当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重。
处理方法:
一个简单方案就是将缓存失效时间分散开,不要所以缓存时间长度都设置成5分钟或者10分钟;比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
缓存失效时产生的雪崩效应,将所有请求全部放在数据库上,这样很容易就达到数据库的瓶颈,导致服务无法正常提供。尽量避免这种场景的发生。
redis和memcached的区别
Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等;
Memcached仅支持简单的key-value结构的数据记录。Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;
虚拟内存--Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘;
过期策略--memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10;
分布式--设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从;
存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);
灾难恢复--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;
Redis支持数据的备份,即master-slave模式的数据备份;
redis常用数据结构和使用场景
-
String(字符串)
set 键 值 (设置键值对)
get 键 (根据键取值)
String结构又存在3种类型,分别是字符串、数值、bitmap。
使用场景:
字符串:分布式锁
数值:在一些系统中看似不是很重要的统计,抢购,秒杀,详情页中数据统计,点赞,评论等。可以规避并发情况下对数据库的事务操作,完全由redis内存操作代替。
bitmap:用户签到、在线用户统计、统计活跃用户。
-
Hash(哈希)
hset 键 字段 值
hmset 键 字段1 值1 字段2 值2 字段3 值3 字段n 值n
hger 键 字段
hmget 键 字段1 字段2 字段3 字段n
-
hgetall 键
容量:每个hash可以存储4294967295个键值对(2的32次方-1)
使用场景:点赞、收藏、详情页等
-
Set(集合)
sadd 键 值1 值2 值3 值n
smembers 键
使用场景:
关注集合:共同关注、二度好友
点赞集合
抽奖集合
用户标签
-
List(列表)
lpush 键 值1 值2 值3 值n
-
lrange 键 开始下标 结束下标
容量:每个hash可以存储4294967295个键值对(2的32次方-1)
使用场景:
栈
队列,比如:关注队列、粉丝队列
消息队列
-
Zset(Sorted Set: 有序集合)
zadd 键 分数1 值1 分数2 值2 分数n 值n
zrange 键 开始下标 结束下标
zrangebyscore 键 开始分值 结束分值
使用场景:
排行榜
时间轴
优先级队列
Zset底层实现?跳表搜索插入删除过程?
zset的编码有ziplist和skiplist两种。 底层分别使用ziplist(压缩链表)和skiplist(跳表)实现。
当zset满足以下两个条件的时候,使用ziplist:
- 保存的元素少于128个
- 保存的所有元素大小都小于64字节
不满足这两个条件则使用skiplist (注意:这两个数值是可以通过redis.conf的zset-max-ziplist-entries 和 zset-max-ziplist-value选项 进行修改。)
-
ziplist编码
ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。
-
skiplist编码
skiplist 编码的有序集合对象使用 zet 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳表:
字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。
这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。
注意:单独使用字典,时间复杂度很低(O(1)),但字典是以无序的方式保存集合元素的,需要重新进行排序;单独使用跳表,虽然能执行范围操作,但是查找操作从O(1)的复杂度变为了O(logN)。所以使用两种数据结构共同实现
redis过期淘汰策略
淘汰策略
定期删除+惰性删除
所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。(注意:是随机)
但是,定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠惰性删除了。
惰性删除就是在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下
通过上述两种手段结合起来,保证过期的key一定会被淘汰
内存淘汰机制
如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错(一般没人用)
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key(一般没人用)
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
redis持久化机制?都有什么优缺点?持久化的时候还能接受请求吗?
RDB机制和AOF机制
RDB:
RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
触发机制
-
save触发方式(同步)
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。
-
bgsave触发方式(异步)
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。
-
自动触发
自动触发是由我们的配置文件来完成的。
RDB 的优势和劣势
①优势
(1)RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
(2)生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
(3)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
②劣势
RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
AOF:
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
AOF也有三种触发机制
(1)每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
(2)每秒同步everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失
(3)不同no:从不同步
AOF的优缺点
优点
(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。(2)AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。
(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
(4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
缺点
(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。
持久化的时候还能接受请求吗?
RDB就是生成某个时间点快照,有异步bgsave和同步save,同步的话肯定不能对外服务了,异步是通过fork子进程完成的,需要注意的是,在异步的时候,数据可能发生变化,redis并不是直接复制一份进行复制,redis运用写时复制cow思想,即一开始redis和子进程都指向同一个数据,当某个key改变时redis主进程指向新的,子进程不变。
AOF也是同步不行,异步可以
redis事务
概念:
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Redis事务没有隔离级别的概念:
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
Redis事务不保证原子性:
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
Redis事务的三个阶段:
开始事务
命令入队
执行事务
Redis事务相关命令:
watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi : 标记一个事务块的开始( queued )
exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard : 取消事务,放弃事务块中的所有命令
unwatch : 取消watch对所有key的监控
缓存雪崩和缓存穿透,以及解决方法
-
缓存穿透
缓存穿透是指缓存和数据库中没有的数据,而用户不断发起请求,若发起 id为“-1”的数据或id为特别大而不存在的数据,此时的用户很可能是攻击者,攻击会导致数据库压力过大,而影响数据库的性能
-
解决方案
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
从缓存取不到的数据,在数据库中也没有取到,这时可以设置该id对应的value为null,设置一定的有效时间,可以防止攻击用户在一定时间内反复使用同一id进行暴力攻击
-
缓存击穿
缓存击穿是指缓存中没有但是数据库中有的数据(一般是由于缓存时间到期销毁),这时由于并发用户特别多,同时读缓存没有读到数据,又同时去数据库中取数据,使数据库压力瞬间增大,造成数据库性能大幅度降低
-
解决方案
设置热点数据永不过期
加互斥锁
定时任务更新缓存数据
-
缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数量巨大,引起数据库压力巨大甚至down机。和缓存击穿不同的是,缓存击穿是指大量用户并发查同一条数据,缓存雪崩是指大量不同数据都在同一时间过期了,大量用户不能从缓存中得到这些数据,而进入数据库查询
-
解决方案
缓存数据的过期时间设置为随机,防止大量数据在同一时间过期的现象出现
如果缓存数据库是分布式部署,将热点数据均匀分布在不同缓存数据库中
设置热点数据永不过期
如何保证缓存和数据库的数据一致性?
选择淘汰缓存
原因:数据可能为简单数据,也可能为较复杂的数据,复杂数据进行缓存的更新操作,成本较高,因此一般推荐淘汰缓存
方案:
先删除缓存,后更新数据库。
更新的时候,先删除缓存,然后再更新数据库。
读的时候,先读缓存;如果没有的话,就读数据库,同时将数据放入缓存,并返回响应。
原因:因为即使后面更新数据库失败了,缓存是空的,读的时候会从数据库中重新拉,虽然都是旧数据,但数据是一致的。
- 有如下场景:同时有一个请求A进行更新操作,另一个请求B进行查询操作。 (1)请求A进行写操作,删除缓存 (2)请求B查询发现缓存不存在 (3)请求B去数据库查询得到旧值 (4)请求B将旧值写入缓存 (5)请求A将新值写入数据库
次数便出现了数据不一致问题。采用延时双删策略得以解决
- 数据库读写分离的场景:
两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。
(1)请求A进行写操作,删除缓存 (2)请求A将数据写入数据库了, (3)请求B查询缓存发现,缓存没有值 (4)请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值 (5)请求B将旧值写入缓存 (6)数据库完成主从同步,从库变为新值
依旧采用延时双删策略解决此问题
延时双删是将1秒内所造成的缓存脏数据,再次删除。
redis是单线程还是多线程?为什么那么快?
Redis是单线程的
为什么快?
Redis完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
使用多路I/O复用模型,非阻塞IO;
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
五种IO模型的区别
- 阻塞式IO
使用系统调用,并一直阻塞直到内核将数据准备好,之后再由内核缓冲区复制到用户态,在等待内核准备的这段时间什么也干不了
下图函数调用期间,一直被阻塞,直到数据准备好且从内核复制到用户程序才返回,这种IO模型为阻塞式IO
阻塞式IO式最流行的IO模型
- 非阻塞式IO
内核在没有准备好数据的时候会返回错误码,而调用程序不会休眠,而是不断轮询询问内核数据是否准备好
下图函数调用时,如果数据没有准备好,不像阻塞式IO那样一直被阻塞,而是返回一个错误码。数据准备好时,函数成功返回。
应用程序对这样一个非阻塞描述符循环调用成为轮询。
非阻塞式IO的轮询会耗费大量cpu,通常在专门提供某一功能的系统中才会使用。通过为套接字的描述符属性设置非阻塞式,可使用该功能
- IO多路复用
类似与非阻塞,只不过轮询不是由用户线程去执行,而是由内核去轮询,内核监听程序监听到数据准备好后,调用内核函数复制数据到用户态
下图中select这个系统调用,充当代理类的角色,不断轮询注册到它这里的所有需要IO的文件描述符,有结果时,把结果告诉被代理的recvfrom函数,它本尊再亲自出马去拿数据
IO多路复用至少有两次系统调用,如果只有一个代理对象,性能上是不如前面的IO模型的,但是由于它可以同时监听很多套接字,所以性能比前两者高
-
多路复用包括:
select:线性扫描所有监听的文件描述符,不管他们是不是活跃的。有最大数量限制(32位系统1024,64位系统2048)
poll:同select,不过数据结构不同,需要分配一个pollfd结构数组,维护在内核中。它没有大小限制,不过需要很多复制操作
epoll:用于代替poll和select,没有大小限制。使用一个文件描述符管理多个文件描述符,使用红黑树存储。同时用事件驱动代替了轮询。epoll_ctl中注册的文件描述符在事件触发的时候会通过回调机制激活该文件描述符。epoll_wait便会收到通知。最后,epoll还采用了mmap虚拟内存映射技术减少用户态和内核态数据传输的开销
- 信号驱动式IO
使用信号,内核在数据准备就绪时通过信号来进行通知
首先开启信号驱动io套接字,并使用sigaction系统调用来安装信号处理程序,内核直接返回,不会阻塞用户态
数据准备好时,内核会发送SIGIO信号,收到信号后开始进行io操作
- 异步IO
异步IO依赖信号处理程序来进行通知
不过异步IO与前面IO模型不同的是:前面的都是数据准备阶段的阻塞与非阻塞,异步IO模型通知的是IO操作已经完成,而不是数据准备完成
异步IO才是真正的非阻塞,主进程只负责做自己的事情,等IO操作完成(数据成功从内核缓存区复制到应用程序缓冲区)时通过回调函数对数据进行处理
unix中异步io函数以aio_或 lio打头
各种IO模型对比
前面四种IO模型的主要区别在第一阶段,他们第二阶段是一样的:数据从内核缓冲区复制到调用者缓冲区期间都被阻塞住!
前面四种IO都是同步IO:IO操作导致请求进程阻塞,直到IO操作完成
异步IO:IO操作不导致请求进程阻塞
select、poll、epoll的区别?
-
支持一个线程所能打开的最大连接数
image -
FD剧增后带来的IO效率问题
image.png
-
消息传递方式
image
综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点:
表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。
redis热key问题?如何发现以及如何解决?
热点问题概述
热点问题一般出现在读多写少的场景
在日常工作生活中一些突发的的事件,诸如:“双11”期间某些热门商品的降价促销,当这其中的某一件商品被数万次点击、购买时,会形成一个较大的需求量,这种情况下就会产生一个单一的Key,这样就会引起一个热点;同理,当被大量刊发、浏览的热点新闻,热点评论等也会产生热点;另外,在服务端读数据进行访问时,往往会对数据进行分片切分,此类过程中会在某一主机Server上对相应的Key进行访问,当访问超过主机Server极限时,就会导致热点Key问题的产生。
当某一热点的Key在某一主机上超过该主机网卡上线时,由于流量的过度集中,会导致服务器中其它服务无法进行。
此外,热点Key的缓存过多,超过目前的缓存容量时,就会导致缓存分片服务被打垮现象的产生。当缓存服务崩溃后,此时再有请求产生,会缓存到后台DB上,由于其本身性能较弱,在面临大请求时很容易发生请求穿透现象,会进一步导致“雪崩”现象,严重影响设备的性能。
如何发现
对于db上热点数据的发现,首先会在一个周期内对Key进行请求统计,在达到请求量级后会对热点Key进行热点定位,并将所有的热点Key放入一个小的LRU链表内,在通过Proxy请求进行访问时,若Redis发现待访点是一个热点,就会进入一个反馈阶段,同时对该数据进行标记。
如何解决
首先Client会将请求发送至Server上,而Server又是一个多线程的服务,本地就具有一个小的缓存空间。当Server本身就拥堵时,Server不会将请求进一步发送给DB而是直接返回,只有当Server本身畅通时才会将Client请求发送至DB,并且将该数据重新写入到Cache中。
https://blog.csdn.net/qq_35956041/article/details/81195826
redis数据分布方式?有什么优点?一致性hash呢?
分布方式:
-
节点取余
客户端分片:哈希+取余
节点伸缩:数据节点关系变化,导致数据迁移
迁移数量和添加节点数量有关:建议翻倍扩容
-
一致性哈希
客户端分片:哈希+顺时针(优化取余)
节点伸缩:只影响邻近节点,但是还是有数据迁移
翻倍伸缩:保证最小迁移数据和负载均衡
-
虚拟槽分区
预设虚拟槽:每个槽映射一个数据子集,一般比节点数大
良好的哈希函数:例如CRC16
服务端管理节点、槽、数据:例如Redis Cluster


