redis知识点总结
redis是现在web开发中用的最广泛的组件之一了,用了很多年,是时候总结一下用法和经验了。
持久化
两个方案:DUMP和LOG。前者就是直接生成快照(SAVE),或者主进程fork一个进程(BGSAVE,内存使用要加倍)然后将内存里面的数据全部存入文件;后一个方案类似LSM-TREE的思路,将操作生成日志,并定期重写。
SAVE会导致其他命令被阻塞,BGSAVE同样也会(数据量过大时,复制内存本身也很消耗时间),而且系统如果down掉,save之后的数据就会丢失。
AOF的方案有两个问题:1是生成日志体积可能过大,2是恢复数据的速度比较慢。好在这两个都可以通过重写日志来改进。一般情况下,这两个方案是同时使用的。
事务
redis并不支持传统意义上的事务,想要原子提交,最简单的办法是:使用lua脚本。注意lua脚本逻辑如果太过复杂,可能会使redis阻塞较长时间。
另一种方案是用WATCH
配合MULTI
和EXEC
这两个命令,watch会生成一个乐观锁,当监控的变量改变时,后面的pipeline就会中断执行并在程序中抛出异常。事务在收到EXEC
的时候才开始执行,前面的命令只是入列,而WATCH
恰好持续到EXEC
为止。注意如果pipeline中有命令出错,redis事务并不会终止,而是会接着把所有命令跑完,而且redis事务本身也不支持回滚,有需要的话你只能手动回滚。
HA
简单的是一主一从,用SYNC(新版是PSYNC)同步到从库。
稍微复杂的是一主多从的哨兵模式,主down了以后会在从库中选出新的主。
集群
本质仍然是sharding. redis集群节点之间相互通信,知道需要处理的命令在哪个分片,如果客户端查询到错误的节点,该节点会返回一个MOVED错误,redirect客户端到正确的节点。当然,如果使用redis驱动的话,对客户端是透明的。
由于每个节点负责多个分区(在这里称为slot),再平衡过程很简单,就是将其他节点的分区转移到新节点即可(移除节点也类似)。
集群和副本集配合使用,节点的副本即所谓“从节点”。
底层数据结构
字符串 -> 作者自己实现的简单动态字符串(SDS);
zset -> skiplist(类似红黑树,但是更简单)
list -> 就是链表
set/hset -> hash表
优化:如果set都是数值,且数量较少,会使用intset节省内存;如果array和hset都是小整数或者短字符串,会使用ziplist节省内存。
GC:自己实现的引用计数。
过期策略
redis将所有key的过期时间保存在一个公用的过期字典里,用来计算ttl.
过期删除使用惰性策略+定期删除。
save的时候会忽略掉已经过期的key,aof只有在显式删除key后才会追加DEL命令。不过aof重写日志的时候也会判断键是不是过期,类似save.