Redis
-
数据结构:Redis是一个Hash结构的K-V数据库,类似HashMap,Key是字符串对象,Value有5个
-
第一个String,底层是一个SDS,简单动态字符串
- 保存了字符串的长度Len,用的时候可以直接返回这个成员变量 STRLEN
- 修改的时候它会先检查分配给buf数组的总长度,可以防止缓存区溢出
- 还有flags变量,表示SDS的类型,5种类型对应不同的初始长度,可以根据字符串的大小来分配内存空间
- 存储对象的json, 常用的字典,配置,登录的token
- 常规计数,连续登录,密码错误多少次,给他锁定
- 分布式锁,加锁加过期时间,nx,px 看门狗
-
然后List是一个双向链表,增删快,查询慢
- 数据少的时候用压缩列表ziplist存,这是一块连续的内存空间
- 数据多的时候用quicklist存,这是双向链表和压缩列表的组合,ziplist作为双向链表的节点
- 提供了rpush,rpop等操作,可以做简单队列用
-
Hash,类似于HashMap,存储用户信息 hset,hget
- 数组+链表的结构
- 通过索引可以快速定位数组下标
- Hash冲突的时候会存到链表里,查询和增删都快
-
Set,类似于HashSet,点赞,好友关系 add,smembers
- 内部键值对,无序,唯一,自动去重
- 元素全是整数,并且数量少于512,会用整数集合intset存,这是一个有序集合
- 不满足intset的用hash表存
-
Zset,排行榜 zadd,zrange
- 在Set的基础上,加了一个Score,是有序的
- 底层用到了跳表和hash表
- 跳表就是链表+多级索引,类似于有序数组,可以进行二分查找,时间复杂度是O(logN)
-
-
持久化:就是将内存中的数据,持久化到磁盘上,然后定期同步或者备份到云上
-
RDB:每隔一段时间,进行数据持久化,他是一份一份的数据快照,save 10 1 #10秒1次key变更save
问题:容易造成数据丢失,比如新数据还没来及做快照,Redis重启了
-
AOF:(Append only File)只允许追加不允许修改的文件,保存所有写操作来实现持久化,需要修改配置文件开启
redis.conf : appendonly yes ,问题:文件很大,恢复很慢 (策略:always,everysec,no)
-
Redis使用混合持久化,开启配置aof-use-rdb-preamble yes
-
-
缓存穿透:查不到,缓存没有,数据库也没有,然后大量的无效Key过来请求,压力直接到数据库
- 布隆过滤器:有请求过来的时候先通过过滤器去判断数据库有没有这个值,有就返回,然后更新缓存,没有返回Null
- 缓存空值:布隆没有删除的操作,数据库把这个Key删了,布隆里还有,所以可以对那些删除的Key在缓存里存个空值
-
缓存击穿:热点Key过期或缓存里直接没有,但数据库有,这时候比较大的并发过来,会直接穿过缓存把压力打到数据库上
- 设置热点Key永不过期
- 加分布式锁
-
缓存雪崩:有大量热点Key同时失效,或者Redis直接down机不可用了,都直接导致大量的请求穿过缓存直接访问数据库
- Redis主从+哨兵模式去实现高可用
- 服务降级,短期内将不重要的服务先停掉
- Redis持久化,快速恢复缓存数据,一般重启,自动从磁盘中加载数据恢复内存中的数据
-
双写一致性
-
cache aside pattern(缓存+数据库读写的模式) + 延迟双删(避免脏读)
-
读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应
-
更新的时候,先删除缓存,再更新数据库,然后再删一遍缓存(根据读业务逻辑的耗时来定)
- 脏读:删缓存之后操作数据库之前,有个读请求过来了,就会读到旧数据
-
-
过期Key的删除
- TTL keyName 看key的剩余过期时间,-2表示不存在,-1表示永久有效,否则返回剩余时间
- 定时删除:用定时器轮询的方式去删除过期Key,消耗CPU资源,但内存不会浪费
- 惰性删除:只有当访问到这个Key的时候,才会判断是否过期,要不要删,会占用内存
- 定期删除:每隔一段时间,随机扫描一定数量的Key,判断是否过期,Redis同时使用惰性和定期删
-
淘汰策略
- 当 Redis 内存满了,在进行Set 的时候,就会触发淘汰策略
- NoEviction默认策略:对于写请求不再提供服务,直接返回错误
- LRU:最近最少使用
- LFU:最近使用次数最少的
- TTL:将要过期的
- Random:随机的 在redis.config中配置
-
为什么Redis单线程模型效率高
-
单线程指的是Redis对外提供的读写服务是单个线程完成的,其他的持久化,异步删除是其他线程做的
-
纯内存操作
-
单线程避免多线程频繁上下文切换的问题
-
核心是非阻塞的IO多路复用机制
-
使用一个线程来检查多个文件描述符FD的就绪状态,如果有一个就绪就返回
-
复用有三种方式:select,poll,epoll,前两种的操作方式都是遍历,时间复杂度是O(N),线程不安全
-
Redis默认epoll,线程安全,通过回调函数的方式来操作,时间复杂度是O(1)
-
Epoll是Linux提供的系统实现,谁有数据,就处理谁的请求,不会阻塞
-
核心方法有3个
-
epoll_create 创建epoll对象,对红黑树和双向链表初始化
- 红黑树主要存储了需要进行状态监控的文件描述符FD
- FD:一个被打开的文件的索引
- 双向链表就是就绪列表
-
epoll_ctl 注册监听,绑定事件,读写事件发生的时候,通过回调函数CallBack把事件放入就绪列表
-
epoll_wait 读取就绪列表,通过epoll这种方式,Redis就不需要一直轮询检查到底有没有实际的请求发生,避免CPU资源的浪费,Nginx也是用的epoll的方式,不过两者epoll的模式不一样
- Redis水平触发LT:事件发生的时候,epoll_wait会通知程序去处理,如果这次没有处理完,下次调用的的时候,它还会通知,就是你一直不处理,它就一直通知你,会降低效率
- Nginx边缘触发ET:它只会通知你一次,直到这个文件描述符出现第二次可读写事件才会通知,这种模式比LT效率高,系统不会充斥大量你不关心的就绪文件描述符
-
-
-
-
IO模型
- BIO:同步阻塞IO,读写请求都会被阻塞,直到读到数据,或完成写入操作
- NIO:同步非阻塞IO,不管数据准没准备好,都会直接返回
- AIO:异步非阻塞IO,数据拷贝阶段完全由操作系统处理,而应用程序只需要等待通知
-
Redis事务,执行顺序
- 开始事务 multi(标记事务开始)
- 命令入队 queued (一行一行的命令进入队列等待执行)
- 执行事务 exec(触发事务)
- 单个Redis命令的执行是原子性的,但Redis事务的执行并不是原子性的。
- 即中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
-
主从+哨兵 etc/redis/sentinel.conf 默认监听端口26379
-
一主多从,master用来写,多个slave用来读,用slaveof去设置从节点,设置好后,主从的数据会进行自动同步(主从复制)
- slave节点向master发送数据同步命令
- master收到命令后,创建RDB快照文件,发给slave,一起发的还有缓存的所有写命令(RDB时候有写的操作)
- 复制初始化完成后,master每一次写,都会同步到slave
-
用哨兵集群去监控Redis集群状态,是通过定时任务去做的,当发现Redis主节点Down掉后,Sentinel会发起选举(Raft),最终选举的Leader去完成Redis集群主从切换的过程,切换完后还会通知Client客户端新的Master地址
-
哨兵定时任务和下线
- 每10秒一个info命令,发现slave节点,确定主从关系
- 每2秒哨兵之间互相通信,交换对集群的监控状态
- 每1秒每个哨兵会对整个集群包括Redis集群和其他Sentinel做心跳检测,判断节点存活
- 主观下线:一个Sentinel判断Redis主节点down掉
- 客观下线:半数以上Sentinel判断主节点down掉
-