一 、基本的操作命令(5种常用数据类型指令,通用指令)
5种常用数据类型:string set list sort_set hash
二、 jedis的基本使用
三、 redisTemplate
四、 数据持久的2种方式(RDB和AOF)
RDB:
指定的时间间隔内保存数据快照,把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发,自动触发和其它触发机制。
手动触发分别对应save和bgsave命令:
save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,先上环境不建议使用
bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一段时间很短,因此Redis内部涉及到RDB都采用bgsave命令
自动触发底层采用的是bgsave指令,最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。
其他触发机制
在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行 bgsave 命令,并将rdb文件发送给从节点,执行shutdown命令时,自动执行rdb持久化
AOF持久化:
AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态,也就是每当 Redis 执行一个改变数据集的命令时, 这个命令就会被追加到 AOF 文件的末尾, 重启时再重新执行AOF文件中命令达到恢复数据的目的,目前已经是Redis持久化的主流方式。Redis的AOF功能默认是关闭的,需要我们手动开启 ,AOF只会保存服务器所执行的写(set,del,add 等)的命令,对于其他对结果集并没有造成影响的命令是不会写入aof文件的,如(get、hget、lrang等)。
AOF重写:
AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以AOF文件的大小随着时间的流逝一定会越来越大,其中就包含了很多不必要的命令,如set某个key两次,其结果肯定为最后一次,那么前面一次的命令就没有必要再记录下来。为了解决此问题,Redis引入了AOF重写机制用于压缩aof文件大小,并且可以提高数据恢复的效率。
当开启aof功能时,默认采用aof方式恢复数据,不会采用rdb方式来恢复。只有把aof关闭redis才会去读取.dump文件来恢复数据
五 、发布订阅模式,HyperLogLog,BitMaps,GEO(了解)
六、 redis事务和watch
Redis 事务的本质是一组命令的集合。在redis中开启事务后,事务中的命令并不会立即执行,而是会推送到一个事务队列中,该队列积攒此次事务的所有命令,等到事务提交(执行)后,会逐步执行此队列中的命令,执行队列中的命令过程是一个整体,如果其中有一个命令执行失败,那么此次事务队列中的所有命令取消。不会被其他客户端所干扰。
注意:Redis事务中没有隔离级别的概念,因此不存在脏读、不可重复读、幻读等情况。
事务3阶段
开启:以MULTI 开启一个事务
入队:将多个命令入队到事务中,接到这些命令不会立即执行,而是放到等待执行的事务队列里面
执行:由EXEC命令触发事务
事务3特性
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
不保证原子性:redis同一个事务中如果有一条命令发生语法错误,执行EXEC命令后Redis就会直接返回错误,连语法正确的命令也不会执行。如果事务里的一条命令出现了运行错误,事务里其他的命令依然会继续执行(包括出错命令之后的命令)。
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行
七、 redis三种删除策略(定时删除,惰性删除,定期删除)
定时删除策略
在设置键的过期时间的同时,Redis内部会创建一个定时器,当key设置有过期时间,且过期时间到达时,或者执行了del的key,由定时器任务立即执行对键的删除操作
优点:定时器时间到了就执行删除,能够有效快速释放内存空间,节约内存
缺点:定时器时间到了就执行删除,在这一时刻如果CPU正在处理其他命令,那么CPU的负载会一下子变得非常高,会直接影响Redis处理请求的响应速度。
总结:牺牲CPU性能来释放内存(时间换空间)
惰性删除策略
数据到达过期时间不删除,等下次访问该数据时再删除。
优点:有效节约CPU性能,发现必须要删除时才删除。
缺点:如果数据过期后一直没有访问数据,那么将长期占用内存使用量,内存压力大
总结:牺牲内存使用量来换取CPU的性能(空间换时间)
定期删除策略
从定时删除和惰性删除策略来看,惰性删除浪费内存容易造成内存溢出,而定时删除影响CPU性能降低redis的响应速度和吞吐量。两种删除方式在不关是在CPU还是内存上都有些极端。定期删除策略则是一种折中办法。
特点:周期性轮询redis中数据的有效性,采取智能算法抽取的策略,对过期数据占比较大的数据库进行有效删除。
优点:智能根据数据库有效数据占用率来选择删除的粒度,并且检测频率可以根据CPU占用率适当调整。
redis底层采用的是定期删除+惰性删除策略
八、内存淘汰策略
我们知道Redis底层默认采用的是惰性删除+定期删除,如果定期删除没有删除的key,再加上也没有访问过这个key,那么redis内存的占用量会越来越高,假设此时来了新的数据,而redis的存储空间已经满了,那么此时redis的内存淘汰机制可以根据淘汰策略帮我删除一部分部分key来释放新的空间。
淘汰策略
allkeys-lru:在16个数据库(前提你没有修改databases)的所有key中,选择使用次数最少的数据进行淘汰
allkeys-lfu:在16个数据库的所有key中,选择最后一次使用离现在最长的数据进行淘汰
allkeys-random:在16个数据库的所有key中,随机选择一部分数据进行淘汰
volatile-lru:在所有有设置过期时间的key中,选择使用次数最少的数据进行淘汰
volatile-lfu:在所有有设置过期时间的key中,选择离最后一次使用离现在最长的数据进行淘汰
volatile-ttl:在所有有设置过期时间的key中,选择将要过期的数据进行淘汰
volatile-random:在所有有设置过期时间的key中,随机选择一部分数据进行淘汰
no-envication:放弃淘汰数据,redis4.0及以上版本默认策略
逐出算法
当新数据进入redis时,如果内存不足怎么办?
Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。
注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。
九、 模拟并发指令
使用redis自带的redis-benchmark命令来检测redis压力
十、 主从复制
redis官方数据显示可以达到10w的QPS
为了避免单点故障,数据存储需要进行多副本构建。同时由于 Redis 的核心操作是单线程模型的,单个 Redis 实例能处理的请求 TPS 有限。因此 Redis 自面世起,基本就提供了复制功能,而且对复制策略不断进行优化。
通过数据复制,Redis 的一个 master 可以挂载多个 slave,而 slave 下还可以挂载多个 slave,形成多层嵌套结构。所有写操作都在 master 实例中进行,master 执行完毕后,将写指令分发给挂在自己下面的 slave 节点。slave 节点下如果有嵌套的 slave,会将收到的写指令进一步分发给挂在自己下面的 slave。
通过多个 slave,Redis 的节点数据就可以实现多副本保存,任何一个节点异常都不会导致数据丢失,同时多 slave 可以 N 倍提升读性能。master 只写不读,这样整个 master-slave 组合,读写能力都可以得到大幅提升。
master 在分发写请求时,同时会将写指令复制一份存入复制积压缓冲,这样当 slave 短时间断开重连时,只要 slave 的复制位置点仍然在复制积压缓冲,则可以从之前的复制位置点之后继续进行复制,提升复制效率。
主库 master 和从库 slave 之间通过复制 id 进行匹配,避免 slave 挂到错误的 master。
Redis 的复制分为全量同步和增量同步。
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
Redis主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
十一、哨兵机制
哨兵工作原理
哨兵工作原理分为三个步骤:
1当哨兵启动时需要监控master与slave的状态,并且保证哨兵集群能够正常通信。我们把这个阶段称为哨兵监控阶段
2当哨兵发现master或者slave出现故障时,当前哨兵会通知其他哨兵,其他哨兵挨个发送ping命令来到出故障的redis节点,如果redis节点没有响应则哨兵集群认为此节点出现故障,我们把这个阶段称为哨兵通知阶段
3当哨兵集群已经发现某个节点出现故障时,如果是slave节点则直接剔除,如果是master节点出现故障,那么需要在多个slave中推选出一个新的master节点,我们把这个阶段称为故障转移阶段
哨兵监控阶段,主要完成三件事情:
1)监控master与slave的状态。
2)哨兵集群中保持正常的通信。
3)不同的哨兵节点之间通过数据的发布/订阅进行数据同步。
哨兵启动后会做如下事情:
1)info:哨兵启动每间隔10秒钟会向所有的master与slave节点发送info指令来获取信息,当有新的slave加入哨兵可以立马察觉到,获取到信息之后将信息保存到当前的哨兵节点中,同时也可以检测master与slave的状态信息(如有新的机器加入到集群中)。
2)ping:哨兵节点启动之后每隔1秒钟会向其他哨兵节点发送ping心跳,来检测其他哨兵节点是否正常。
3)pub/sub:每个哨兵节点每隔2秒钟会在一个指定的频道发布当前节点保存的master信息,其他哨兵节点都会订阅该频道获取消息。
哨兵通知
哨兵通知阶段会每隔一秒ping一下master与slave,如果得不到响应则会认为当前redis节点出现故障,哨兵通知主要是监控到master或slave出现问题之后进行的处理。
哨兵监控slave:
在哨兵集群中,如果有某一台哨兵节点发现某台slave节点出现故障后,那么则会在哨兵集群中发布消息,其他哨兵接收到消息之后,将此slave剔除。并将此节点标记为+sdown
,等到下次slave重新回复哨兵时,哨兵则会把此slave添加进主从复制集群,并将此节点标记为-sdown
哨兵监控master:
在哨兵集群中,如果有某一台哨兵节点发现某台Master节点出现故障后,当前哨兵节点会认为此master节点出现故障,此时当前哨兵标记此master节点为+sdown
,当前哨兵标记master状态为主观下线(即:单个哨兵认为此master节点有问题,有可能是网络抖动问题。)如果后续其他哨兵节点也发现master出现故障(ping没有回应)各自的哨兵节点都标记+sdown
,直至数量大于等于哨兵配置文件中的quorum(票数)时,那么哨兵集群认为此master出现故障,其他所有哨兵标记为odown状态
,此时master标记为客观下线。
故障转移
leader选举
如果主节点被标记为客观下线之后,就要在哨兵集群中选出一个哨兵节点来完成后面的故障转移工作,每个哨兵节点都能成为这个领导者(leader),选举流程如下:
1)在master节点被标记为客观下线时,哨兵节点会向其他哨兵节点发送is-master-down-by-addr
命令,表明自己想成为leader来处理master的故障转移。
2)当其它哨兵节点收到此命令时,可以同意或者拒绝它成为领导者;
3)当如果票数大于哨兵集群总数量的一半时将成为领导,如果没有大于,则继续开始下一轮选举(票数累加)。
哨兵在哨兵监控时就已经保存了master与slave的信息,选举出leader哨兵之后,哨兵要开始进行master与slave的故障转移了。在众多的slave直接推选出一个slave充当master节点。
选举master流程如下:
1)首先过滤已经下线的slave节点
2)优先级高的推选为master节点,默认都为100(在 info Replication组,可查看优先级:slave_priority参数)
3)推选出复制偏移量(offset)最大的节点(offset大,意味着和原master节点的数据最相近)
4)推选出run_id最小的节点。
当故障转移后,需要更新当前的主从状态。
更新状态如下:
1)被选举出来的新master将执行slaveof no one
,断开与原master的连接,并在其他slave的配置文件上加上slaveof
命令。
2)将已经剔除的主节点设置为新主节点的从节点,后续恢复正常时,将其变为从节点,并在其配置文件中加上slaveof
命令。
在redis哨兵环境中,需要注意的是,当原master恢复正常时,不会重新"抢回"master的位置,原来的master将变为slave节点,这与nginx的非抢占模式是一样的道理。
master恢复正常后,其他的哨兵标记master状态为-sdown
。
十二、 集群
十三、缓存穿透,缓存击穿,缓存雪崩
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。比如查询id=-1,-2,-3...,所有的请求都会在redis中查不到,然后全部查询数据库。
解决方案:
1最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
2另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。如果大量请求中存在有重复的数据,那么第二次以后的操作都会查询到redis,减少MySQL压力
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。比如同一时刻,大量的相同的一个请求查询,刚好这时redis中数据失效,结果这些大量的请求最终全部查询了数据库。
解决方案:互斥锁
缓存雪崩指某一时间大量缓存过期,请求直接来到数据库,造成数据库压力过载。与缓存击穿不同的是缓存击穿通常是大的并发量访问某一条数据,而某一条数据正好失效。而缓存雪崩是大量的数据同时失效,多个key累加起来的请求流量压力过大。很多数据都查询不到,从而到数据库查询,数据库压力飙升。
解决方案:
1在原有的失效时间基础上增加一个随机值,防止同一时间大量缓存失效
2加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写
十四、分布式锁