类型:Redis对key-value封装成对象,key是一个对象,value也是一个对象redisObject。每个对象都有type(类型)、encoding(编码)、ptr(指向底层数据结构的指针)来表示
- string
- 数据结构: SDS简单动态字符串
- sdshdr数据结构中用len属性记录了字符串的长度。那么获取字符串的长度时,时间复杂度只需要O(1)
- SDS不会发生溢出的问题,如果修改SDS时,空间不足。先会扩展空间,再进行修改!(内部实现了动态扩展机制)
- SDS可以减少内存分配的次数(空间预分配机制)。在扩展空间时,除了分配修改时所必要的空间,还会分配额外的空闲空间(free 属性)
- SDS是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据
- 缓存、JSON、计数器、共享session
- 数据结构: SDS简单动态字符串
- list
- 数据结构: 使用listNode结构来表示每个节点,使用list结构来持有链表
- 无环双向链表
- 获取表头指针,表尾指针,链表节点长度的时间复杂度均为O(1)
- 链表使用void *指针来保存节点值,可以保存各种不同类型的值
- 队列
- 数据结构: 使用listNode结构来表示每个节点,使用list结构来持有链表
- hash
- 数据结构:数组 + 链表
- Redis哈希冲突时:是将新节点添加在链表的表头
- 对象
- 数据结构:数组 + 链表
- set
- 数据结构:
- 无序集合
- sortset
- 数据结构: 跳跃表(shiplist)
- 排行榜
键的过期时间 EXPIRE EXPIREAT
- 过期策略
- 定时删除(对内存友好,对CPU不友好)
- 到时间点上就把所有过期的键删除了
- 惰性删除(对CPU极度友好,对内存极度不友好)
- 每次从键空间取键的时候,判断一下该键是否过期了,如果过期了就删除
- 定期删除(折中)
- 每隔一段时间去删除过期键,限制删除的执行时长和频率
- 定时删除(对内存友好,对CPU不友好)
- 内存淘汰机制 如果定期删除漏掉了很多过期key,也没及时去查(没走惰性删除),大量过期key堆积在内存里,导致redis内存块耗尽了
- volatile-lru
- 从已设置过期时间的数据集中挑选最近使用最少使用的数据淘汰
- volatile-ttl
- 从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random
- 从已设置过期时间的数据集中任意选择数据淘汰
- allkeys-lru
- 从所有数据集中挑选最近使用的数据淘汰
- allkeys-random
- noeviction 默认
- 禁止驱逐数据
- volatile-lru
缓存
- 雪崩
- 在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期
- 穿透
- 校验参数,不合法就返回
- 当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了
- 空对象设置一个较短的过期时间
- 击穿
- nginx配置同一个IP次数
- 缓存永不过期
- 双写一致 update
- 操作缓存
- update
- 高并发环境下,无论是先操作数据库还是后操作数据库而言,如果加上更新缓存,那就更加容易导致数据库与缓存数据不一致问题。(删除缓存直接和简单很多)
- delete
- 如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那我到数据库找,在数据库找到再写到缓存里边(体现懒加载)
- update
- 先操作数据库,再操作缓存
- 第一步成功(操作数据库),第二步失败(删除缓存),会导致数据库里是新数据,而缓存里是旧数据
- 如果第一步(操作数据库)就失败了,我们可以直接返回错误(Exception),不会出现数据不一致
- 删除缓存失败的解决思路
- 将需要删除的key发送到消息队列中
- 自己消费消息,获得需要删除的key
- 不断重试删除操作,直到成功
- 先操作缓存,再操作数据库
- 第一步成功(删除缓存),第二步失败(更新数据库),数据库和缓存的数据还是一致的
- 如果第一步(删除缓存)就失败了,我们可以直接返回错误(Exception),数据库和缓存的数据还是一致的
- 并发下解决数据库与缓存不一致的思路
- 将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化
- 操作缓存
集群
持久化
- RDB(基于快照),将某一时刻的所有数据保存到一个RDB文件中
- SAVE
- 会阻塞Redis服务器进程,服务器不能接收任何请求,直到RDB文件创建完毕为止
- BGSAVE
- 创建出一个子进程,由子进程来负责创建RDB文件,服务器进程可以继续接收请求
- 配置文件
- save 900 1 #在900秒(15分钟)之后,至少有1个key发生变化,
- save 300 10 #在300秒(5分钟)之后,至少有10个key发生变化
- save 60 10000 #在60秒(1分钟)之后,至少有10000个key发生变化
- SAVE
- AOF(append-only-file),当Redis服务器执行写命令的时候,将执行的写命令保存到AOF文件中
- 步骤
- 命令追加:命令写入aof_buf缓冲区
- 文件写入:调用flushAppendOnlyFile函数,考虑是否要将aof_buf缓冲区写入AOF文件中
- 文件同步:考虑是否将内存缓冲区的数据真正写入到硬盘
- 选项
- appendfsync always # 每次有数据修改发生时都会写入AOF文件。
- appendfsync everysec # 每秒钟同步一次,该策略为AOF的默认策略。
- appendfsync no # 从不同步。高效但是数据不会被持久化。
- 步骤
- RDB和AOF并不互斥,它俩可以同时使用,服务器会优先使用AOF文件来还原数据(因为AOF更新频率比RDB更新频率要高,还原的数据更完善)
- RDB的优点:载入时恢复数据快、文件体积小
- RDB的缺点:会一定程度上丢失数据(因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失
- AOF的优点:丢失数据少(默认配置只丢失一秒的数据
- AOF的缺点:恢复数据相对较慢,文件体积大
其他
- Redis单线程为什么快
- 纯内存操作
- 核心是基于非阻塞的IO多路复用机制
- 单线程避免了多线程的频繁上下文切换问题
- 分布式锁
- 先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放
- 如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样
- set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的
- scan替代keys
- lpop blpop
- 延时队列
- sortset score 时间戳
- pipeline
- 多个指令一次执行,减少IO操作
- zookeeper
- 多个系统同时操作redis