热衷学习,热衷生存!😄
积淀、分享、成长,让本人和别人都能有所播种!😄
一、简略的介绍一下 Redis
简略的说 Redis
就是一个应用 C 语言
开发的一个数据库,不过与传统数据库不同的是 Redis
的数据是存在内存中的,它是内存数据库,所以读写速度十分快,所以 Redis
被广泛应用于缓存方向。
另外,Redis
除了做缓存之外,也还常常用于做分布式锁,甚至是音讯队列。
Redis
提供了多种数据类型来反对不同的业务场景。Redis
还反对事务、长久化、Lua 脚本、多种集群计划。
二、Redis 长久化机制
Redis
是一个反对长久化的内存数据库,通过长久化机制把内存中的数据同步到硬盘文件来保证数据长久化。当 Redis
重启后通过把硬盘文件从新加载到内存,就能达到复原数据的目标。
实现:独自创立一个 fork()
子过程,将以后父线程的数据库文件复制到子过程的内存中,而后由子过程写入到临时文件中,长久化的过程就完结了,再用这个临时文件替换上次的快照文件,而后子过程退出,内存开释。
RDB(Redis DataBase)
是 Redis
默认的长久化形式。依照肯定的工夫周期策略把内存的数据以快照的模式保留到硬盘的二进制文件 。即Snapshot
快照存储,对应产生的数据文件为 dump.rdb
,通过配置文件中的save
参数来定义快照的周期。
AOF(Append Only Field)
形式:Redis
会将每一个写命令都通过 write
函数追加到文件最初,相似 MySQL 的 binlog
,当重启Redis
会加载 appendonly.aof
文件复原数据。
三、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级问题
缓存雪崩
缓存雪崩能够了解为:因为原有缓存生效,新缓存未到期间,而造成一些列连锁反应,而造成整个零碎解体。
举个栗子:
咱们设置缓存采纳了雷同的过期工夫,在同一时刻呈现大面积的缓存过期,而后所有的申请都去拜访数据库了,对数据库 CPU
和存在造成了微小压力,重大的时候造成数据库宕机。
解决办法:
大多数零碎设计者思考用加锁或者队列的形式保障不会有大量的线程对数据库一次性进行读写,从而防止生效时大量的并发申请落到底层存储系统上。还有一个简略计划就时讲缓存生效工夫扩散开。
缓存穿透
缓存穿透是指用户查询数据库,在数据库没有,天然在缓存中也不会有,这样就导致用户在查问的时候,在缓存中找不到,每次都要去数据库再查问一遍,而后返回空,相当于两次无用的查问,这样申请就间接绕过缓存间接查询数据库,这也是常常说的缓存命中问题。
解决办法:
最罕用的就是采纳 布隆过滤器 ,将所有可能存在的数据哈希到一个足够大的bitmap
中,不存的数据会被这个 bitmap
拦挡掉,从而防止了对底层存储系统的查问压力。
另外一个更为 简略粗犷的办法,如果一个查问返回的数据为空(不论是数据不存在,还是系统故障),咱们依然把这个空后果进行缓存,但它的过期工夫会很短,不会超过五分钟,通过这个间接设置的默认值寄存到缓存,这样子二次到缓存中获取就有值了。
缓存预热
缓存预热就是零碎上线后,将相干的缓存数据间接加载到缓存零碎。这样子就能够防止在用户申请的时候,先查询数据库,而后再将数据缓存的问题!用户间接查问当时被预热的缓存数据!
解决思路:
- 间接写个缓存页面,上线时手工操作下。
- 数据量不大,能够在我的项目启动时主动进行加载。
- 定时刷新缓存。
缓存更新
除了缓存服务器自带的缓存生效策略之外(Redis 默认的有 6 中策略可供选择),咱们还能够依据具体的
业务需要进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户申请过去时,再判断这个申请所用到的缓存是否过期,过期的话就去底层零碎失去新数
据并更新缓存。
两者各有优劣,第一种的毛病是保护大量缓存的 key 是比拟麻烦的,第二种的毛病就是每次用户申请过
来都要判断缓存生效,逻辑绝对比较复杂!具体用哪种计划,能够依据本人的利用场景来衡量。
缓存降级
当访问量剧增、服务呈现问题或非核心服务影响到外围流程的性能时依然须要保障服务还是可用的,即便是有损服务。零碎能够依据一些要害数据进行主动降级,也能够配置开关实现人工降级。
降级的最终目标是保障外围服务可用,即便是有损的。而且有些服务是无奈降级的(如退出购物车、结
算)。
以参考日志级别设置预案:
(1)个别:比方有些服务偶然因为网络抖动或者服务正在上线而超时,能够主动降级;
(2)正告:有些服务在一段时间内成功率有稳定(如在 95~100% 之间),能够主动降级或人工降级,
并发送告警;
(3)谬误:比方可用率低于 90%,或者数据库连接池被打爆了,或者访问量忽然猛增到零碎能接受的
最大阀值,此时能够依据状况主动降级或者人工降级;
(4)严重错误:比方因为非凡起因数据谬误了,此时须要紧急人工降级。
服务降级的目标是为了避免 Redis
服务故障,导致数据库跟着一起产生雪崩问题。因而,对于不重要的缓存数据,能够采取服务降级策略,例如一个比拟常见的做法就是,Redis 呈现问题,不去数据库查
询,而是间接返回默认值给用户。
四、单线程的 Redis 为什么这么快
- 纯内存操作。
- 单线程操作,防止了频繁的上下文切换。
- 采纳非阻塞 I / O 多路复用机制。
五、Redis 的数据类型,以及每种数据类型的应用场景
string
string
数据结构是简略的 key-value
类型,value
能够是字符串也能够是数字。
个别用于做一下简单的计数性能,比方用户的拜访次数、点赞性能等等。
list
list
即是链表,实现了一个双向链表,能够反对反向查找、遍历,更不便操作,然而带来了额定的内存开销。
罕用于公布和订阅或者音讯队列、慢查问。
hash
hash
相似 JDK1.8
前的HashMap
,内存实现也差不过是数组 + 链表。特地适宜用于贮存对象,后续操作的时候,你能够间接仅仅批改这个对象中的某个字段的值。
罕用于零碎中对象数据的存储。
set
set
相似于 Java
中的HashSet
,是一个无序且不反复的汇合。能够基于 set 轻易实现交加、并集、差集的操作。
罕用于须要寄存的数据不能反复以及须要获取多个数据源交加和并集等场景。
sorted set
和 set
相比,减少了一个权重参数 score
,是的汇合中的元素可能依照score
进行有序排列,还能够通过 score 的范畴来获取元素的列表。
罕用于须要对数据依据某个权重进行排序的场景。比方:各种排行榜、TOP N
。
bitmap
bitmap
存储的是间断的二进制数字(0 和 1),通过 bitmap
,只需一个bit
位来示意某个元素对应的值或者状态,key
就是对应元素自身。
罕用于须要保留状态信息(比方是否签到、是否登陆)并须要进一步对这些信息进行剖析的场景。比方用户签到状况、沉闷用户状况、用户行为统计。
六、Redis 的过期策略以及内存淘汰机制
redis
采纳的是 定期删除 + 惰性删除策略。
为什么不必定时删除策略?
定时删除用一个定时器来负责监督 key
,过期则主动删除。尽管内存及时开释,然而非常耗费CPU
资源。
在大量并发申请下,CPU
要将工夫用在解决申请,而不是用来删除key
。
定期删除 + 惰性删除是怎么工作的呢?
定期删除,redis
默认每隔 100ms
查看是否有过期的 key
,有的话就删除。须要阐明是redis
不是每隔 100ms
将所有的 key
查看一次,而是随机抽取进行查看,因而如果只采纳定期删除策略会导致很多过期的 key
没有删除,这个时候惰性删除就派上用场了,获取某个 key
的时候,redis
会检查一下这个 key
是否过期了,如果过期了就删除。
采纳定期删除 + 惰性删除就没其余问题了吗?
不是的,如果定期删除没删除的key
,也没有申请去获取,这个时候惰性删除就不会失效,redis 的内存会越来越高,这个时候就要用到内存淘汰机制了。
在 redis.conf
中有一行配置:
maxmemory-policy volatile-lru
该配置就是配置内存淘汰策略,次要有以下策略:
volatile-re
:从已设置过期的数据集中筛选最近起码应用的数据淘汰。volatile-ttl
:从已设置过期的数据集中筛选将要过期的数据淘汰。volatile-random
:从已设置过期的数据集中随机抉择数据淘汰。allkeys-lru
:从数据集中筛选最近起码应用的数据淘汰。allkeys-random
:从数据集中随机筛选数据淘汰。no-enviction
:禁止淘汰数据,新写入操作会报错。
ps:如果没有设置 expire
的key
, 不满足先决条件(prerequisites
); 那么 volatile-lru
, volatile-random
和volatile-ttl
策略的行为, 和 noeviction(不删除)
基本上统一。
七、Redis 为什么是单线程的
官网 FAQ
示意,因为 redis
是基于内存操作,CPU
不是 redis
的瓶颈,Redis
的瓶颈最有可能是机器内存的大小或者网络宽带。既然单线程容易实现,而且 CPU
不会成为瓶颈。那就牵强附会地采纳单线程的计划了(毕竟采纳多线程会有很多麻烦!)
Redis
利用队列技术将并发拜访变为串行拜访。
- 绝大部分申请是纯正的内存操作。
- 采纳单线程能够防止不必要的上下文切换和竞争条件。
Redis
采纳了非阻塞 I / O 技术。
八、为什么 Redis 的操作是原子性的,怎么保障原子性的?
对 Redis
而言,命令的原子性指的是:一个操作的不能够再分,操作要么执行,要么不执行。
Redis
的操作之所以是原子性的,是因为 Redis
是单线程的。
Redis
自身提供的所有 API
都是原子操作,Redis
中的事务其实是要保障批量操作的原子性。
多个命令在并发中也是原子性吗?
这个不肯定,将 get
和set
改成单命令操作可能其中一个命令胜利,一个失败。能够应用 Redis
事务或者应用 Redis + Lua 脚本
的形式实现。
九、Redis 事务
Redis
事务性能是通过 MULTI、EXEC、DISCARD、WATCH
四个原语实现的。
Redis
会将一个事务中的所有命令序列化,而后按程序执行,并且不会被中途打断。
Redis
是不反对 roll back
的,因此不满足原子性的(而且不满足持久性)。
Redis 官网也解释了本人为啥不反对回滚。简略来说就是 Redis 开发者们感觉没必要反对回滚,这样更简略便捷并且性能更好。Redis 开发者感觉即便命令执行谬误也应该在开发过程中就被发现而不是生产过程中。
四个原语性能如下:
MULTI
:用于开始一个事务,它总是返回OK
。MULTI
命令执行结束之后,客户端能够持续向服务器发送任意多条命令,这些命令不会被立刻执行,而是被放到一个队列中,当EXEC
命令被执行时,所有队列中的命令才会被执行。EXEC
:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列,当操作被打断时返回空值。- 通过调用
DISCARD
命令,客户端能够清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出。 WATCH
:命令用于监听指定的键,当调用EXEC
命令执行事务时,如果一个被WATCH
命令监督的键被批改的话,整个事务都不会执行,间接返回失败。