Redis
基础和应用
5种基本结构
-
string
- 动态字符串
- 结构类似于ArrayList
- 最大value - 512MB
-
list
- 双向链表
- 结构类似于LinkedList
-
hash
- 数组+链表
- 结构类似于老的HashMap
-
set
- 数组+链表
- value全用null填充
- 结构类似于老的HashSet
-
zset
- 跳跃链表
分布式锁
-
应用
- 第一步:setnx
- 第二步:expire
-
set-expire原子性
如果setnx和expire中间出现意外打断,造成expire没有得到执行,那么这个锁将永远得不到释放
-
set key value ex
后来提供的指令
可以保证赋值的过程和过期的设置是两个操作是一个原子性的
-
-
超时问题
-
阻塞-锁过期-其他线程拿到锁
假设A持有锁,过期时间是10s,但是A由于某种阻塞,自身阻塞超过10s。
这时候锁过期了,B也得到了锁,会造成锁不互斥的情况。
解决方式见:Redlock
-
-
可重入问题
上述的策略都是不支持可重入锁的
-
使用hash实现可重入锁
可以使用hash结构实现可重入锁
实现思路:
key :锁名称
field :线程id
value:锁计数
每次获得锁的时候,检查锁是否存在,线程id是不是对应
离开锁的时候,锁计数-1
-
-
RedLock
普通算法存在的问题:
在主从结构中,如果某个线程A刚在主节点刚获得锁,还没来得及同步到从节点,然后主节点挂掉了,就会造成锁丢失。
下一个线程B还是可以在新晋升的主节点申请到这把锁-
前提:更多独立的Redis示例
-
RedLock算法
加锁,向过半的节点发送set key value nx ex 指令
只要过半的节点都认为成功,那就成功
释放锁,向全部的节点发送del指令 -
代价
- 同时对多个独立的redis读写,性能降低
- 引入了额外的library
- 运维代价也上升
-
bitMap
-
底层还是字符串
-
用法
注意bitcount和bitpos的[start,end]是以字节为单位的
- setbit
- getbit
- bitcount
- bitpos
HyperLogLog
GeoHash
-
底层是zset
-
GeoHash算法
把二维坐标映射到一维
-
用法
- geoadd
- geodist
- geopos
- geohash
- georadiusbymember
布隆过滤器
-
原理
添加key的时候:输入一个key,经过多个Hash函数得到多个下标,然后把对应值的下标位数设为1。
判断key存在的时候:输入一个key,经过多个Hash函数得到多个下标。(1) 如果下标都为1,那么认为存在(2)如果下标有一个不为1,认为不存在- 优点:用很小的空间完成工作
- 缺点:有一定的误判率
-
用法
- bfadd
- bfexists
- bfmadd(批量)
- bfmexists(批量)
-
误判率
如果数组很大,很稀疏,误判率就会很低
如果数组很小,很拥挤,误判率就会上升
在实际使用的时候,应当注意,不要让实际元素数量超过初始化容量。否则应该进行更大size的重建。
限流
-
简单限流
-
断尾限流
-
效果
一个用户在指定的时间内进行某个行为最多只能N次
-
-
-
漏斗限流
-
基本原理
漏斗结构重要的几个参数
漏斗容量
漏嘴流水速率
漏斗剩余空间
上次漏水时间每次申请往里添水的时候,先计算从上次漏水到当前时间,流了多少水,更新漏斗剩余空间。
如果剩余空间够此次添水,允许添水。
如果剩余空间不够此次添水,不允许添水 -
RedisCell
- cl.throttle指令
-
scan
-
应用
查找符合某种规则的key,带有limit选项
游标从0开始,每次查找,都会返回下次的游标地址
游标地址重新归零的时候,代表查找完一圈 -
比keys多了limit选项
-
说明
-
limit指的是槽位数量
-
遍历顺序
遍历顺序不是按照槽位地址递增
-
原理
线程IO模型
- 非阻塞IO
- 多路复用
- 指令队列
- 相应队列
- 定时任务
通信协议
-
RESP
- 直观的文本协议
持久化
-
快照
-
二进制序列化形式
-
文件紧凑
-
实现原理
- COW原理
- fork子进程
- 数据页的COW处理
-
-
AOF
-
修改数据的指令记录文本
-
可读性强
-
随着运行会越来越大
-
上线加载AOF耗时长
-
AOF重写(瘦身)
-
原理
- 开辟一个子进程
- 对内存进行扫描
- 转化成操作指令
- 拼接上增量AOF
-
-
-
AOF策略
- 从不
- 每秒
- 每个操作
-
-
混合持久化
- 快照
- 增量AOF
管道
-
提高速度
-
原理
- 用缓冲区,暂存一些指令
- 一起发送,一起接受
- 减少网络传输次数
事务
- multi
- exec
- discard
- 可配合watch使用
- 使用管道执行事务可以更优化
订阅发布
用的不多
内存机制
-
内存回收
-
以页为单位回收
操作系统以页为单位回收内存,只要这个页上有一个key还在使用,就不会被回收。
因此删除了很多key,也不能马上看到内存腾出来。
redis会重新利用那些尚未回收的空闲内存
-
-
内存分配
- jemalloc(facebook)默认,性能好一些
- tcmalloc(google)
集群
CAP
-
一致性
- Redis不满足一致性,但是满足最终一致性
-
可用性
- Redis满足可用性
-
分区容忍性
最终一致性
- 从节点努力追赶主节点
主从同步
-
主从同步
-
从从同步
链状,减轻主节点的负担
-
增量同步
- 同步的是指令流
- 修改性的执行记录在本地缓冲
- 缓冲异步发给从节点
- 如果缓冲环状数组发生覆盖,就要进行快照同步
-
快照同步
-
先bgsave到本地磁盘
-
再把快照文件发送到子节点
-
子节点加载快照文件
-
然后进行增量同步
-
如果快照部分太慢,增量又会出现缓冲覆盖,从而形成死循环
- 解决:配置合适的buffer大小
-
-
无盘复制
- 一遍遍历内存,一遍序列化发送到从节点
- 通过套接字
-
wait指令
-
wait N time
等待N个从节点完成同步,最多等待time秒
time为0表示一直等待
如果发生了网络分区,会造成永久阻塞,丧失可用性 -
让异步变成同步
-
哨兵
-
监控主节点的健康
-
3-5个节点,保证高可用
-
消息丢失问题
- 因为主从的异步复制(缓冲区)
-
基本原理
- 客户端连接从Sentinel获得主节点位置
- 主节点故障的时候,会把新的主节点位置告诉客户端
- 监控挂掉的原主节点,恢复之后变成从节点
-
基本用法
Cluster
-
槽位
- 16384个(2的14次方)
- 槽位信息存于每个节点
- 客户端来连接获得槽位信息,直接定位到目标节点
-
槽位定位算法
- CRC16算法,然后对16384取模
-
跳转
-
迁移
-
网络抖动
-
可能下线-确定下线
- 协商机制
- 认为某个节点可能失联的节点数达到大多数则认为该节点确认下线
扩展
Info指令
- Server
- Clients
- Memory
- Persistence
- Stats
- Replication
过期策略
-
主节点
-
定时删除
-
贪心策略
从过期字典中随机选出20个key
删除其中已经过期的key
如果过期的key超过了1/4,重新执行 1步骤 -
有最大时间限制,默认25ms
-
要避免大量key同时到期,加一点随机时间
-
-
惰性删除
-
-
从节点
- 主节点发送del到从节点
- 还是异步会不一致的问题
淘汰策略
-
noeviction
- 不继续写服务-默认
-
volatile-lru
- 设置了过期时间的key,lru
-
volatile-ttl
- 设置了过期时间的key,ttl最小的淘汰
-
volatile-random
- 设置了过期时间的key随机
-
allkeys-lru
- 全部key-lru
-
allkeys-random
- 全部key-随机
懒惰删除
-
引入
删除一个大的key的时候,会造成明显卡顿
-
原理
把要删除的key的数据结构从原数据结构上断开
把后续内存回收操作包装成一个任务放入异步队列
留给后台线程处理
并不是所有的key都向上面一样
小key直接删
大key先断开再删 -
flushdb和flushall后面加async会变成异步任务
-
AOF的Sync也很慢,也有一个独立的任务队和异步线程处理
源码
字符串
- embstr(短)
- raw(长)
- 阈值:长度超过44字节(64-1-16-3)
字典
-
包含两个Hashtable
-
渐进式搬迁
-
扩容条件
元素个数超过长度
如果在做bgsave,为了减少COW,不会去扩容
如果元素个数达到了5倍,不管bgsave强制扩容 -
缩容条件
元素个数低于数组长度的10%
压缩列表
快速列表
跳跃列表
紧凑列表
XMind - Trial Version