redis知识点总结

redis是现在web开发中用的最广泛的组件之一了,用了很多年,是时候总结一下用法和经验了。

持久化

两个方案:DUMP和LOG。前者就是直接生成快照(SAVE),或者主进程fork一个进程(BGSAVE,内存使用要加倍)然后将内存里面的数据全部存入文件;后一个方案类似LSM-TREE的思路,将操作生成日志,并定期重写。

SAVE会导致其他命令被阻塞,BGSAVE同样也会(数据量过大时,复制内存本身也很消耗时间),而且系统如果down掉,save之后的数据就会丢失。

AOF的方案有两个问题:1是生成日志体积可能过大,2是恢复数据的速度比较慢。好在这两个都可以通过重写日志来改进。一般情况下,这两个方案是同时使用的。

事务

redis并不支持传统意义上的事务,想要原子提交,最简单的办法是:使用lua脚本。注意lua脚本逻辑如果太过复杂,可能会使redis阻塞较长时间。

另一种方案是用WATCH配合MULTIEXEC这两个命令,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.