目录
一、NoSQL和SQL区别
(1)SQL对数据有着强烈的结构化定义,而NoSQL对数据约束没有那么强烈。
(2)SQL数据之间有关联,而NoSQL没有,它通过json嵌套来实现数据的关联。
(3)SQL 查询数据的语法是固定的,相对应的NoSQL没有固定语法。
(4)SQL满足事务的ACID特性,而NoSQL不满足。
(5)SQL基于磁盘存储,NoSQL基于内存存储。
(6)SQL是垂直性扩展,NoSQL是水平扩展的。
(7)SQL适合数据结构固定,相关业务对数据安全性、一致性要求较高,NoSQL数据结构不固定,对一致性、安全性要求不高,对性能有要求。
二、redis特点
(1)键值(key-value)型,value支持多种不同数据结构,功能丰富。
(2)单线程,每个命令具备原子性
(3)低延迟,速度快(基于内存、IO多路复用和良好的编码)
(4)支持数据持久化
(5)支持主从集群、分片集群
(6)支持多语言客户端
三、redis的数据结构
redis是一个key-value的数据库,key一般是String类型,不过value的类型是多种多样的。
四、redis的通用命令
(1)KEYS:查看符合模板的所有key,可以使用*通配符,不建议在生产环境使用
(2)DEL:删除一个指定的key,也可以删多个,返回删除的数量
(3)EXISTS:判断一个KEY是否存在
(4)EXPIRE:给key设置有效时间,时间到期该key被自动删除
(5)TTL:查看一个KEY的剩余有效期
五、redis的数据类型
5.1 String类型
String类型,其value是字符串,不过根据字符串的格式不同分为三类:string(普通字符串串)、int(整数类型)和float(浮动类型),后两种可以做自增和自减操作。底层都是采用字节数组存储的,只不过编码形式不同,数据类型的字符串将转换成二进制类型存储。字符串类型的上限不能超过512m
string常见命令
set:添加或修改一个字符串
get:根据key获取String类型的value
mset:操作多个
mset:查询多个
incr:让key的value值自增1
incrby:让key的value值自增相应的步长
incrbyfloat:浮点数自增
setnx:添加key-value,前提key不存在
setex:添加key-value,设置key的过期时间
String的编码
(1)当存储字符串时,存储的SDS长度小于44字节,会采用EMBSTR编码,此时object head与SDS是一段连续空间。申请内存时只需要调用一次内存分配函数,效率更高。当存储的SDS长度超过44字节,会采用RAW编码。存储上限为521mb。
(2)当存储的字符串是整数,并且大小在LONG_MAX范围内,则会采用INT编码;直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了
5.2 Hash类型
hash的value值是无序字典,类似于java中的hashMap结构
常见命令:
增加:HSET key field value 批量添加 HMSET
查询:HGET key field 批量查询 HMGET
查询所有 HGETALL 查询所有field HKEYS 查询所有的value HVALS
字段自增 HINCRBY
HSETNX:判断hash的field是否存在,不存在则执行
hash底层数据存储结构
5.3 List类型
list类型与java的LinkedList类似,是一个双向链表,支持正反检索。特点是有序、可重复、插入和删除快,查询慢
常见命令:
插入元素:LPUSH RPUSH
获取并移除元素:LPOP RPOP 阻塞获取BLPOP BRPOP
范围获取 LRANGE
List底层数据结构
5.4 Set类型
redis的Set结构与java的hashSet类似,可以看做一个value为null的hashmap、因为也是一个hash表,和hashset类似 。特征有无序、元素不重复、查找快、支持交集、并集和差集等。
常见命令:
增加元素 SADD
移除元素 SREM
查询元素总数 SCARD SMEMBERS 查询所有元素
SISMEMBER 判断一个元素是否存在于set中
SINTER 求key1和key2的交集
SDIFF 求key1和key2的差集
SUNION 求key1和key2的并集
set的底层数据结构
set是redis中的集合,set采用HT编码(Dict)。Dict中的key用来存储元素,value统一为null。当存储的所有数据都是整数时,并且元素数量不超过set-max-intset-entries,set会采用intSet编码,以节省内存。
5.5 SortedSet类型
SortedSet是一个可排序的集合,根据每个元素的score对元素排序,底层是一个跳表+hash表 sortedSet具有可排序、不重复和查询速度快
常见命令:
新增 zadd 删除 zrem
zrank key member 获得排序,默认是升序 ZREVRANK 降序
ZCARD 获得set的总数
sortedset底层数据结构
zset底层数据结构必须满足键值存储、键必须唯一和可排序,因此底层采取的存储方式是SkipList和HT
六、redis客户端--SpringDataRedis
(1)需求引入spring-boot-starter-data-redis依赖和commons-pools依赖
(2)spring-boot-starter-data-redis默认引入的lettuce的客户端,如果使用jedis需要单独引入
(3)注入RedisTemplate
(4)SpringDataRedis默认使用jdk的序列化工具,使用时需要重新指定,对于key使用string序列化,value使用json序列化,这样会使得value多存储一个class的键值对,占据更多内存。
(5)Spring提供了一个StringRedisTemplate类,它的key和value的序列化方式默认是spring,这样需要手动序列化和反序列
七、缓存常见问题
7.1 缓存更新
缓存更新有三种策略,分别是内存淘汰策略、超时剔除和主动更新
对于低一致性需求:使用内存淘汰策略,
对于高一致性需求:主动更新,并以超时剔除作为兜底方案
读操作: 缓存命中则直接返回,未命中则查询数据库,并写入缓存,设定超时时间
写操作:先写数据库,然后再删除缓存;要保证数据库和缓存操作的原子性
7.2 缓存穿透、缓存击穿和缓存雪崩
缓存穿透:用户查询的数据在缓存和数据库都不存在。解决方案使用缓存空对象加设置key的过期时间和布隆过滤器
缓存雪崩:同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来严重压力。解决方案包括设置key不同的TTL时间,搭建redis集群、降级限流和添加多级缓存。
缓存击穿:也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。采用互斥锁和逻辑过期解决
八、redis的持久化
8.1 redis的RDB持久化
RDB(Redis Database Backup file)是redis数据快照,即将内存中的所有数据记录到磁盘用于故障恢复。使用命令save和bgsave,save会阻塞所有命令,bgsave使用子进程保存数据,效率高
RDB触发机制
(1)redis停机之前会先写RDB文件,之后停机
(2)在redis.conf配置 ,如下
RDB持久化原理
bgsave开始会fork主进程到子进程,子进程共享主进程的内存数据,子进程写数据到新的RDB文件会替换旧的文件。fork子进程仅仅是复制主进程的页表,所以阻塞会非常小。同时,当主进程执行写操作时,会拷贝一份数据,执行写操作。
RDB的缺点
(1)RDB执行间隔时间长,会存在丢失数据的风险
(2)fork子进程、压缩、写出RDB文件比较耗时
8.2 AOP持久化
AOP(Append Only File)追加文件,redis会将操作的每一个命令追加到文件里,可以看做一个命令的日志文件
AOP的开启
AOP重写
为了减少aop文件的大小,使用bgrewriteaof命令重写aof文件,这个命令是开启一个独立线程执行的
触发机制
8.3 RDB和AOP区别
九、redis主从架构
redis主从架构,实现读写分离
开启主从模式
(1)永久开启:在redis.conf配置文件添加一行slaveof <masterIp> <masterport>
(2)临时开启:redis-cli客户端连接redis服务器,在从节点执行slaveof命令,重启后失效
注意在redis5.0以后新增命令replicaof
主从同步原理
全量同步
(1)slave节点请求增量同步
(2)master节点判断replid,发现不一致,拒绝增量同步
(3)master将完整内存数据生成RDB,发送RDB到slave
(4)slave清空本地数据,加载master的RDB
(5)master将RDB期间的命令记录在repl_baklog,并持续将log命令发送到slave
(6)slave执行接收到的命令,保持与master之间的同步
增量同步
slave重启后采用增量同步,repl_baklog大小有上限,写满后覆盖最早的数据。如果slave断开太久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。
优化redis主从同步
(1)在master配置repl-diskless-sync yes 启用无磁盘复制,避免全量复制
(2)控制redis单节点内存占用量,不要太大
(3)适当提高repl_baklog的大小,尽快恢复宕机的从节点
(4)限制一个master的slave节点数量,可以采用主-从-从链式结构,减少master的压力
十、哨兵模式
redis的哨兵主要为了实现主从集群的自动故障恢复
哨兵的作用
(1)监控:哨兵会不断检查集群节点的健康状态
(2)自动故障恢复:若集群master节点宕机,哨兵会选择一个slave节点提升为master节点
(3)通知:当集群发生故障转移,哨兵会将最新信息推送给redis的客户端
哨兵监控
哨兵基于心跳机制每隔一秒向集群的每个实例发送ping命令,若某个哨兵在规定时间未收到响应,认为该实例主观下线;若超过指定数量(quorum)的哨兵节点都认为该实例主观下线,则该实例客观下线
选举规则
(1)首先判断slave节点与master节点断开时间长短,超过指定值(down-after-milliseconds * 10)会排除slave节点
(2)判断slave的slave-priority值,越小优先级越高,如果是0则永不参与选举
(3)如果slave-priority越大,则判断slave节点的offset值,越大说明数据越新,优先级越高
(4)最后判断slave节点的运行id大小,越小优先级越高
故障转移
(1)哨兵给有资格的slave节点发送一个slaveof no one命令
(2)给其他节点广播发送salveof ip post,让其他从节点开始从新的master同步数据
(3)哨兵会将故障节点标记为slave节点,故障恢复后成为新master的从节点
哨兵集群搭建
(1)修改sentinel.conf文件
(2)使用命令 redis-sentinel ./sentinel.con启动redis实例
十一、redis分片集群
分片集群
(1)集群有多个master节点,每个master保存不同数据
(2)每个master都可以有多个slave节点
(3)master之间通过ping监测彼此健康状态
散列插槽
redis会把每一个master节点映射到0~16384个插槽上
查询时,数据key不是和节点绑定,而是和插槽绑定,redis会根据key的有序部分(key包含{}里的字符,没有{}当前key都是有效部分),利用CRC16算法得到一个hash值,然后对16384取余,得到结果为slot值。
注:将同一类数据固定保存到redis同一个实例上,可以在key添加相同的有效部分
集群伸缩
(1)创建集群:redis-cli --cluster create --replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003.
(2)新增节点:redis-cli --cluster add-node new_host:new_port existing_host:existing_port --cluster-slave --cluster-master-id <arg>
(3)redis-cli --cluster reshard ip:port 分配哈希槽
故障转移
(1)自动故障转移,类似于哨兵
(2)手动故障转移,通过执行cluster failover命令让集群某个master宕机,在slave节点执行该命令可以该节点升级为master节点
十二、redis底层数据结构
12.1 动态字符串SDS
redis没有采用C语言的字符串,而是采用一种新的字符串结构,简称简单动态字符串(Simple Dynamic String),简称SDS。SDS本质是C语言的结构体。
SDS支持动态扩容,有两种情况:(1)当新字符串小于1M,则新空间为扩展后字符串长度的两倍+1;(2)如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1。
12.2 IntSet
IntSet是Redis中set集合的一种实现方式,基于整数数组来实现,并且具备长度可变、有序等特征。结构如下:
为了查找方便(采用二分查找),Redis会将intset中所有的整数按照升序依次保存在contents数组中。
若当前编码无法满足当前数据存储,会动态升级编码,并倒序依次将数组中的元素拷贝到扩容后的正确位置,然后将待添加的元素放入数组末尾。
12.3 Dict
redis的键值类型由Dict实现,Dict由三部分实现,分别是哈希表、哈希节点和字典
当向Dict添加键值对时,redis首先根据key计算出hash值,然后利用h&sizemask来计算数组的索引下标。
Dict的扩容
负载因子LoadFactor=used/size
(1)当哈希表的LoadFactor >= 1,并且服务器没有执行BGSAVE或者BGREWRITEAOF等后台进程。
(2)当哈希表的LoadFactor > 5;
Dict的收缩
当删除元素时,也会对负载因子做检查,当LoadFactor < 0.1且数组长度大于4时,会做哈希表收缩。
Dict的移动元素
dict的rehash并不是一次性完成的,而是分多次、渐进式的完成,每次在执行增删改查都会重写哈希一个元素,直到所有元素迁移完成。
12.4 ZipList
ZipList是一种特殊的“双端链表”,由一系列特殊编码的连续内存组成。可以在任意一端进行压入/弹出操作,并且该操作的时间复杂度为O(1)。ZipList结构如下:
ZipList的Entry结构如下
注意:ZipList中所有存储长度的数值均采用小端字节序,即低位字节在前,高位字节在后。例如数值0x1234,采用小端字节序后实际存储值为:0x3412
ZipList的Entry中的encoding编码分为字符串和整数
(1)当encoding是以“00”,“01”,或者“10”开头,对应的存储字节数分别为一字节、二字节和五字节,以这些标志开头代表是字符串。
(2)当encoding是以“11"开头,则是整数,且encoding固定只占用1个字节。
ZipList的连锁更新问题
当有N个,长度为250~253字节之间的entry时,此时entry的previous_entry_length属性采用一个字节存储,当插入一个比较大(超过254)的数据时,导致一个字节无法记录前一个entry长度,会导致多次的连续空间扩展,效率极低,发生概率极低。
12.5 QuickList
QuickList是一个双端链表,只不过链表中的每一个节点都是一个ZipList,这样做可以解决内存申请效率低的问题
QuickList会限制ZipList的大小
QuickList压缩节点
12.6 SkipList
skiplist(跳表)是一个链表,其特点如下:
(1)元素按照升序排列存储
(2)节点可能包含多个指针,而且指针跨度不一样
源代码如下:
12.7 RedisObject结构
Redis中的任意数据类型的键和值都会被封装为一个RedisObject,也叫做Redis对象。
十三、网络模型
13.1 用户空间和内核空间
为了避免用户应用崩溃导致内核崩溃,用户应用与内核是分离。
(1)进程的寻址空间分为内核空间和用户空间
(2)用户空间只能执行受限的命令,而且不能直接调用系统资源,必须通过内核提供的接口来访问
(3)内核空间可以执行特权命令,调用一切系统资源
13.2 IO模型
(1)阻塞IO
(2)非阻塞IO
(3)IO多路复用
13.3 redis网络模型
redis通过IO多路复用来提高网络性能,并且支持各种不同的多路复用实现,并且将这些实现进行封装,提供了统一的高性能事件库。
13.4 redis的RESP协议
redis的RESP协议规则如下:
(1)单行字符串:首字节是‘+’,后面跟上单行字符串,以CRLF("\r\n")结尾,例如返回“OK” “+OK\r\n”
(2)错误:首字节是‘-’,与单行字符串格式一样,只是字符串是异常信息,例如:“-Error message\r\n”
(3)数值:首字节是‘:’,后面跟上数字格式的字符串,以CRLF结尾。例如:“:10\r\n”
(4)多行字符串:首字节是‘$’,表示二进制安全的字符串,最大支持512MB;如果大小为0,则表示是空字符串:“$0\r\n\r\n”,如果大小为-1,则代表不存在:“$-1\r\n”
(5)数组:首字节是‘*’,后面跟上数组元素个数,在跟上元素,元素数据类型不限。
十四、Redis内存策略
内存过期策略
(1)惰性删除:过期key不会立即被删除,当访问时会判断当前key是否过期,过期后就删除。
(2)周期删除,通过一个定时任务,周期性的抽样部分过期的key,然后执行删除。
SLOW模式:执行频率默认是10hz,每次执行不超过25ms
FAST模式:执行频率不固定,每次间隔不低于2ms,每次耗时不超过1ms
十五 、淘汰策略
内存淘汰:当redis内存使用达到设置的阈值时,redis主动挑选部分key删除以释放更多内存的流程
注意:LRU(Least Recently Used),最少最近使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
LFU(Least Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。