redis复习指南
2020-09-21 10:18:38 0 举报
AI智能生成
redis 学习指南
作者其他创作
大纲/内容
基本数据结构
存储对象底层结构
REDIS_ENCODING_INT long 类型的整数
REDIS_ENCODING_EMBSTR embstr 编码的简单动态字符串(redis3.0添加)
REDIS_ENCODING_RAW 简单动态字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 双端链表
REDIS_ENCODING_ZIPLIST 压缩列表
REDIS_ENCODING_INTSET 整数集合
REDIS_ENCODING_SKIPLIST 跳跃表和字典
REDIS_ENCODING_EMBSTR embstr 编码的简单动态字符串(redis3.0添加)
REDIS_ENCODING_RAW 简单动态字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 双端链表
REDIS_ENCODING_ZIPLIST 压缩列表
REDIS_ENCODING_INTSET 整数集合
REDIS_ENCODING_SKIPLIST 跳跃表和字典
string
可选:int / raw / embstr
string类型的抉择
对象可转long 用 int
length<39用embstr 否则用raw
embstr对比raw
embstr只分配一次内存
raw需要分配两次:sds&object,释放时也需要两次
SDS(Simple Dynamic String)简单动态字符串,它是由C语言完成
应用场景
基本缓存功能,利用redis的支持的高并发在db间做缓存屏障
计数器,点赞数等数据,可以异步定时缓存这些非及时数据到db
共享用户session,redis高效更新获取用户session,提高响应效率
分布式锁
List
可选:ziplist / linkedlist
zipList
ziplist是一种压缩链表,好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。
当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不好用了。
因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。
对象结构中ptr所指向的就是一个ziplist。整个ziplist只需要malloc一次,它们在内存中是一块连续的区域
当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不好用了。
因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。
对象结构中ptr所指向的就是一个ziplist。整个ziplist只需要malloc一次,它们在内存中是一块连续的区域
malloc : 作用是在内存的动态存储区中分配一个长度为size的连续空间
realloc : realloc函数用于修改一个原先已经分配的内存块的大小,可以使一块内存的扩大或缩小
realloc : realloc函数用于修改一个原先已经分配的内存块的大小,可以使一块内存的扩大或缩小
linkedList
双向链表。它的结构比较简单,节点中存放pre和next两个指针,还有节点相关的信息。
当每增加一个node的时候,就需要重新malloc一块内存
当每增加一个node的时候,就需要重新malloc一块内存
应用场景
消息队列:redis阻塞链表可以很好支持,rPush,lPop
排行榜等列表数据
对于频繁变更且有分页的数据,推荐使用sorted set/list变更后获取分页lrange,容易有重复值
hash
可选:ziplist或者hashtable
zipList
hashTable
由dict结构实现
相当于HashMap,采用的“数组+链表”来解决hash冲突
字典的数据结构中存在两个hashTable,在字典扩容时,
一个存储旧数据,一个保存新数据(渐进式搬迁数据),迁移完成删除旧数据
一个存储旧数据,一个保存新数据(渐进式搬迁数据),迁移完成删除旧数据
扩容:当元素个数等于一维数组长度,则进行扩容2倍;
扩容时在进行bgsave,则尽可能不去扩容,除非元素个数是一维数组长度5倍/hash非常满了
扩容时在进行bgsave,则尽可能不去扩容,除非元素个数是一维数组长度5倍/hash非常满了
dict对象的 dictht ht[2]
dicht[0] 是用于真正存放数据,
dicht[1]一般在哈希表元素过多进行rehash的时候用于中转数据
dicht[0] 是用于真正存放数据,
dicht[1]一般在哈希表元素过多进行rehash的时候用于中转数据
应用场景
购物车场景,(key,filed,value)=(用户id,商品id,商品数量)
set
可选:intset或者hashtable
hashTable
intSet
整数集合,存储同类型数据,支持三种长度的整数: int16_t/int32_t/int64_t
查找元素时间复杂度:O(logN)
插入数据时间复杂度不一定为O(logN);
如果插入数据int32_t数据到int_16集合中,涉及到集合的升级,把int16转为32,重新分配内存
插入int16到int32集合中,不会集合类型降级
插入数据时间复杂度不一定为O(logN);
如果插入数据int32_t数据到int_16集合中,涉及到集合的升级,把int16转为32,重新分配内存
插入int16到int32集合中,不会集合类型降级
应用场景
黑/白名单
关注/粉丝 名单
zset(sorted set)
可选: ziplist 或者 skipList&dict
skiplist&dict
skiplist:跳表,实现了快速查找,大多情况下和平衡树查不多,可以作为替代品
ziplist
ziplist作为集合和作为哈希对象是一样的,member和score顺序存放。
按照score从小到大顺序排列
按照score从小到大顺序排列
应用场景
排行榜,可以根据score排序
高级数据结构
Bitmaps
String类型,最大512mb
最大设置2^32个bit
最大设置2^32个bit
实时分析数据结果;
大型数据关联布尔值;
用户登录统计,签到状态,在线状态
大型数据关联布尔值;
用户登录统计,签到状态,在线状态
Hyperloglogs
HyperLogLog是一种算法,并非redis独有
适用于海量数据的计算统计
在输入元素的数量或者体积非常大时计算所需的空间仍旧很小且固定
在输入元素的数量或者体积非常大时计算所需的空间仍旧很小且固定
统计注册ip数
统计页面UV数
统计在线用户数
统计页面UV数
统计在线用户数
GEO
主要用于存储地理位置信息。并对存储的信息进行操作
常用于LBS位置服务,我周围的人 等功能
redis持久化
RDB
可以指定时间间隔生成数据集的备份文件/可以指定不同的时间间隔
优点
· rdb数据结构紧凑,可以指定多时间点进行数据备份/在恢复时有多版本
· rbd是一份内容紧凑的数据,方便数据加密传输备份
· rdb数据备份性能更好,fork出来一个子线程来备份,不影响主线程工作
· rdb的数据恢复速度比较快;当数据修改较多时尤为显著
· rbd是一份内容紧凑的数据,方便数据加密传输备份
· rdb数据备份性能更好,fork出来一个子线程来备份,不影响主线程工作
· rdb的数据恢复速度比较快;当数据修改较多时尤为显著
缺点
rdb不能尽量保证数据的完整性:容易丢失时间间隔内的数据
每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。
在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端;
如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。
虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失
在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端;
如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。
虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失
fork失败案例
redis存储类14G数据,内存只有16G
redis存储类14G数据,内存只有16G
fork
fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,
如果返回值>0,则是父进程(返回值是子进程的pid),这是众为周知的。
还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,
包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。
如果返回值>0,则是父进程(返回值是子进程的pid),这是众为周知的。
还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,
包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。
AOF
记录所有写操作到log文件,以追加的形式写入,恢复时可以执行log中的数据集
可以rewrite.来调整log文件大小
可以rewrite.来调整log文件大小
优点
1.fsync策略可以设置每次写入/每秒执行,最小损失数据同步
2. redis-check-aof 可以对写入有问题的命令进行修复
3.aof文件过大时,redis会对文件重写,以最小命令集来压缩文件大小,
并且会在重写后替换现有的aof文件,不用担心期间异常导致的数丢失
4.aof文件即便再flushall后仍可恢复/修改aof,删掉最后的命令
2. redis-check-aof 可以对写入有问题的命令进行修复
3.aof文件过大时,redis会对文件重写,以最小命令集来压缩文件大小,
并且会在重写后替换现有的aof文件,不用担心期间异常导致的数丢失
4.aof文件即便再flushall后仍可恢复/修改aof,删掉最后的命令
缺点
1.相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
2.使用的fsync aof速度慢于rdb
2.使用的fsync aof速度慢于rdb
事件
AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。
(举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug 。)
测试套件里为这种情况添加了测试: 它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。
虽然这种 bug 在 AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。
(举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug 。)
测试套件里为这种情况添加了测试: 它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。
虽然这种 bug 在 AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。
内存淘汰机制
noeviction -> 不做任何处理,只在写入时 ,提示错误
volatile-ttl -> 删除最近到期的key
allkeys-random -> 随机删除任意key
volatile-random -> 删除具有过期时间的随机秘钥
allkeys-lfu -> 移除最近最少使用的key 默认
volatile-lfu -> 在设置了过期时间的键空间中,移除最近最少使用的key
allkeys-lru -> 移除最少使用的key
volatile-lru -> 在设置了过期时间的键空间中,移除最少使用的key
volatile-ttl -> 删除最近到期的key
allkeys-random -> 随机删除任意key
volatile-random -> 删除具有过期时间的随机秘钥
allkeys-lfu -> 移除最近最少使用的key 默认
volatile-lfu -> 在设置了过期时间的键空间中,移除最近最少使用的key
allkeys-lru -> 移除最少使用的key
volatile-lru -> 在设置了过期时间的键空间中,移除最少使用的key
特性
优点
QPS数据
所有操作的原子性,以及多操作指令可以保证原则性(支持Lua脚本)
数据结构复杂
支持事务
基于内存的
基于数据存储的/基于硬盘/基于内存/基于cpu缓存 做对比
为什么这么快
单线程
不用切换线程上下文
数据结构简单
底层模型不同
它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,
因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
多路IO复用模型,非阻塞
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,
当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),
并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作
当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),
并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作
缺点
容易受到物理机器内存限制
容错性差,自动恢复能力差
实用场景
redis分布式锁
实现:
setnx:set not exist
redis-session共享
分布式session共享问题
redis-消息队列
消息队列
生产故障
keys、smembers、hgetall、zrange这类可能造成阻塞的指令
redis常见性能问题和解决方案
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
常见问题
如何保证缓存数据一致性问题?
读取:先读缓存,后读数据库
写入:先写数据库,后删除缓存【缓存刷新成本不固定,按缓存读取实际情况而定】
写入:先写数据库,后删除缓存【缓存刷新成本不固定,按缓存读取实际情况而定】
缓存雪崩
redis缓存中大面积数据失效,一瞬间的数据全部到服务端,导致系统压力暴增,甚至服务直接挂掉
解决
1-在批量存储数据到Redis中时在每个key的失效时间后面加上随机数
可以设置热点数据永不过期,有操作就更新缓存
缓存穿透
服务访问一个缓存中不存在的数据,这样服务直接就会到服务端
解决
对于反复重复暴力服务获取数据,可以在接口层面加强校验,
也可以针对查询的key值,配置缓存,在缓存中的key直接返回结果null.未知错误等
也可以针对查询的key值,配置缓存,在缓存中的key直接返回结果null.未知错误等
使用布隆过滤器实现
https://mp.weixin.qq.com/s/BdwZViiAqnFhCde4ZsxwPg
缓存击穿
类似排行榜的热点数据,有大量的服务访问一个key,当key失效时服务就打到服务端
解决
设置热点数据永不过期,或者加上互斥锁
0 条评论
下一页