写在前面
学习任何技术框架,切记不要一下子就陷入到细枝末节中,而是要做到对其整体有一个全面的了解,这样当我们学习某个知识点时就能清楚的知道,该知识点是属于哪个部分的,在整体的架构中是处于哪个位置的,从而就能避免迷失在细节中,而细节往往是最复杂,也是最难的。接下来我们就通过造轮子
的方式来了解下redis的整体架构。
1:给我们的轮子起一个名字
首先是因为是我们自己的,而且非常简单,其次是轮子,最后是kv数据库,所以就叫做MySimpleWheelKv
。
2:可以存哪些数据?
即数据模型
。
数据库的核心是数据,MySimpleWheelKv自然也是如此,那么我们的MySimpleWheelKv都支持存放什么数据呢?因为是键值数据库,所以基本的数据模型就是key-value模型
,其中key我们仅仅支持String,而Value支持String,整型。这样我们的kv数据MySimpleWheelKv可以存储的数据就确定了。
memcached和redis的key也都是仅仅支持String,但是Memcached value仅仅支持String,redis的value值则更加丰富,String,哈希,列表,集合等,也正因为redis多样化的value类型,使得其能够适应更多的应用场景,从而得到了更加广泛的应用。
3:可以对数据做什么操作?
即操作接口
。
作为资深的CRUD boy,我们设计的kv数据库自然是要支持crud操作了,如下:
PUT:新增或者是更新一个kv
GET:获取k对应的v
DELETE:删除k对应的整个kv对
其中以上的PUT,在有些kv数据库中也叫做SET,这里我们知道即可。
4:数据存储在哪里?
数据的存储位置,我们有两个选择,一个是内存,另一个是磁盘,二者比较如下:
内存:访问速度在百万分之一秒级别,速度极快,但是掉电所有数据会丢失,即不可持久化。
磁盘:访问速度在几毫秒级别,速度慢,但是掉电数据不会丢失,即可持久化。
我们的MySimpleWheelKv追求的就是简单和速度,所以我们选择将数据存储在内存中。
5:都有哪些模块?
我们想一下,当我们使用PUT存入一对kv的时候,需要连接到服务器,服务器需要解析我们的指令,解析完我们的指令后需要将数据写到内存中,等等,各种各样的工作,试想一下,如果是将这么多的功能都写到一个地方,程序势必变得非常臃肿,后续也难以维护,难以扩展,修改A功能影响到B功能,修改B功能又影响到C功能,为了解决这些问题业界的统一方案就是:分模块,那么什么是分模块呢?就是将不同的功能独立开发,然后相互协作,完成一个整体的功能,我们根据这个思想,分为如下模块:
其中每个模块作用如下:
数据访问模块:负责接收客户端发送的各种指令,如PUT
操作模块:负责解析具体的指令,并执行相应的操作,如PUT key val就会将key->val写到内存中
索引模块:负责加速数据查找,如执行GET key时,快速找到对应的val
存储模块:负责存储数据
接下来我们再看下每个模块应该如何设计。
5.1:数据访问模块
业界对数据访问有一个专业的话术,叫做访问模式,一般访问模式有如下两种:
封装调用库:即客户端和服务端在同一个进程中,客户端直接使用调用库的API操作,如果是java的话可能就是,给出一个jar包,
然后客户端执行如MySimpleWheelKvServer server = new MySimpleWheelKvServer();server.put(key, val)的操作。
socket通信:即服务端部署在独立的机器上,客户端通过网络访问,这也是我们使用的最多的一种方式。
封装调用库的方式仅支持单机模式,所有这里我们不采用,而考虑socket通信方式,但是socket通信也有其自身的一些问题,我们的数据访问模块需要处理网络连接,解析网络请求,根据不同的指令完成不同的数据存取操作,这些动作是用一个线程,还是用多个线程,这就是我们经常看到的I/O模型设计,是非常重要的一部分。单线程容易产生阻塞等待,多线程又存在资源竞争问题,确实很麻烦,需要好好的设计一番。
redis是单线程,但是又能实现高性能,可见redis的I/O模型设计还是比较NX的。
5.2:操作模块
该模块功能比较单一,即根据不同的指令调用存储模块的不同接口。
5.3:索引模块
索引,其实就是使用某些数据结构来加速查找,因为我们这里就是简单的key val,所以采用查询时间复杂度为O(1)的数据结构:哈希 。
5.4:存储模块
负责数据的存储,提供相应接口,供操作模块使用。
6:模块间如何协作
以PUT和GET为例。
6.1:PUT
客户端请求数据写到数据访问模块,数据访问模块解析指令,并调用操作模块执行指令,然后操作模块调用存储模块的写入数据接口,写入数据,然后存储模块内部异步的将新的数据更新到索引模块,供后续的数据查询。
6.2:GET
客户端请求数据写到数据访问模块,数据访问模块解析指令,并调用操作模块执行指令,然后调用存储模块的数据读取接口,存储模块内部调用索引模块接口获取数据位置,然后获取数据并返回。
6.3:DELETE
客户端请求数据写到数据访问模块,数据访问模块解析指令,并调用操作模块执行指令,然后调用存储模块的数据删除接口,存储模块删除数据,并调用索引模块更新索引,然后返回。
7:redis的架构图
上面简单分析了我们自己的MySimpleWheelKv的架构和设计,最后来看下Redis的架构图: