字符串(String)
内部结构类似ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配即分配空间(capacity)> 长度(len)。1M以下翻倍扩容,以上每次增加1M(最大512M)。
列表(list)
内部结构类似LinkedList,插入快、查询慢。内部采用quicklist,元素较少的情况下使用一块连续的内存存储、无前驱和后继指针即压缩列表(ziplist),元素多的时候以ziplist为单位加上前驱和后继指针构成的quicklist。
字典(hash)
内部结构类似HashMap,数组+列表构成,redis采用渐进式rehash,同时保存新旧两个哈希结构,然后后续通过定时任务逐渐将旧的迁移到新的,迁移完成,旧的删除。
集合(set)
内部结构类似HashSet,无序唯一。
有序集合(zset)
跳跃列表
分布式锁
Redis2.8之后可以使用该指令,临界区代码控制在60s内执行完毕,超过60s将无法保证严格串行,需要人工介入修正数据。
set lockName true ex 60 nx
可重入锁
通过使用ThreadLocal和引用计数可以实现可重入锁。
锁冲突处理
- 直接抛出异常、通知用户稍后重试
- sleep一会再次重试
- 将请求存至延时队列、一会再试
队列
右进左出,构成队列。blpop为阻塞读,没有数据的时候进入休眠状态,消息延迟几乎为零。另外,长时间休眠后导致连接被释放,可以在捕获异常后重试。
rpush list apple pear
blpop list
延迟队列
zadd task 1000 task1
zadd task 2000 task2
# 多个线程
zrangebyscore task 0 1000
# 谁删除成功算谁的
zrem task task1
位图
setbit s 1 1
getbit s 1
# 有多少个1【签到次数】
bitcount s
# 第一个1在哪个位置【第一次签到时间】
bitpos s 1
HyperLogLog
- PV:网页的请求数、redis自增即可
- UV: 网页的访问用户数量、set集合scard获取总数太浪费【HyperLogLog有较小的误差】
pfadd uv usrId
pfcount uv
pfmerge合并统计结果、数值较小时采取稀疏矩阵、空间占用很小、量超过阈值后才会转为稠密矩阵、占用12KB空间【大大节约空间】。
布隆过滤器
布隆过滤器由一个大型的位数组和几个不一样的无偏hash函数构成.向布隆过滤器中添加key时,使用这几个函数进行hash,在对位数组长度取模。判断是否存在时候必须都一致才认为存在。使用的时候不要让设置元素数量远远超过实际元素数量,由于刚超出设置元素数量时、error_rate不会立马飙升,给我们充足的时间去重建。【布隆过滤器认为不存在的就一定不存在,存在的不一定存在】
# initial_size=100,error_rate=0.01
bf.reserve 100 0.01
bf.add task1 true
bf.exists task1
限流(令牌桶算法)
redis 4.0 以后开始支持的扩展模块
# 10:容量 1 60:1/60s 3:每次取3个
CL.THROTTLE test 10 1 60 3
1) (integer) 0 # 0 允许 1 拒绝
2) (integer) 11 # 容量
3) (integer) 8 # 剩余容量
4) (integer) -1 # 如果拒绝显示多长时间后可以再次尝试
5) (integer) 36 # 多长时间容量恢复到最大值
GeoHash
切蛋糕法、每个小方块(一个整数)代表一组经纬度、GeoHash再对整数进行base32编码变成一个字符串
# 本质是zset删除使用zrem
geoadd company 113.27 33 t1
geoadd company 114.27 33 t2
# 计算距离
geodist company t1 t2 km
# 获取经纬度
geopos company t1
# 获取元素的hash
geohash company t1
# 附近的元素
georadiusbymember company t1 20 km count 3 asc
多路复用
selector负责注册事件、处理读事件、处理写事件。在timeout事件内等待以上事件、如果发生即执行、如果无则返回,定时任务死循环。
Redis的多路复用
基本和一般的多路复用一致、响应队列无数据会将当前客户端描述符从write_fds里移除、避免资源浪费。在协调定时任务上通过最小堆的结构实现、最快要执行的任务放在最上面、timeout即为距离最快要执行任务的时间
RESP
RESP是Redis序列化协议,虽然有大量冗余的回车换行、但是简单、易于理解。
- 单行字符串以“+”符号开头
- 多行字符串以“¥”符号开头、后跟字符串长度
- 整数“:”符号开头
- 错误以“-”符号开头
- 数组以“*”符号开头
- 单元结束换行符\r\n
# 整数1024
:1024\r\n
# 数组【1,2,3】
*3\r\n:1\r\n:2\r\n:3\r\n
持久化
持久化有两种形式、一种是RDB即内存数据的二进制序列化形式、第二种是AOF即内存数据修改的日志文本。
RDB
Redis使用操作系统的多进程COW(Copy On Write)机制来实现快照持久化即调用glibc函数fork产生一个子进程、父进程继续处理客户端请求、两者共享内存。这时候如果客户端请求过来修改内存数据、就会使用COW机制进行数据段分离、将要修改的那一页数据复制一份、父进程对复制的进行修改、随着修改的越来越多、会慢慢接近原来的2倍。
AOF
重新执行这些指令即可恢复。随着运行时间的增加、日志文件越来越大、这时候可以使用bgwriteaof指令进行重写即fork一个子进程对内存遍历转化为set指令+期间的增量指令形成新的日志文件。同时AOF日志以文件保存、所以内容写到了以内核为文件描述符分配的一个内存缓存中、内核会异步把数据刷到磁盘。一般设置为1s调用fsync函数刷一次。
Redis4.0混合持久化
Redis重启的时候、先加载RDB文件、然后重放增量AOF日志、大大提高了重启效率。
管道
该技术本质上由客户端提供。由于写只需要讲内容发送到send buffer即可、而读需要经过服务端写到send buffer -> 缓冲数据发送到网卡 -> 路由到客户端网卡 -> 数据放到客户端内核为套接字分配的recv buffer中 -> 读取数据。原先的模式 写时间 -> 读动作之外的耗时 -> 读时间 -> 写时间 -> 读动作之外的耗时 -> 读时间,使用管道后省了一个读动作之外的耗时即第一个响应耗时后、第二个响应数据到了recv buffer了,直接读取即可。
事务
事务不具备原子性即事务中发生错误会继续执行结束
watch num
# 期间num发生变化、exec后将返NULL即事务失败
multi
incr num
exec
发布订阅PubSub
消息不会持久化、一个消费者重启后期间数据丢失、该功能几乎没有使用场景
subscribe test
publish test hello
内存回收机制
Redis的存储以页为单位、如果你删除了key内存没有减少即该页上还存在其他key,该部分空间会被再次利用。
CAP理论
- C:Consistent即一致性
- A:Availability即可用性
- P:Partition tolerance即分区容忍性
当网络分区发生异常、一致性和可用性难以两全。
主从同步
当发生分区故障时、Redis主从依然满足可用性、Redis保证最终一致性、从节点恢复后会逐渐同步至最新。Redis同步是指令流、记录在一个定长的环形数组上、从节点一边同步指令流、一边反馈偏移量(同步位置)。网络异常后环形数据可能进入第二圈即覆盖了、但覆盖部分尚未同步、这时候需要进行快照同步、主节点进行bgsave后将快照传送到从节点、从节点加载完成后继续同步指令流,如果还存在覆盖则继续快照同步【循环】。
增加从节点
快照同步后继续指令流同步。
无盘复制
通过套接字直接将快照内容传送到从节点、从节点生产快照后一次性加载
wait
异步->同步、失败将会阻塞
set test 1
1:从节点数量 0:无限等待,其他等待具体时间
wait 1 0
哨兵模式(Sentinel)
Sentinel监控主从、主宕机则选举产生新的主节点、原先的主节点恢复后成为从节点,自动完成主从切换。
# 至少一个从节点正常才对外提供服务
min-slaves-to-write 1
# 10s内没有收到从节点反馈认为该从节点不正常
min-slaves-max-lag 10
Codis
Codis默认将所有的key划分为1024个槽位(slot),客户端的key使用crc32得到hash值在对1024取模得到槽位、多个Codis在zk/etcd中维护了槽位和redis的映射关系,Codis在相应的redis中执行指令。Codis对Redis进行了改造、增加了SLOTSSCAN指令,扫描出待迁移的key、然后逐个迁移、如果客户端请求正在迁移的key,则立即对该key迁移后返回给客户端。
Redis Cluster
Redis Cluster默认将所有的key划分为16384个槽位(slot),客户端的key使用crc16得到hash值在对16384取模得到槽位,客户端会请求后会在本地保存一份映射表,如果请求错误,MOVED指令告诉客户端应该去请求哪个节点同时更新映射表。
数据迁移
大致过程:从源节点获取内容->存到目标节点->从源节点删除,整个过程是同步执行的。客户端访问正在迁移的key,先去目标节点执行一个不带任何参数的asking【告诉目标节点必须处理】然后在目标节点执行操作。
容错
cluster-require-full-coverage不设置,默认一个节点故障即不在对外提供服务。每个节点可以配置主从,cluster-node-timeout设置持续timeout时间失联后判定为故障、Redis集群采用Gossip协议广播自己的状态和改变整个集群的认知。当节点发现某个节点失联【可能下线PFail】后进行广播,当收到某个节点的失联的节点数量达到大多数,才标记下线【确定下线Fail】、执行主从切换。