目录
Redis介绍
Redis五种数据类型
1 字符串类型(掌握) String=String 例如:s1="aaa"
2 散列类型(了解) String=hash 例如:s2=key:value
3 列表类型(了解) Stirng=list 例如:s3=a,a,b,b,c,d
4 集合类型(了解) String=set 例如:s4=a,c,d,b
5 有序集合类型(了解) String=zset 例如: s5=a,b,c,d
redis通用操作
一、单点Redis的问题
1.1 数据丢失问题
1.2 并发能力问题
1.3 故障回复问题
1.4 存储能力问题
二、问题解决
2.1 数据丢失问题解决方案
2.1.1 RDB持久化
1. RDB持久化执行时机,在以下四种情况下执行
2. 思考 save和bgsave有什么区别
3. RDB原理
3. RDB缺点
2.1.2 AOF持久化
1. AOF配置
2.1.3 RDB与AOF对比
2.2 并发能力问题的解决
2.2.1 Redis主从集群
1. 主从数据同步原理
全量同步:
2. 思考 :master如何得知salve是第一次来连接的?
增量同步
2.2.2 Redis哨兵集群
集群监控原理
集群故障恢复原理
小结
2.2.3 Redis分片集群
三、 雪崩、击穿以及穿透问题
Redis是用C语言开发的一个 开源的 高性能 键值对(key-value)内存数据库
字符串,散列,列表,集合,有序集合
设置
set key value
获取
get key
删除
del key
设置单个
hset key subkey subvalue :设置一个键值对
获取单个
hget key subkey:获取一个子键的值
设置多个
hmset key subkey1 subvalue1 subkey2 subvalue2 ...:设置多个键值对
获取多个
hmget key subkey1 subkey2...:获取多个子键的值
获取所有属性以属性值
hgetall key:获取指定key值的所有信息
删除-子键
hdel key subkey1 subkey2 ...
删除
del key
两端的设置:
lpush key member1 member2.. : 往左边开始插入
例如:lpush l1 a b c d d,c,b,a
rpush key member1 member2.. :往右边开始插入
例如:rpush l1 a b c d a,b,c,d
lrange key startindex endindex:查看 例如:lrange key 0 -1 :全查
两端的删除:
lpop key :左边弹出一个
rpop key :右边弹出一个
sadd key member1 member2 :设置
srem key member1 member2 :删除
smembers key :查看
zadd key score1 value1 score2 value2...: 设置添加
zrem key value1 value2... :删除指定成员
zcard key :展示元素的长度
zscore key value :获取成员的数字
1 keys *:查询所有的key
2 exists key:判断是否有指定的key 若有返回1,否则返回0
3 rename key 新key:重命名
4 type key:判断一个key的类型
5 del key :删除
Redis是内存存储,服务重启可能会丢失数据。
单点Redis并发能力虽然不错,但是也无法满足如6.18,11.11这样的高并发场景。
如果Redis宕机,则服务不可用,需要一种自动的故障恢复手段。
Redis基于内存,单节点能存储的数据量难以满足海量数据需求。
Redis有两种持久化方案:
RDB(Redis Database Backup file)Redis数据备份文件,也被叫做Redis数据快照。简单的来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,回复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
Redis停机时汇之星一次save命令,实现RDB持久化。
Redis内部有触发RDB的机制,可以在redis.conf文件中找到。
格式:
# 900s内,如果至少有1个key被修改,则执行bgsave,如果是save "",则表示禁用RDB
save 900 1
# 900s内,如果至少有10000个key被修改,则执行bgsave,如果是save "",则表示禁用RDB
save 900 10000
弊端:898s有10000个key被修改了,但是此时突然断电了,是无法保存的。
#在redis.conf文件中也可以配置如下配置
#1.是否压缩,建议不开启,压缩会消耗cpu
rbdcompression no
#2.RDB文件名称
dbfilename dunp.rdb
#3.文件保存的路径目录
dir ./
save命令是被主进程执行RDB,在此过程中其他命令都会被阻塞。只有在数据迁移时可能用到。
bgsave命令是被独立进程完成RDB,主进程可以持续处理用户请求,不受影响。
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork之后读取内存数据并写入RDB文件
fork采用的是copy-on-write技术:
AOF(Append Only File )追加文件。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:
#表示每执行一次写命令,立即记录到AOF文件 1281行
appendonly yes
#AOF文件的名称 1256行
appendfilename "appendonly.aof"
AOF的命令记录的频率也可以通过redis.conf文件来配置
#表示每执行一次写命令,立即记录到AOF文件 1281行
appendfsync always
#写命令执行完先放入AOF缓冲区,然后表示每隔1S将缓冲区数据写到AOF文件,也是默认的方案
appendfsync everysec
#写命令执行完先放入AOF缓冲区,由操作系统决定如何将缓冲区内容写回磁盘。
appendfsync no
配置项 | 刷新时机 | 优点 | 缺点 |
Always | 同步刷新 | 可靠性高,几乎不丢失数据 | 性能影响大 |
everysec | 每秒刷新 | 性能适中 | 最多丢失1秒数据 |
no | 操作系统控制 | 性能最好 | 可靠性较差,可能丢失大量的数据 |
2. AOF文件重写
因为AOF记录的是命令,AOF文件会比RDB文件大得多。而且AOF会记录同一个key的多次写操作,但是只有最后一个操作才有意义。通过执行bgrewrite命令,可以让AOF文件执行重写功能,用最少的命令达到相同的效果。
Redis也会在触发阈值时自动去重写AOF文件。阈值也可在redis.conf中配置。
bgrewriteaof
bgrewriteaof
#AOF文件比上次文件增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
#Aof文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
RDB和AOF各有自己的优缺点,如果对数据安全性较高,在实际开发中两者往往结合使用
RDB | AOF | |
持久化方式 |
定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积很大 |
宕机恢复速度 | 很快 | 慢 |
数据恢复优先级 | 低、因为数据完整性不如AOF | 高,因为数据完整性更高 |
系统资源占用 | 高,大量CPU和内存消耗 | 低,主要是磁盘IO资源, 但是AOF重写时会占用大量 的CPU和内存资源 |
使用场景 | 对数据安全性要求低,可以接受 数分钟的数据丢失,追求更快的 启动速度 |
对数据安全性要求较高 |
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力就需要单间主从集群,实现读写分离。
Redis集群分为三种方式
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
主从第一次建立连接时,会执行去全量同步,将master节点的所有数据都拷贝给slave节点。
流程如下图所示
完整流程描述:
slave节点请求增量同步
master节点判断replid,发现不一致,拒绝增量同步
master将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步
首先需要了解几个概念:
Replication Id | 简称replid,是数据集的标记,replid一致说明是同一数据集。 每一个master都有唯一的replid,slave则会继承master节点的replid |
offset | 偏移量,随着记录在repl_baklog中的数据增多而主键增大, slave完成同步时也会记录当前同步的offset。 如果slave的offset小于master的offset,说明slave的数据落后于master,需要更新。 |
因此slave做同步数据,必须向master声明自己的replication id和offset,master才可以判断到底需要那些同步数据。
因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接的时候,发送的offset和replid是自己的offset和replid。master如果判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。master会将自己的replid和offset都发送给这个slave,slave保存这些信息,以后slave的replid与master就一致了
因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致。
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
什么是增量同步?就是只更新slave与master存在差异的部分数据。如图:
思考: master怎么知道slave与自己的数据差异在哪里呢?
这就要说到全量同步时的repl_baklog文件了。
这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:
直到数组被填满:
此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:
棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
注意:repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致 尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。
总结:
简述全量同步和增量同步区别?
全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave
什么时候执行全量同步?
slave节点第一次连接master节点时
slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
slave节点断开又恢复,并且在repl_baklog中能找到offset时
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
哨兵的作用如下:
监控:Sentinel 会不断检查您的master和slave是否按预期工作
自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
•主观( subjective )下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
•客观(objective)下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:
首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
最后是判断slave节点的运行id(run_id)大小,越小优先级越高。
当选出一个新的master后,该如何实现切换呢?
流程如下:
sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
sentinel给所有其它slave发送slaveof 192.168.85.143 7003 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
Sentinel的三个作用是什么?
监控
故障转移
通知
Sentinel如何判断一个redis实例是否健康?
每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
如果大多数sentinel都认为实例主观下线,则判定服务下线
故障转移步骤有哪些?
首先选定一个slave作为新的master,执行slaveof no one
然后让所有节点都执行slaveof 新master
修改故障节点配置,添加slaveof 新master
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
海量数据存储问题
高并发写的问题
使用分片集群可以解决上述问题,如图:
分片集群特征:
集群中有多个master,每个master保存不同数据
每个master都可以有多个slave节点
master之间通过ping监测彼此健康状态
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
雪崩:Redis中的数据同时失效向Redis中存放数据的时候,都要设置失效时间,两个小时,刚好过期,大量的请求过来,直接访问到数据库中
解决方案:失效的时间做成随机数据
击穿:高频访问同一条数据,这条数据Redis没有,MySQL有
解决方案:在Redis中设置Null
穿透:大量数据Redis没有,MySQL也没有
解决方案:在Redis中设置为Null,布隆过滤器
布隆过滤器(BloomFilter):
Bloom filter是由Howard Bloom在1970年提出的二进制向量数据结构,它具有空间和时间效率,被用来检测一个元素是不是集合中的一个成员。如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中。因此Bloom filter具有100%的召回率。这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况,可见 Bloom filter 是牺牲了正确率和时间以节省空间。
计算方法
如需要判断一个元素是不是在一个集合中,我们通常做法是把所有元素保存下来,然后通过比较知道它是不是在集合内,链表、树都是基于这种思路,当集合内元素个数的变大,我们需要的空间和时间都线性变大,检索速度也越来越慢。 Bloom filter 采用的是哈希函数的方法,将一个元素映射到一个 m 长度的阵列上的一个点,当这个点是 1 时,那么这个元素在集合内,反之则不在集合内。这个方法的缺点就是当检测的元素很多的时候可能有冲突,解决方法就是使用 k 个哈希 函数对应 k 个点,如果所有点都是 1 的话,那么元素在集合内,如果有 0 的话,元素则不在集合内。
Bloom过滤器最大的问题是有误判率False Positives,这种误判率是避免不了的,但是尽可能的降低!!
优化方法: 可以将数组的长度和hash函数的个数尽量加大。
计算方法:
应用场景:商品查询
问题:如果一直不停的查询ID大于10000的商品,那么就会一直查询MySQL数据库,尽管查不到,但是也在消耗MySQL的资源,这就是典型的缓存穿透现象,那么应该怎么解决呢?
可以使用Bloom Filter
Bloom Filter具体实现
1、Google的guava框架实现了BloomFilter
2、Redis中也实现了BloomFilter