基本数据类型
String
在C语言的基础上,封装了简单动态字符串(Simple Dynamic String),增加了capacity和len两个字段分别记录容量和当前长度。
空间预分配:当字符串需扩容时,针对小于1M的,每次加倍,对于超过1M的每次增加1M
空间懒回收:当空间不再使用,不是立马进行回收
List
个数少时用压缩列表ziplist,一段连续的空间(当元素数据大小变更时可能存在级联更新)
个数多时用链表linkedlist
高版本的做了优化,结合了链表和压缩列表,使用quicklist,其实际是每个链表节点下面是ziplist
Hash
个数少时用压缩列表
个体多时用hashtable(数组链表法,实际内部保存了两个hashtable,主要用于扩容)
扩容策略:保存h0、h1,渐进hash的方法进行扩容和缩容
Set
如果全是整数且元素个数小于512个,用整数集合inset
如果不是则使用hashtable,value都是空值
ZSet
存储类型
存储类型主要由raw、int、ziplist、linkedlist、quicklist、hashtable、intset,针对上述几种数据类型,在数据长度或元素个数不同时,为了节省空间,底层会选择不同的存储结构进行存储(本质目的都是节约内存)
持久化
过期&淘汰策略
1.如何清理过期数据
定时清理:cpu不友好型,如果某一时间大量数据过期,会占据cpu,而Redis是单线程的,影响正常的业务请求
惰性清理:在每次用户访问时判断是否过期,并进行清理,cpu友好,内存不友好,如果key长期不访问就会长期存在
定期清理:定期的去清理,每次按照一定策略取出20个key进行清理,如果取出的20key中待清理的数量达到85%,还会再清理一次
Redis使用的是惰性清理+定期清理
2.Redis的key的淘汰策略
当redis的内存空间已经用满时,需要依据淘汰策略,进行相应的key的淘汰
(1)no-eviction:不删除策略。当需要更多内存,直接返回错误
(2)allkeys-lru:对所有的key按照lru淘汰
(3)allkeys-random
(4)volatile-lru:设置了过期时间的key,按lru淘汰
(5)volatile-random
(6)volatile-ttl:按照过期剩余时间淘汰
RDB
RDB文件:记录的是内存的数据。
命令:save和bgsave。 一般使用bgsave,使用fork子进程,利用cow(copy on write)技术,子进程执行结束后告知父进程
bgsave的执行策略:时间n内发生m次变更,可以配置多条规则
AOF
AOF文件:记录的是对变更数据的命令。
aof缓冲区(命令追加) -> aof文件缓冲区(文件写入) -> 磁盘(文件同步)
fsync参数:
always:每次同步磁盘
everysec:没秒同步磁盘
no:不同步,由操作系统来决定
解决AOF文件过大,AOF文件重写(bgrewriteaof):重写的过程中合并操作,并将重写过程中执行的命令保存到重写缓冲区,等重写完成再追加到新的AOF文件
AOF和RDB有点类似数据库记录binlog的方式:记录行变化,记录sql,组合方式
集群方案
主从
复制策略
sync:从服务器发起同步,主服务器开始生成RDB文件,等从服务器进行RDB回放完之后,同时将同步期间的命令也发给从服务器, 用到复制缓冲区(用于记录RDB文件之后执行的命令,每个从节点对应一个)
psync:三个关键点:服务id,偏移量,缓冲区。 如果断开时间中缓存区满了,会丢失数据,用到复制积压缓冲区(在增量复制时,会把中间执行的放在积压缓冲区,如果从节点差距过大,则会全量复制,全局只有一个)
psync 2.0
缺点:无自动故障恢复的能力
哨兵
用一个Sentinel集群来监管Redis集群中的状态,并负责集群的故障转移和恢复(包括选主)的实现
集群
分片策略:客户端分片、代理层分片、服务器分片,Redis Cluster使用的服务器分片,每个服务器还记录了整个集群的分片情况
16384个槽,分布在各个主服务器,每个主服务器有各自的从服务器,服务器之间通过Gossip协议来同步信息。 我司的主从服务器分布于不同的机房
常见问题
大Key问题
(1)大key扫描
公司是使用Scan来处理的, scan相对于keys指令,具有分批查询的功能,可以每处理一批,停顿一段时间,避免影响到线上其他的服务
参考:http://jinguoxing.github.io/redis/2018/09/04/redis-scan/
(2)在写入或读取的时候判断是否为大Key
缓存穿透、缓存击穿、缓存雪崩
(1)缓存穿透
访问不存在的key,导致流量打到数据库
解决方案:(1)将不存在的key进行缓存(2)使用布隆过滤器
(2)缓存击穿
某一个热点key过期,导致大量请求直接打到数据库
解决方案:通过加锁的方式,让一个请求去读数据库并更新缓存,其他请求等待
(3)缓存雪崩
当大量的key的在同一个时间过期,就会导致大量的请求全走到数据库,导致缓存雪崩
解决方案:为不同的key设置不同的过期时间(比如加上一个随机值)
缓存与数据库一致更新
更新数据库和缓存时,多个请求的操作顺序引发的不一致问题, 一个解决方案是每次写数据库,但是删除缓存(这里不要用更新缓存,会存在不一致的情况)
热key问题
(1)如何发现热key
统计请求,按照4.0的LFU统计
(2)热key如何处理
缓存(读热key),迁移到单独的分片(写热key)
分布式锁
(1)原子命令
setnxexpire,需要考虑超时续期问题
(2)几种实现
RedLock:思想是同时向多个主节点写,如果超过一半的主节点写成功则获得锁
Redisson:思想是基于setnxexpire,在获得锁之后,隔一段时间对锁进行续期(基于watchdog)(获得锁的任务未执行完), Redisson也对RedLock提供了实现
Redisson分布式锁是基于lua脚本的
公司的分布式锁:提供了基于zk、redis和cellar的三种实现
应用场景
缓存
消息队列
使用双端队列的性质,可以左边入队,右边出队或者右边入队,左边出队,出队时可以使用blpop和brpop来避免出空队的问题