浅述NoSQl之Redis+内部机制ITeye - 凯时娱乐

浅述NoSQl之Redis+内部机制ITeye

2019-01-11 11:38:39 | 作者: 梦竹 | 标签: 效劳,指令,客户端 | 浏览: 392

1  Redis概述
1.1 前语
最近公司的项目用Redis,所以了解一下。本文首要是论述Redis的内部运转机制。

Redis是一个开源、支撑网络、依据内存亦可耐久化的日志型、键值对存储数据库。运用ANSI C编写,并供给多种语言的API。

其开发由VMware掌管,是最盛行的键值对存储数据库之一。

Redis的一大特色便是速度反常快,官方发布的功用测验成果显现,每秒钟能够到达10万次的操作。

1.2 装置和验证
在Redis的官网上,咱们下载了redis的稳定版redis-2.8.9.tar.gz。

咱们顺次履行以下指令:

$ tar xzf redis-2.8.9.tar.gz

$ cd redis-2.8.9

$ make

在履行完以上指令后,会在同级目录下生成src目录。再履行指令:

$ src/redis-server

就发动好了Redis。



快速验证效劳是否发动成功用够履行以下指令:

$ src/redis-cli

redis set foo bar

OK

redis get foo

"bar"

2  Redis的内部完结
咱们首要来重视一下redis的内部完结。

2.1 效劳器初始化
Redis效劳器除了保持效劳器状况之外,最重要的便是将数据结构、数据类型、业务、Lua 环境、事情处理、数据库、耐久化等功用模块组合起来。

从发动Redis效劳器,到效劳器能够承受外来客户端的网络衔接这段时刻, Redis 需求履行一系列初始化操作。

整个初始化进程能够分为以下六个进程:

1)  初始化效劳器大局状况。

2)  载入装备文件。

3)  创立daemon 进程。

4)  初始化效劳器功用模块。

5)  载入数据。

6)  开端事情循环。

以下各个末节将介绍 Redis 效劳器初始化的各个进程。

2.1.1 初始化效劳器大局状况
redis.h/redisServer结构记载了和效劳器相关的一切数据,这个结构首要包括以下信息:

ü  效劳器中的一切数据库。

ü  指令表:在履行指令时,依据字符来查找相应指令的完结函数。

ü  事情状况。

ü  效劳器的网络衔接信息:套接字地址、端口,以及套接字描述符。

ü  一切已衔接客户端的信息。

ü  Lua 脚本的运转环境及相关选项。

ü  完结订阅与发布(pub/sub)功用所需的数据结构。

ü  日志(log)和慢查询日志(slowlog)的选项和相关信息。

ü  数据耐久化(AOF 和 RDB)的装备和状况。

ü  效劳器装备选项:比方要创立多少个数据库,是否将效劳器进程作为daemon 进程来运转,最大衔接多少个客户端,紧缩结构(zip structure)的实体数量,等等。

ü  核算信息:比方键有多少次指令、不射中,效劳器的运转时刻,内存占用,等等。

为了简练起见,上面只列出了单机情况下的 Redis 效劳器信息,不包括SENTINEL 、 MONITOR、CLUSTER 等功用的信息。

在这一步,程序创立一个redisServer结构的实例变量server用作效劳器的大局状况,并将server的各个特点初始化为默许值。

当server变量的初始化完结之后,程序进入效劳器初始化的下一步:读入装备文件。

2.1.2 载入装备文件
Redis.conf文件里各个装备项的含义都有英文注释,这儿不再烦琐。

在初始化效劳器的上一步中,程序为server变量(也便是效劳器状况)的各个特点设置了默许值,但这些默许值有时候并不是最合适的:

ü  用户或许想运用 AOF 耐久化,而不是默许的 RDB 耐久化。

ü  用户或许想用其他端口来运转Redis,以防止端口抵触。

ü  用户或许不想运用默许的 16 个数据库,而是分配更多或更少数量的数据库。

ü  用户或许想对默许的内存约束办法和收回战略做调整。

等等。

为了让运用者按自己的要求装备效劳器, Redis答应用户在运转效劳器时,供给相应的装备文件(config file)或许显式的选项(option), Redis在初始化完server变量之后,会读入装备文件和选项,然后依据这些装备来对server变量的特点值做相应的修正:

1.  假如单纯履行redis-server指令,那么效劳器以默许的装备来运转 Redis。

2.  另一方面,假如给 Redis 效劳器送入一个装备文件,那么 Redis 将按装备文件的设置来更新效劳器的状况。

比方说,通过指令redis-server /etc/my-redis.conf ,Redis 会依据

my-redis.conf文件的内容来对效劳器状况做相应的修正。

3.  除此之外,还能够显式地给效劳器传入选项,直接修正效劳器装备。

举个比方,通过指令redis-server --port 10086,能够让 Redis 效劳器端口变更为10086。

4.  当然,一起运用装备文件和显式选项也是能够的,假如文件和选项有抵触的当地,那么优先运用选项所指定的装备值。

举个比方,假如运转指令redis-server /etc/my-redis.conf –port 10086,而且my-redis.conf也指定了port选项,那么效劳器将优先运用—port 10086(实践上是选项指定的值覆盖了装备文件中的值)。

2.1.3 创立 daemon 进程
Redis 默许以 daemon 进程的方法运转。

当效劳器初始化进行到这一步时,程序将创立 daemon 进程来运转 Redis ,并创立相应的 pid 文件。

2.1.4 初始化效劳器功用模块
在这一步,初始化程序完结两件事:

为server变量的数据结构子特点分配内存。

初始化这些数据结构。

为数据结构分配内存,并初始化这些数据结构,等同于对相应的功用进行初始化。

比方说,当为订阅与发布所需的链表分配内存之后,订阅与发布功用就处于安排妥当状况,随时能够为 Redis 所用了。

在这一步,程序完结的首要动作如下:

  初始化 Redis 进程的信号功用。

  初始化日志功用。

  初始化客户端功用。

  初始化同享目标。

  初始化事情功用。

  初始化数据库。

  初始化网络衔接。

  初始化订阅与发布功用。

  初始化各个核算变量。

  相关效劳器惯例操作(cron job)到时刻事情,相关客户端应对处理器到文件事情。

  假如 AOF 功用已翻开,那么翻开或创立 AOF 文件。

  设置内存约束。

  初始化 Lua 脚本环境。

  初始化慢查询功用。

  初始化后台操作线程。

完结这一步之后,效劳器打印出 Redis 的 ASCII LOGO 、效劳器版别等信息,表明一切功用模块现已安排妥当,能够等候被运用了。

尽管一切功用现已安排妥当,但这时效劳器的数据库仍是一片空白,程序还需求将效劳器上一次履行时记载的数据载入到当时效劳器中,效劳器的初始化才算真实完结。

2.1.5 载入数据
在这一步,程序需求将耐久化在 RDB 或许 AOF 文件里的数据,载入到效劳器进程里边。

假如效劳器有启用AOF功用的话,那么运用 AOF 文件来复原数据;不然,程序运用 RDB 文件来复原数据。

当履行完这一步时,效劳器打印出一段载入完结信息:

[6717] 22 Feb 11:59:14.830 * DB loaded from disk: 0.068 seconds

2.1.6 开端事情循环
到了这一步,效劳器的初始化现已完结,程序翻开事情循环,开端承受客户端衔接。

以下是效劳器在这一步打印的信息:

[6717] 22 Feb 11:59:14.830 * The server is now ready to accept connections on port 6379

至此初始化完结。

2.2 内部结构
Redis的内部结构如下图所示:




各功用模块阐明如下:

File Event: 处理文件事情(在多个客户端中完结多路复用,承受它们发来的指令恳求(读事情),并将指令的履行成果回来给客户端(写事情))

Time Event: 时刻事情(更新核算信息,整理过期数据,隶属节点同步,定时耐久化等)

AOF: 指令日志的数据耐久化

RDB:实践的数据耐久化

Lua Environment : Lua 脚本的运转环境. 为了让 Lua 环境契合 Redis 脚本功用的需求, Redis 对 Lua 环境进行了一系列的修正,包括增加函数库、替换随机函数、维护大局变量,等等

Command table(指令表):在履行指令时,依据字符来查找相应指令的完结函数。

Share Objects(目标同享):

首要存储常见的值:a.各种指令常见的回来值,例如回来值OK、ERROR、WRONGTYPE等字符;b. 小于 redis.h/REDIS_SHARED_INTEGERS (默许1000)的一切整数。通过预分配的一些常见的值目标,并在多个数据结构之间同享目标,程序防止了重复分配的费事。也便是说,这些常见的值在内存中只要一份。

Databases:

Redis数据库是真实存储数据的当地。当然,数据库自身也是存储在内存中的。

Databased的数据结构伪代码如下:

typedef struct redisDb {

  // 保存着数据库以整数表明的号码

  int id;



  // 保存着数据库中的一切键值对数据

  // 这个特点也被称为键空间(key space)

  dict *dict;



  // 保存着键的过期信息

  dict *expires;



  // 完结列表堵塞原语,如 BLPOP

  // 在列表类型一章有具体的评论

  dict *blocking_keys;

  dict *ready_keys;



  // 用于完结 WATCH 指令

  // 在业务章节有具体的评论

  dict *watched_keys;

} redisDb;

一个数据库,在内存中的数据结构如下图所示:


Database的内容关键包括:

  数据库首要由 dict 和 expires 两个字典构成,其间 dict 保存键值对,而 expires 则保存键的过期时刻。

  数据库的键总是一个字符串目标,而值能够是恣意一种 Redis 数据类型,包括字符串、哈希、调集、列表和有序集。

  expires 的某个键和 dict 的某个键一起指向同一个字符串目标,而 expires 键的值则是该键以毫秒核算的 UNIX 过期时刻戳。

  Redis 运用慵懒删去和定时删去两种战略来删去过期的键。

  更新后的 RDB 文件和重写后的 AOF 文件都不会保存现已过期的键。

  当一个过期键被删去之后,程序会追加一条新的 DEL 指令到现有 AOF 文件结尾。

  当主节点删去一个过期键之后,它会显式地发送一条 DEL 指令到一切隶属节点。

  隶属节点即便发现过期键,也不会自作主张地删去它,而是等候主节点发来 DEL 指令,这样能够确保主节点和隶属节点的数据总是共同的。

  数据库的 dict 字典和 expires 字典的扩展战略和一般字典相同。它们的缩短战略是:当节点的填充百分比缺乏 10% 时,将可用节点数量削减至大于等于当时已用节点数量。

2.3 客户端衔接到效劳器
当Redis效劳器完结初始化之后,它就准备好能够承受外来客户端的衔接了。

当一个客户端通过套接字函数connect到效劳器时,效劳器履行以下进程:

1.  效劳器通过文件事情无堵塞地accept客户端衔接,并回来一个套接字描述符 fd 。

2.  效劳器fd创立一个对应的redis.h/redisClient结构实例,并将该实例加入到效劳器的已衔接客户端的链表中。

3.  效劳器在事情处理器为该fd相关读文件事情。

完结这三步之后,效劳器就能够等候客户端发来指令恳求了。

Redis 以多路复用的方法来处理多个客户端,为了让多个客户端之间独立分隔、不相互搅扰,效劳器为每个已衔接客户端保持一个redisClient 结构,然后独自保存该客户端的状况信息。

redisClient结构首要包括以下信息:

  套接字描述符。

  客户端正在运用的数据库指针和数据库号码。

  客户端的查询缓冲(query buffer)和回复缓存(reply buffer)。

  一个指向指令函数的指针,以及字符串方式的指令、指令参数和指令个数,这些特点会在指令履行时运用。

  客户端状况:记载了客户端是否处于SLAVE、MONITOR 或许业务状况。

  完结业务功用(比方MULTI和WATCH)所需的数据结构。

  完结堵塞功用(比方BLPOP和BRPOPLPUSH)所需的数据结构。

  完结订阅与发布功用(比方PUBLISH和SUBSCRIBE)所需的数据结构。

  核算数据和选项:客户端创立的时刻,客户端和效劳器终究交互的时刻,缓存的巨细,等等。

为了简练起见,上面列出的客户端结构信息不包括仿制(replication)的相关特点。

2.4 指令的恳求、处理和成果回来
当客户端连上效劳器之后,客户端就能够向效劳器发送指令恳求了。

从客户端发送指令恳求,到指令被效劳器处理、并将成果回来客户端,整个进程有以下进程:

1.  客户端通过套接字向效劳器传送指令协议数据。

2.  效劳器通过读事情来处理传入数据,并将数据保存在客户端对应redisClient结构的查询缓存中。

3.  依据客户端查询缓存中的内容,程序从指令表中查找相应指令的完结函数。

4.  程序履行指令的完结函数,修正效劳器的大局状况server变量,并将指令的履行成果保存到客户端redisClient结构的回复缓存中,然后为该客户端的fd相关写事情。

5.  当客户端fd的写事情安排妥当时,将回复缓存中的指令成果传回给客户端。至此,指令履行结束。

3  指令恳求实例:SET
为了更直观地了解指令履行的整个进程,咱们用一个实践履行 SET 指令的比方来解说指令履行的进程。

假定现在客户端 C1 是衔接到效劳器 S 的一个客户端,当用户履行指令SET YEAR 2014 时,客户端调用写入函数,将协议内容*3\r\n$3\r\nSET\r\n$4\r\nYEAR\r\n$4\r\n2014\r\n" 写入衔接到效劳器的套接字中。假如你翻开了AOF效劳的话,你会发现这些协议的内容都会写入到这个文件中。。

当 S 的文件事情处理器履行时,它会察觉到 C1 所对应的读事情现已安排妥当,所以它将协议文本读入,并保存在查询缓存。

通过对查询缓存进行剖析(parse),效劳器在指令表中查找SET字符串所对应的指令完结函数,终究定位到t_string.c/setCommand函数,别的,两个指令参数YEAR和2014也会以字符串的方式保存在客户端结构中。

接着,程序将客户端、要履行的指令、指令参数等送入指令履行器:履行器调用setCommand函数,将数据库中YEAR键的值修正为2014,然后将指令的履行成果保存在客户端的回复缓存中,并为客户端fd相关写事情,用于将成果回写给客户端。

由于YEAR键的修正,其他和数据库命名空间相关程序,比方 AOF、REPLICATION 还有业务安全性查看(是否修正了被 WATCH 监督的键?)也会被触发,当这些后续程序也履行结束之后,指令履行器退出,效劳器其他程序(比方时刻事情处理器)持续运转。

当 C1 对应的写事情安排妥当时,程序就会将保存在客户端结构回复缓存中的数据回写给客户端,当客户端接收到数据之后,它就将成果打印出来,显现给用户看。

以上便是SET YEAR 2014指令履行的整个进程。



小结:

效劳器通过初始化之后,才干开端承受指令。

效劳器初始化能够分为六个进程:

1.  初始化效劳器大局状况。

2.  载入装备文件。

3.  创立 daemon 进程。

4.  初始化效劳器功用模块。

5.  载入数据。

6.  开端事情循环。

效劳器为每个已衔接的客户端保持一个客户端结构,这个结构保存了这个客户端的一切状况信息。

客户端向效劳器发送指令,效劳器承受指令然后将指令传给指令履行器,履行器履行给定指令的完结函数,履行完结之后,将成果保存在缓存,终究回传给客户端。
版权声明
本文来源于网络,版权归原作者所有,其内容与观点不代表凯时娱乐立场。转载文章仅为传播更有价值的信息,如采编人员采编有误或者版权原因,请与我们联系,我们核实后立即修改或删除。

猜您喜欢的文章