赞
踩
参考:图解Redis介绍 | 小林coding (xiaolincoding.com)
目录
快,就是快。
Redis基于内存的数据库,对数据的读写都是在内存中完成的,所以它的读写速度非常快,适用于服务器与数据库间的缓存,还可以用来做分布式锁,秒杀,消息队列。
1 因为它是基于内存的数据库,对数据的读写都是在内存中完成的。
2 并采用了高效的数据结构,cpu不是瓶颈,自然采用单线程的解决方案)
这句话多意思就是说,cpu并不是影响redis读写操作速度的因素
其次是它采用的是单线程,相比多线程快,避免了多线程之间等竞争,
省去了多线程切换带来的时间和性能上的开销,也不会导致死锁问题
3 redis采用了I/o多路复用机制处理大量的客户端socket请求,啊啊啊这是神马!
因为redis具备高性能,高并发两种特性
redis具备高性能:
用户第一次访问mysql中的某些数据,这个过程会很慢,因为是从硬盘上读取的。将该用户访问的数据缓存在redis当中,下一次访问的时候就可以直接从缓存中获取了,
操作redis直接就是操作内存,所以速度非常快
redis具备高并发:
补充:直接访问redis能够承受的请求上远远大于直接访问mysql的,把数据库中的部分数据放到redis中,这样一部分请求会直接到缓存里而不用经过数据库
String,Sett,Zset,Hash,Lsit
AOP日志:每执行一条执行操作命令,就把该命令以追加的方式存到文件当中
RDB快照:将某一时刻的内存数据,以二进制的方式写入磁盘
混合持久化:就是集成aop和RDB快照这两种方式的优点
持久化的意思是什么,就是说我们接收客户端写入,第一次读写数据库是MySQL读写的,是在硬盘当中读写的,所以会很慢,然后下一次重启redis的时候之前的数据都消失了,那怎么办呢?就是将这些数据存入redis当中,下一次访问的时候就不用再经过数据库了,直接来到redis里面读写,所以会很快
补充:持久化机制会把数据存储到磁盘,这样在redis重启就能够从磁盘中恢复原有的数据
redis单线程指的是:当接收到客户端请求后,redis就解析这个请求,并进行数据读写操作,然后发送给客户端
redis是单线程的,但是redis程序不是单线程的。
因为在redis启动的时候,后台线程也启动了,然后把这些线程放到主线程当中处理,就容易造成线程拥塞
关闭文件任务队列:当队列有任务后,后台线程会调用关闭文件任务队列关闭文件
AOF刷盘任务队列:刷盘
lazy_free任务队列:释放
redis采用单线程为什么还这么快?
1 redis大部分操作都是在内存中进行的,所以快
2 redis采用单线程模型可以避免多线程之间的竞争
3 redis采用了i/o多路复用机制
redis6之前还在用单线程,redis6版本之后引入了多线程,但是执行命令的还是单线程,不代表执行的是多线程命令
补充:redis6.0版本引入的多线程i/o特性对性能的提升至少是一倍以上
其次不会出现多线程的那些问题,多线程会比较复杂,还有线程切换,加锁解锁,出现死锁问题等
满足分布式系统或者集群模式下多线程可见并且互斥。
1分布式系统 集群模式
2多线程可见 互斥
四个关键词
可靠性,安全性,对称性,互斥性。
可靠:不管做什么都要考虑它可靠不可靠吧
互斥:就是要保证每一个竞争者都有自己的锁
补充:锁的目的是获取资源的使用权,所以只让一个竞争者持有锁,这一点要保证啊,
安全:能够避免死锁情况发生(我去这里乱套了把对称性的点都写到这来了)
补充:当一个竞争者在持有锁期间,由于意外崩溃-》而导致未能主动解锁,不要慌,其持有的锁也能被正常释放 并保证后续其他竞争者也能加锁
对称性:同一个锁,加锁和解锁都要在同一个竞争者上,不能其他的竞争者释放了锁,
可不可以用一条语句来完成加锁操作
可以。
补充:Redis的set命令有个NX参数可以实现(key不存在才插入),所以可以用它来实现分布式锁
key不存在,则插入成功,加锁成功
key存在,则插入不成功,加锁失败
redis在执行完一条操作命令后,就会把改命令以追加的方式写入一个文件里,然后redis重启时,会读取文件记录的命令,然后逐一执行的命令来进行数据恢复
为什么要先执行命令,然后再把数据写入日志呢
为了避免额外的检查开销
你要是先把命令写入日志再执行,那日志里的命令是对是错都不知道,redis在使用日志恢复数据的时候就容易出错
不会阻塞当前操作命令的执行
那肯定啊,你在那写入日志是不是就会影响了操作命令的执行效率
缺点:
数据可能会丢失:执行命令和写入日志是俩进程,你在执行命令的时候还没来得及写入日志,服务器就宕机了。。。
可能会阻塞其他操作的执行,具体是什么操作我也不知道
AOF日志文件
AOF重写工作原理:就是客户端写入两条命令,一条是set name xiaolin,另一条是set name yixiaolin,然后redis执行这两条命令,最后以一条命令set name yixiaolin写入AOF日志文件当中
补充:扫描数据库中所有数据,逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志
就是拿来避免AOF文件越写越大的问题,因为客户端写入多条命令一直在写它一直在存总是会存的过多吧。
RDB快照
目前记得的就是
900 秒修改 1次
600 秒修改 3次
6 秒修改10000次
还有save 和bgsave,他们的区别就是是否造主线程中执行
执行save命令 会阻塞主线程
执行bgsave命令 避免主线程的阻塞
正所谓有了问题就去解决它
混合持久化方式
就是混合了他俩的优点
使用了混合持久化,AOF文件的前半部分是RDB格式的全量数据,后半部分是AOF格式的增量数据
主从复制,哨兵模式,切片集群模式
主从复制:一主多从,主从分离
补充:主从复制是redis高可用服务的最基础的保证
哨兵模式:哨兵发现主节点下线了失联的时候负责替换新旧主节点的
补充:提供主从节点故障转移的功能
切片集群模式:这个太复杂了不好说
在redis主从架构中,部署方式一般是一主多从,主节点提供写操作,从节点提供读操作
主节点的网络突然发生问题,它与所有的从节点失联了,但是测试的主节点和客户端的网络是正常的,这个客户端并不知道redis内部已经出现了问题,还在照样的向失联的主节点写数据,此时这些数据被旧节点缓存到了缓冲区里,因为主节点之间的网络问题,这些数据都是无法同步给从节点的
哨兵以为主节点挂了,就从中选一个leader来上任主节点。然后网络突然恢复正常了,旧主节点回来了,这时哨兵将其变为从节点,从节点请求和新主节点数据同步,因为第一次同步是全量同步的方式,只能先把自己本地的数据全部清空,这时之前客户端写入的数据全部没了,丢失了,这就是集群产生脑裂数据丢失的问题
总结一句话:由于网络问题,集群节点之间失去联系。主从数据不同步,重新平衡选举,产生两个祝福我,等网络恢复,旧主节点会降级为从节点,再与新节点进行同步赋值的时候,由于从节点会清空自己的缓冲区,所以导致之前客户端写入 的数据丢失了
解决什么呢,解决最后从节点和新的主节点数据同步后以前的数据没有丢失的问题
怎么解决呢,就是在redis配置文件当中有两个参数设置:
min-slaves-to-write x
min-slaves-max-lag x
把这两个配置项搭配起来使用,分别给他们设置一定阀值
防止在主节点失联了之后客户端还在一直给数据库里面写入数据,同时也避免了主库去接收客户端写入的数据
补充:原主库就会被限制接收客户端血请求,也不能在原主库中写入新数据
就是主节点都失联了下线了,哨兵还没有安排新的主节点的时候那会儿数据库一直在写入数据但是没用,所以就是解决这个问题的。进行这样的操作之后,就算有新的主节点并且原来的主节点变成了从节点,把自己的数据清空后与新主节点实现全量同步了,原来的数据也不会丢失
嗯会给每一个key设置一个过期时间,既然超过这个过期时间,那这个健值对是不是就没用了。没用了怎么办,给他删掉啊
怎么删,先是这样的,我们有一个过期字典,每给一个key设置一个过期时间的时候,就会把这个已经设置过期时间的key和过期时间保存在过期字典里面
每次在里面查询一个key值,都要检查是否过期,和当前系统的时间进行比对,超过当前系统时间就说明已经过期了
redis使用的过期删除策略
1 惰性删除策略
惰性删除策略就是指用到的时候就去查询这个key,然后检测它是否已过期,过期了的就删掉
优点:减小cpu的压力,自己也少忙活一点,确实挺惰性的
缺点就是删的太少频率也低,容易造成key存的太多了会占用空间资源
2 定期删除策略
就是定期的,规律的去删除键,每一次定期删除过程中用的时间大约25ms
优点就是减少了空间资源的占用,每次定期删除把已过期的该删了的删了
缺点就是给cpu带来了巨大的压力
所以redis选择两者配合使用
持久化文件有两种格式:AOF日志文件和RDB快照处理
AOF和RDB
RDB文件分两个阶段:
RDB文件生成阶段:从内存状态持久化成RDB的时候,会对key进行过期检查,过期的键不会对生成新RDB问价产生任何影响
RDB加载阶段:看是主服务器运行模式还是从服务器
主服务器运行模式的话,在载入RDB文件时程序会对文件中保存的键进行过期检查,过期了的键自然不要了,不会保存
从服务器模式的话,他不管有没有过期都存,但是最后从服务器里面的数据都是要清空掉的,所以不会有影响
AOF文件分为两个阶段:
AOF写入阶段:如果数据库中某个过期键还没有被删除,AOF文件就会保留此过期键
如果这个过期键被删除了,那么redis就会向AOF追加一条del命令来显示的删除该过期键
AOF重写阶段:会在执行之前检查有没有过期,过期了的不会存入重写后的AOF文件当中,联想到之前的什么是AOF重写就知道什么意思了
先执行命令,查看是否过期,然后再写入AOF文件
从库是不会自己主动去删除过期键的,他说被动的,只有主库在key到期的时候向AOF文件里面增加一条del命令的时候从库才会去执行命令删除
redis内存满了,会发生什么
内存满了就该淘汰的淘汰啊
运行内存到达了某一个阀值,就会出发内存淘汰机制,这个阀值在配置文件里面能看到,maxmemory
1 不进行数据淘汰的机制
2 进行数据淘汰的机制
LRU:least recently used选择最近最少使用的数据淘汰
LFU:least frequently used 选择最近最不常使用的数据使用
redis进行内存淘汰机制的时候,会随机取样几个值,几个值自己配置,然后选择最不常用的那个淘汰掉
LFU算法:
缓存雪崩:当redis缓存里面的大量数据都是在同一时间过期失效的,同时大量用户来缓存里面请求数据请求不到,就会直接去数据库里面请求数据,会给数据库带来巨大的压力,容易系统奔溃
解决方案:
1 打散缓存数据的过期时间:这样就不会同时过期失效了
2 不给数据设置过期时间
缓存击穿:就是类似于秒杀这些数据,属于热点数据,大量用户又热衷于这个数据就会来缓存里请求,但是该热点数据都没了,所以他们就会直接去数据库请求,会给数据库造成巨大压力
差不多和缓存雪崩一样,应该可以说是缓存雪崩的子集
解决方案:
1 互斥锁(这不就是实现黑马点评中秒杀下单一人一单的问题嘛)redis中使用setNX方法设置一个状态位,表示这是一种锁状态。保证一个业务线程只能持有一把锁,每拿到锁的等锁释放之后才能持有
2 不给热点数据设置过期时间
缓存穿透:表示缓存和数据库里面根没有用户想要请求的数据,但是大量用户还是要来缓存和数据库里请求,给数据库带来巨大的压力
解决方案:
1 非法请求的限制:因为造成缓存穿透的原因可能是黑客故意搞定,明知道缓存和数据库里面没有还是要来请求,怎么限制呢,,新判断该请求是不是非法的,直接返回空和默认值
2 给这些不存在的数据设置成空和默认值,下次他们再来请求数据就直接给返回空或默认值,不要再来访问了
3 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在
在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过布隆过滤器判断数据是否存在,即使发生了缓存穿透,大量请求只会查询缓存和布隆过滤器而不是查询数据库,就是别去沾边数据库,保证了数据库能正常运行
热点数据,就是在近期被访问的次数最多,近期被访问的数据了
由于数据存储受限,不是所有的数据都被存到缓存里面的,存的大部分都是热点数据,没被访问过的数据是不存进去的,占用空间,所以要设置动态缓存热点数据的策略
策略总体思路:通过最近最新访问时间来做排名,并过滤掉不经常访问的数据,只留下经常被访问的数据
场景题:
现在要求只缓存用户经常访问的top1000的商品
1 先通过缓存系统做一个排序队列(比如存放1000个商品),系统会根据商品的访问时间,更新队列信息,越是最近访问的商品排名越靠前
2同时系统会定期过滤掉队列中排名最后的两百个数据,然后再从数据库中随机读取两百个数据放到队列中,就这样一直排名一直过滤一直读取
3 这样当请求每次到达的时候,会先从队列中获取商品id,如果命中就根据id再从另一个缓存数据中读取世纪的商品信息,并返回
在redis中用zadd方法和zrange方法来完成排序队列和获取200个商品的操作
常见的缓存更新策略:
旁路缓存策略
读写/写穿策略
写回策略
旁路缓存策略:
写策略的步骤:先更新数据库里的数据,再删除缓存里的数据
读策略的步骤:看用户的请求是否命中缓存(就是说看缓存里有没有用户请求访问的数据),命中则直接返回给用户,没有则先去数据库里写入读取数据,然后写入缓存,再返回给用户
写策略的顺序不能反啊,反了会影响到缓存里的数据和数据库里的数据的一致性
怎么说呢,就好比你是20岁,此时你在缓存里和在数据库里都是20岁,用户A请求你变成21岁,然后直接把缓存里20岁的你给删了,此时用户B来了,请求20岁的你,就把你放到缓存里,还是20岁。但是用户A已经把数据库里的你变成21岁了,最终你在缓存里20岁,在数据库里21岁,就是这么个意思
缓存的写入远远快于数据库的写入,所以可想而知了
读穿 /写穿策略:
读穿:和上面的读策略差不多只是,只是多了个缓存组件这几个字
看用户的请求是否命中缓存,命中则直接返回,没命中则由缓存组件负责从数据库中查询数据,并将结果写入缓存,再通过缓存组件返回给用户
写穿:当有数据更新的时候,先查询要写入的数据在缓存中是否已经存在
如果缓存中数据已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,然后缓存组件告知应用程序更新完成
如果数据不存在,直接更新数据库,然后返回
呃反正最后就是先更新数据库,再删除缓存
为什么是删除而不是更新缓存呢
因为更新花的时间比删除多
延迟是什么意思,就是到点了本应该结束这件事的,但是你要给他再多等一会儿再结束
在Redis中可以使用有序集合Zset来实现延迟消息队列,而Zset中的score就是拿来存储延迟的时间的
使用zadd score1 value1就可以一直往内存中生产消息,再用zrangebyscore查询条件中所有待处理的任务,通过循环执行队列任务即可
redis当中的大key怎么处理
redis中的大key是什么,不是指key的值很大,是指key对应的value很大
大key会造成什么问题?
客户端超时阻塞:客户端那边访问的时候跳转网页会特别慢
阻塞网络:网络也会很不好
阻塞工作线程
内存分配不匀
Redis事务支持回滚吗
Mysql在执行事务时,会提供回滚机制,当事务执行发生错误时,事务中所有的操作都会撤销,已经修改的数据也会被恢复到事务执行前的状态
Redis中并没有提供回滚机制
就是怎么用一句命令实现分布式锁呢,Redis的Zset命令中有个NX参数,可以实现「key不存在才插入」,所以可以用它来实现分布式锁
当key不存在,则加锁成功,加锁成功就没有key了呀
当key存在,则加锁失败,就这么个意思
基于Redis节点实现分布式锁对于加锁操作,要满足三个条件:
加锁包括了读取锁变量,检查锁变量,设置锁变量
锁变量需要设置过期时间
锁变量需要区分出来自不同客户端的加锁操作,以免释放锁时出现误释放操作
优点:
性能高效,自己从Redis是基于内存的数据库来想就知道了
实现方便,肯定要方便啊,不方便会用它吗
避免单点故障
(因为Redis是跨集群部署的,自然就避免了单点故障)
缺点:
超时时间不好设置
怎么设置超时时间呢:就是搞个续约,差不多到点了要超时了就给他续约上,等到主线程执行完成,销毁续约锁即可
补充:基于续约的方式设置超时时间,先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间后,重新设置这个锁的超时时间
写一个守护线程,然后去判断锁的情况,当锁快要失效的时候再次进行续约加锁,当主线程执行完成 销毁续约即可 不过搞挺复杂的
Redis主从复制中模式中的数据都是异步复制的(异步代表同时进行哈),这样会导致锁的不可靠性
分布式锁算法:redlock红锁算法
redlock算法思路:让客户端和多个独立的redis节点去申请加锁,当客户端和超过半数的redis节点都申请加锁成功就说明该客户端成功的获得分布式锁,否则失败
redlock算法三个过程:
1 让客户端去获取当前时间t1
2 客户端按顺序依次向n个redis接单执行加锁操作
加锁操作使用set命令,带上NX,EX/PX选项 以及带上客户端的唯一标识
如果某个redis节点发生故障了,为了保证在这种情况下redlock算法能够继续运行,。。。。。。。就很拗口不想写了
加锁成功要满足两个条件:如果有超过半数的redis节点获取到锁,并且总耗时没有超过锁的有效时间,就是加锁成功
加锁失败后,客户端向所有的redis节点发起了释放锁操作,失败了你也不能一直握着锁不放啊,所以要给他们释放掉,操作和在单节点上释放锁的操作一样,只要释放锁的lua脚本就行
好了,到这里关于Redis的一轮总结全部结束了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。