笔记

260 阅读9分钟

字符串(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】、执行主从切换。