这个月粗略的读了一下《redis实战》这本书,学习了一下日常的操作命令,以及它的一些高阶操作之外,感触颇多,以下是对感兴趣深入的几章的深入和扩展阅读。
1. redis为什么这么快?
Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。除了书中介绍的,单线程、单独设计了数据结构、使用了不同的底层模型,其中有一点,I/O多路复用,我稍微深入地了解了一下。
I/O多路复用(multiplexing)
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流。
就好比一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。 这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。
2. redis如何降低内存占用?
压缩列表
Redis对于每种数据结构、无论是列表、哈希表还是有序集合,在决定是否应用压缩列表作为当前数据结构类型的底层编码的时候都会依赖一个开关和一个阈值,开关用来决定我们是否要启用压缩列表编码,阈值总的来说通常指当前结构存储的key数量有没有达到一个数值(条件),或者是value值长度有没有达到一定的长度(条件)。任何策略都有其应用场景,不同场景应用不同策略。为什么当前结构存储的数据条目达到一定数值使用压缩列表就不好?压缩列表的新增、删除的操作平均时间复杂度为O(N),随着N的增大,时间必然会增加,他不像哈希表可以以O(1)的时间复杂度找到存取位置,然而在一定N内的时间复杂度我们可以容忍。然而压缩列表利用巧妙的编码技术除了存储内容尽可能的减少不必要的内存开销,将数据存储于连续的内存区域,这对于Redis本身来说是有意义的,因为Redis是一款内存数据库软件,想办法尽可能减少内存的开销是Redis设计者一定要考虑的事情。
另外,经过仔细琢磨,我认为使用压缩列表的好处除了节约内存之外,还有减少内存碎片的作用,我把这种行为叫做"合并存储",也就是将很多小的数据块存储在一个比较大的内存区域,试想想,如果我们将要存储的数据都是很小的条目,我们为每一个数据条目都单独的申请内存,结果是这些条目将有可能分散在内存的每一个角落,最终导致碎片增加,这是一件令人头疼的事情。
分片式散列
分片本质上就是基于某些简单的规则,将数据划分为更小的部分,然后根据数据所属的部分来决定将数据分配到哪或者从哪获取数据。而Redis分片就是将数据拆分到多个Redis服务器的过程,这样每个Redis服务器将只存储原数据的子集。
Redis分片的作用:
- 允许使用很多电脑的内存总和来支持更大的数据库。没有分片,你就被局限于单机能支持的内存容量。
- 允许伸缩计算能力到多核或多服务器,伸缩网络带宽到多服务器或多网络适配器。
Redis分片的方式:
假设我们有4个Redis服务器R0,R1,R2,R3,还有很多表示用户的键,像user:1,user:2....,我们能找到不同的方式来选择一个指定的键存储在哪个服务器中。换句话说,有许多不同的办法来映射一个键到一个指定的Redis服务器。 最简单的执行分片的方式之一是范围分片(range partitioning),通过映射对象的范围到指定的Redis实例来完成分片。例如,我可以假设用户从ID 0到ID 10000进入实例R0,用户从ID 10001到ID 20000进入实例R1。 这套办法行得通,然而,这样做有一个缺点,就是需要一个映射范围到实例的表格。这张表需要管理,不同类型的对象都需要一个表,所以范围分片在Redis中并不经常使用,因为这要比其它分片可选方案低效得多。