导图社区 reids
这是一篇关于reids的思维导图
编辑于2021-09-22 11:30:51redis
什么是redis
Redis是是C语言编写的,开源的高性能非关系型的key-value数据库, Redis把数据保存在内存中,所以读写速度非常快,每秒可以处理超过10W次读写,是目前一直最快的key-value数据库
数据结构
五大基本结构
字符串 String:最简单基本的数据结构,可以用来实现 Session共享、分布式锁,分布式ID,计数器等
列表 List: 通过使用命令,lpush、lpop、rpush、rpop来实现队列或者栈;例如可以用来保存评论数据
散列表 Hash:可以用来保存一些简单对象
集合 Set:和列表类似,可以存储多个元素,但是不能重复,集合可以进行交集、并集和差集等操作,可以实现共同关注等功能
有序集合 Zset:常用语排行榜
其他数据结构
位图 Bitnap: 布隆过滤器
坐标 Geo:主要用于存储地里位置,并对数据进行操作;例如:给定一个坐标点可以计算出周围多少距离的其他坐标点,可用于打车软件
HyperLo gLog:基数计算,用来统计不重复数据,优点是计算基数所需的空间总是固定的、并且很小的
发布订阅 pub/sub:一种消息通信模式
消息队列 Streams:保存在内存中的消息队列
Redis为什么那么快
1、缓存内存操作
2、采用的是非阻塞的IO多路复用机制,同是监听多个Socket,根据Socket的类型采用不同的处理机制
3、单线程避免了多线程上下文切换
持久化方式
RDB
Redis DataBase:将某一时刻的内存快照以二进制的形式写入磁盘,不会保存已过期的key
手动触发
save命令:使Redis主线程进入阻塞状态,知道RDB完成,期间不执行客户端请求的命令
bgsave命令:Redis fork一个子进程来进行持久化,主线程在fork的时候会出现短暂的阻塞,等fork完成就会立即执行客户端请求,
自动触发
save m n:意思是在m秒内n个键发生变化就会自动触发持久化,执行bgsave命令
flushall:当清空全部或当前数据库时,会清空RDB文件
主从同步:全量同步会触发bgsave命令,生成RDB文件发送给从节点
优点
整个Redis只有一个RDB文件,方便持久化
容灾性好,方便备份
性能最大化,fork一个子进程来实现持久化,主线程继续执行客户端的其他命令
在恢复大数据集的时候,效率比AOF高
缺点
数据安全性低,RDB是间隔一段时间来进行持久化,如果在两次持久化之间发生故障,则会造成数据丢失
由于RDB是通过fork子进程来进行持久化,当数据集较大时可能会造成服务器短暂的暂停服务
Linux fork子进程采用的是copy-on-write的方式。在Redis之执行RDB持久化期间如果客户端写入数据很频繁,那么将增加Redis内存占用的内存,最坏的情况下内存占用将达到原先的2倍。刚fork的时候,主进程和子进程共享内存,但是随着主进程要处理写操作,逐渐向需要将修改的页面复制一份处理出来,然后进行修改,在极端情况下,如果所有的页面都需要修改,则此时占用的内存是原先的2倍
AOF
Append Only File:以log日志的方式记录每一个写操作,依次追加到文本末尾,
实现可分为三步
命令追加:服务器在执行完一个命令后,都会将被执行的写命令追加到服务器状态的 aof_buf缓冲区 的末尾
文件写入:将aof_buf中的内容写到磁盘上
文件同步:Linux为了提高性能,使用了页缓存,当我们将aof_buff的内容写到磁盘时,此时并没有真正的落盘,而是在page cache中,为了讲page cahe中的数据真正的落盘,需要执行fsync/fdatasync命令来强制刷盘,然后再对AOF文件进行同步
AOF 持久化的效率和安全性
服务器配置appendsync选项的值直接决定AOF持久化功能的效率和安全性
always:每处理一个命令都将aof_buf缓冲区中的所有内容写入并同步到AOF文件中,即每个命令都刷盘
everysec:将aof_buf缓冲区中的所有内容都写到AOF文件,并且每隔一秒就要在子线程中对AOF文件进行一次同步进行刷盘
no:将aof_buf缓冲区中的所有内容写入到AOF文件,单不对AOF文件进行同步,至于何时同步由操作系统决定
优点
AOF别RDB可靠
AOF是一个纯追加的日志文件,即使日志因为某些原因包含了未写入完成的命令,我们也可以使用 redis-check-aof工具也可以轻易的修复这种问题
当AOF文件太大时,Redis会自动在后台进行重写:重写后的新的AOF文件包含了恢复当前数据集所需的最小命令集合。重写是在新的文件上进行的,同时Redis会继续在旧的文件追加数据。当新文件重写完毕,Redis会把新旧文件进行切换,然后开始把新的数据写入到写文件。重写是并不需要读取现有的AOF文件,而是通过读取服务器当前的数据库状态来实现的。
触发条件
auto-aof-rewrite-min-size 64MB // 当文件小于64M时不进行重写
auto-aof-rewrite-min-percenrage 100 // 当文件比上次重写后的文件大100%时进行重写
从主进程fork一个子进程,读取当前Redis服务器状态,写入一个新的的AOF文件
在重写过程中Redis收到的写命令会同时写到AOF缓冲区和重写缓冲区中,这样保证不丢失重写过程的操作命令
重写完成后通知主进程,主进程会将AOF重写缓冲区的数据追加到子进程生成的文件中
Redis会原子的将旧文件替换为新文件,并开始将数据写入到新的AOF文件中
AOF文件顺序的保存了对Redis的写操作,内容非常容易被人读懂,对文件分析也很简单。如果不小心执行了FLUSHALL命令,这时候这要AOF文件没有被重写,我们只要停止服务器,期初AOF文件末尾的FLUSHALL命令,并重启Redis,就可以将数据集恢复到FLUSHALL之前的状态
缺点
AOF文件一般比RDB文件大,且恢复速度慢
根据同步策略(fsync)不同,AOF在运行效率上往往慢于RDB。通常fsync设置每秒一次就能获得比较高的效率,关闭同步则效率和RDB一样
总结
AOF文件一般比RDB文件大,也更安全
RDB性能比AOF好
生产环境两个都开启
如果两个都配置了,优先加载AOF
Redis过期key删除策略
惰性删除
只有当访问一个key时,才会判断这个key是否已经过期,过期则删除;
优点,节省cpu资源,
缺点,当存在大量过期key的时候,如果这些key都不会被访问,那么这些key会一直存在内存中,占用大量内存资源
定期删除
每隔一定的时间,会对一定量数据库中设置expires的key执行一次检查,并随机抽取部分过期的key来删除;为什么是随机?假如reids存了几十万key,每个100ms就遍历所有设置了过期时间的key,会占有大量的cpu资源。Redis2.6版本规定没100ms执行一次,没秒10次,2.8版本之后可以修改hz来调整次数
优点:可以通过限制删除操作执行时长和频率来减少删除操作对cpu的影响,也可以有效的删除过期的key
缺点:难以确定删除操作的执行时长和频率;如果执行太频繁就和定时删除一样对cpu不友好;如果设置的时间长就和惰性删除一样对内存不友好;最最重要的是万一某个key已经过期了,但是还没有执行删除操作,那么还是会返回这个kiey对应的数据
定时删除
在设置key过期时间的同时创建一个定时器,当到了过期时间,立即执行删除操作
优点:对内存最友好,能够保证内存中的key一旦过期立即删除
缺点:对cpu不友好,当同一时间大批量key过期时,删除key会占用cpu时间,会影响服务器的吞吐量和响应时间
Redis采用的 定期删除 + 惰性删除
内存淘汰策略
lru: 最近最少使用,根据最近被使用情况,淘汰距离当前时间最远被使用的数据
allkeys-lru:利用LRU算法删除任何key
volatile-lru:利用LRU算法删除设置了过期时间的key
allkeys-random:无差别随机删除
volatile-random:随机删除设置了过期时间的key
allkeys-lfu:从所有键中删除使用频率最少的键
volatile-lfu:从所有配置了过期时间的键中删除使用频率最少的键
volatile-ttl:从配置了过期时间的键中删除马上就要过期的键
noeviction:不删除任何key,只返回一个错误写错误,读功能正常,默认是这个
Redis如何设置过期时间?原理?
设置过期时间
exprire
setnx
实现原理
惰性删除
定期删除
常见问题
缓存击穿
击穿是一个热点key失效,有大并发对其进行访问,导致请求都落到数库上,导致数据库压力激增
解决方案
设置热点key永不过期
加锁排队
缓存穿透
查询缓存和和数据库中都不存在的数据,导致所有的请求的落到数据库上
解决方案
对参数进行合法化校验,例如 id <= 0的直接拦截
把无效key也保存在Redis中,但是如果无效key过多容易造成资源浪费
使用布隆过滤器,把肯定存在的数据保存在一个足够大的bitmap中,每次请求前都进行存在性校验
缓存雪崩
同一时间大面积key失效,导致所有的请求都打在数据库上,造成数据库短时间内承受大量的请求而崩掉
解决方案
key过期时间加一个随机值,防止同一时间大面积数据过期
热点数据设置永不过期,有更新操作主动刷新缓存
加锁排队,缓存失效后没通过加互斥锁或者队列来控制读数据库的线程数
布隆过滤器
实现:它实际上是一个很长的二进制向量和一系列随机映射函数 原理:当一个元素被加入假如集合时,通过K个散列函数将这个元素映射成数组中的K分点,并把它们从0改为1; 检索时,我们通过同样的办法把元素映射为数组中的K个点,并检测这些点是否都为1,如果都是1则元素可能存在,如果有一个不为1那么元素一定不存在
优点
占用内存小
效率高,增加和删除的时间复杂度都为O(K), K为哈希函数个数
哈希函数之间相互独立,可以并行
不保存元素本身,保密性强
缺点
有误判率,即存在假阳性,至于假阳性我们可以在业务代码中处理,例如可以在Redis中保存一个value为null的值
不能获得元素本身
一般情况不能删除元素,如果一定要删除元素,可以创建一个带有计数的过滤器,删除元素的时候要重新计算计数
常见应用常见
爬虫对URL去重
垃圾邮件过滤
数据一致性
延迟双删
先删除Redis数据
再更新数据库
更新成功后休眠一会再删除Redis中数据,防止更新期间其他业务读取旧数据
订阅binlog日志异步删除
利用阿里的canal将binlog日志采集发送到MQ队列中,然后通过消费者删除Redis中的数据,保证数据缓存一致性
分布式锁
分布式锁一般有如下特性
并不是所有分布式锁都需要支持以下特性,根据具体业务场景,可以只使用其中的一个或多个特性
互斥性:同一时刻只能有一个线程持有锁
可重入性:同一节点上同一个线程,允许重复加锁
锁超时:一旦锁超时即时释放拥有的锁资源,防止死锁
高性能和高可用:加锁、解锁需要高效,同时也需要保证高可用,防止分布式锁失效
非阻塞性:支持获取锁的时候直接返回成功或失败结果,而不是在获取锁失败时阻塞住线程
公平锁和非公平锁:公平锁是按照请求加锁顺序获取锁,非公平锁是加锁顺序是无序的
实现
setnx + setex:setnx保存key,不存在key保存成功,存在保存失败,成功后设置超时时间
过程
key不存在是创建,并设置value和过期时间,返回1;加锁成功
如果key存在直接返回0,加锁失败
持有锁的线程手动删除key,或者过期时间到,key自动删除了,释放锁
问题
如果setnx成功,但是expire设置失败,一旦出现没有手动释放锁,那么这个锁就会被永远占用,其他现在永远也抢不到锁
如果业务处理时间过长,到了超时时间,业务还没有处理完,那么锁会自动释放
解决
可以把两个命令合成一条命令执行
使用lua脚本,将加锁的命令放在lua脚本中原子性执行
对于问题二我们可以使用Redission,其可以自动续期
事务实现
集群方案
分布式系统常见缓存方案
变慢的原因
https://mp.weixin.qq.com/s?__biz=MzAwNDA2OTM1Ng==&mid=2453150932&idx=2&sn=3999a97c3e74b30009ddfede258a3a5d&scene=21#wechat_redirect