学习Redis(二)——单机数据库

一.Redis的安装:

首先来回忆一下如redis的安装和使用。
redis是使用C写的,所以需要先安装c的运行环境:

1、安装gcc套装:

yum install cpp
yum install binutils
yum install glibc
yum install glibc-kernheaders
yum install glibc-common
yum install glibc-devel
yum install gcc
yum install make

2、升级gcc (必须进行升级,redis的版本对gcc的版本也有要求)

yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash

3、设置永久升级:

echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile

接下来就是安装和启动redis了( Redis数据库也是标准的C/S结构,用户只有通过redis-cli 登录客户端后,方可使用redis),redis官网有安装教程
下图示安装好的目录结构:

redis的目录结构
注意:配置文件中有什么功能100%的有覆盖(如后台运行,数据库通知等),所以配置文件很重要,注释更重要!

二.Redis数据库

总结:软件都是由代码编写的,在学习的过程中,每一个对象总会有自己的数据结构。如redis服务器状态redisServer,客户端状态redisClient,数据库状态redisDB,各种状态里又有自己的属性和行为,这不就是面向对线吗。我们在分析的时候,不要忘了这个!

一个值得思考的问题:redis不是C写的吗,C不是面向过程语言吗?为什么不用C++?。
1.可移植性
2.自己编写数据结构,速度快,直用考虑redis自己使用就行
3.C的结构体中封装函数指针,就能实现面向对象,尽管这种方式谈不上优雅
4.redis的作者更喜欢C语言

1.数据库及键空间

数据库结构

2.键的生存或过期时间
1.定义:

生存时间:多少秒或毫秒后失效。命令:expire,pexpire
过期时间:到哪一个时间点(unix时间戳)失效。命令:expireat,pexpireat
上面4个设置时间的命令最终都是转化为pexpireat命令设置,并将其保存至数据库。
可通过命令 ttlpttl返回键的剩余生存时间,可通过命令:persist移除过期时间。

2.保存过期时间

过期字典的键指向键空间中某个键对象,值是一个long long类型的整数,保存着键的过期时间。

3.过期键的判断

是否存在过期时间?是否小于当前系统时间?

4.过期键的删除策略

定时删除:设定过期时间时,同时设定一个定时器,定时器执行对键的删除。
内存友好,cpu不友好,当过期键和命令请求同时较多时,cpu的时间应该优先用于命令而不该是删除过期键

惰性删除:被动删除,当从键空间获取键时,再判断此键是否过期,过期则立即删除。
cpu友好,内存不友好,内存泄露。内存中充斥着大量无用的过期键

定期删除:设置一个定时任务,每隔一段时间对数据库进行检查,删除其中的过期键。
前两者的折中,但是要确定合理的时间周期!

Redis采用惰性删除(db.c/expireIfNeeded)和定期删除(redis.c/activeExpirecycle)两种策略来实现自己的过期键删除策略。
惰性删除不再赘述,定期删除:redis中有定时任务函数(redis.c/serverCron),当此函数执行时,会调用activeExpirecycle函数,此函数在规定时间内,分多次遍历服务器各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除过期键,全局变量current_db会记录当前检查的数据库,在此次时间内未完成时,下次周期到来时,会继续从这个数据库进行检查。

5.redis的其他功能如何处理过期键

RDB:生成新的RDB文件时,程序会对数据库中的键进行检查,已过期的不会被保存到RDB文件中;载入RDB文件时,如果服务器以主服务器模式运行,那么已过期的不会被保存到RDB文件中,如果以从服务器模式运行时,不论是否过期都会全部加载至数据库。
AOF:当键过期后,程序向AOF文件中增加一条del命令;在执行AOF重写时,会先对键进行检查,已过期的键不会对AOF重写造成影响。
复制:当redis运行在复制模式下时。从服务器不会主动删除过期键,只有主服务器删除过期键后,显式的向多有从服务器发送del命令,这是从服务器才会删除过期键。这样就保证了数据的一致性。

3.数据库通知

客户端可以通过订阅频道或模式,获取数据库中键命令的情况。配置notify-keyspace-events的值即可打开此功能

notify-keyspace-events AKE 服务器发送所有的键空间和键事件
notify-keyspace-events AK服务器发送所有的键空间
notify-keyspace-events AE 服务器发送所有的键事件
notify-keyspace-events K$ 服务器只发送所有的有关字符串键有关的键空间通知
notify-keyspace-events Elg 服务器只发送所有的有关列表键有关的基础命令通知

测试:在一个客户端中订阅,可以在另一个客户度中数据命令:
订阅demo
发送数据库通知功能由notify.c/notifyKeyspaceEvent实现,

//type通知类型,就是我们在配置文件中配置的值,如果不是服务器所允许的类型,功能不可用
//event事件名称
//key产生事件的键
//dbid数据库编号
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid)

三.持久化

redis是内存数据库,所以需将数据持久化至磁盘中,有两种方式RDB和AOF

1.RDB

RDB持久化生成一个经过压缩的二进制文件,通过此文件可以还原生成RDB文件时的数据库状态(非空数据库及其中所有的键值对)

创建:

SAVE:执行期间阻塞服务器进程,服务器不能处理任何请求。
BGSAVE:BGSAVE会fork出一个子进程来创建RDB文件,服务器进程继续处理请求,但不能在执行过程中执行SAVE和BGSAVE(会产生竞争条件),会被服务器直接拒绝,但BGREWRITEAOF不会被拒绝,会在BGSAVE执行完之后执行。

下面是伪代码实现:

def SAVE():
	rdbSave()
	
def BGSAVE():
	pid = fork()
	if pid == 0:
		rdbSave()
		# 完成后向父进程发送信号
		signal_parent()
	elif pid > 0:
		# 父进程继续处理命令请求,并通过轮询等待子进程的信号
		handle_request_and_wait_signal()
	else:
		handle_fork_error()

载入:

服务器在启动时会检测RDB文件是否存在,并载入(rdb.c/rdbLoad(),save/load是一对,export/import是一对)载入时,服务器也会处于阻塞状态。
载入顺序

自动保存:

适用于BGSAVE
save a b
服务器在a秒内,对数据库进行了至少b次修改,可配置多个条件,只需满足其一即可。若没有配置,会有默认条件。条件会记录在服务器状态redisServer中:
保存条件
redis定时任务函数serverCron默认每隔100ms就会执行一次,检查是否满足保存条件。

RDB文件结构:

redis文件结构

具体的k-v结构就不写了,RDB文件结构了解即可,redis自身的文件检查工具redis-check-dump,还有其他第三方工具。od -cx dump.rdb 打印RDB文件。-c ASCII方式打印,-o 十六进制格式打印

2.AOF

RDB文件是存放键值对,而AOF是直接将Redis命令存放至文件中,所以AOF文件就是纯文本格式的。

AOF持久化实现:

AOF持久化可分为三个步骤:
1.命令追加:
当AOF持久化功能打开,服务器执行完一个写命令时,会将其追加到服务器状态(redisServer)的aof_buf缓冲区的末尾。
2.文件写入:
服务器每结束一个时间循环之前,调用flushAppendOnlyFile(),考虑是否将aof_buf缓冲区中的内容写入到AOF文件中。
3.文件同步:
OS并未实际的将写入的数据写入到磁盘文件中,而是放在内存缓冲区中,当空间满了或超过一定的时间,OS才会将内存缓冲区的数据写入磁盘。为保证数据安全性,OS提供了fsync(windows)和fdatasync(linux),可强制的让操作系统立即将缓冲区中的数据写入到磁盘里。
Redis的AOF持久化为appendfsync提供了三个值:
always:每个事件循环都要写入并同步。慢但是安全
everysec:每个事件循环都要写入,但是每一秒进行同步。最优
no:每个事件循环都要写入,同步的控制权完全的交给OS。最快不太安全。

AOF文件的载入:

AOF文件载入

AOF重写:

随着服务的运行,AOF文件越来越大,里面充斥着大量的set,del 等命令,这些命令对于数据来说是没有用的。所以为解决这个问题,Redis提供了AOF重写的功能,将创建一个新的AOF文件来替代现有的AOF文件。但新文件中不会有任何的冗余命令。
原理:旧的AOF文件可能对某一个键进行了很多次的增删改操作,AOF重写便是直接用尽可能少的写命令记录键值对(当键值对超过某一个阈值时,需要分多个命令,此阈值可修改)。
过程:命令BGREWRITEAOF和BGSAVE一样,AOF重写也会使用子进程。问题? 为什么不使用线程可以避免使用锁的情况下保证数据安全性。
但这样会导致服务器状态和重写后的AOF文件不一致。如何解决——AOF重写缓冲区:
AOF重写解决方案

3.一些问题

1.为什么是子进程而不是线程?
2.如何完全的保证redis数据的安全?(如突然断电)
3.两种持久化方式的区别?

四.事件

Redis服务器是一个事件驱动程序,服务器需要处理文件事件和时间事件。
文件事件:
服务器通过套接字与客户端进行连接,文件时间就是服务器对套接字的抽象,Server与Client或其他server的通信会产生相应的文件事件,服务器通过监听并处理这些文件事件来完成一系列的网络通信操作。
时间事件:
Redis服务器中的一些操作需要在给定的时间点执行,时间事件就是服务器对这类操作的抽象。

文件事件:
时间事件:

五.Client:

Redis服务器是典型的一对多服务器程序,每一个连接服务器的客户端,服务器都为这些客户端建立了相应的redis.h/redisClient结构(客户端状态),保存了客户端当前的状态信息以及执行相关功能是使用的数据结构。

1.客户端属性:

可以分为通用属性和特定功能的属性

typedef struct redisClient {
		//套接字描述符,-1伪客户端(AOF写入和执行Lua脚本中包含的redis命令),大于-1普通客户端
		int fd;
		//名字
		robj *name;
		/*标志,可以是一个或多个标志。记录客户端的角色(REDIS_MASTER,REDIS_SLAVE等),以及客户端的状态(REDIS_MONITOR,REDIS_BLOCKED等)
		具体的取值可以再查资料,在这写再多也没有用*/
		int flags;
		/*输入缓冲区:保存客户端发送的命令请求*/
		sds querybuf;
		//命令与命令参数:
		robj **argv;//数组,每一项都是一个字符串对象,[0]是要执行的命令,之后是传给命令的参数
		int atgc;//记录argv数组的长度
		//命令的实现函数,根据argv[0]查找命令表,将cmd属性指向redisCommand结构。
		struct redisCommand *cmd;
		//输出缓冲区,保存所有的命令回复。每个客户端状态都有两个,一个大小固定(保存小的回复,OK,整数值等),一个大小可变(由一个或多个字符串对象组成的链表)。
		char buf[REDIS_REPLY_CHUNK_BYTES];//值默认16*1024
		int bufpos;//记录buf数组目前已使用的字节数量
		//身份验证,0未通过,除了AUTH命令所有命令都会被拒绝。1通过。若关闭了验证则默认为0,同1一样。配置文件中requirepass的相关说明。
		int authenticated;
		//时间相关
		time_t ctime;//创建客户端的时间
		time_t lastinteraction;//C和S进行最后一次互动的时间
		time_t obuf_soft_limit_reached_time;//记录输出缓冲区第一次到达软性限制的时间
}

2.打开和关闭

普通的客户端:服务器调用连接事件处理器,为客户端创建相应的客户端状态。正常关闭或达到某种限制或遇到异常时关闭。
伪客户端:Lua:服务器初始化时创建,直到服务器关闭时销毁;AOF:服务器在载入AOF文件时创建,完成后销毁。

服务器使用两种模式来限制客户端输入缓冲区的大小,可在配置文件中使用client-output-buffer-limit选项设置。
硬性限制:如果输出缓冲区的大小超过了硬性限制所设置的大小,那么这个客户端就会被服务端关闭。
软性限制:如果输出缓冲区的大小超过了软性限制所设置的大小,但没有超过硬性限制的大小,那么obuf_soft_limit_reached_time就会记录客户端到达软性限制的时间。之后服务器会继续监视客户端,如果输出缓冲区的大小一直超过软性限制,并持续超过服务器设定的时长,那么就会关闭客户端;如果输出缓冲区在设定时间内不在超出软性限制,那么客户端就不会被关闭并且属性值清0。

六.服务器

1.命令请求的执行过程:

1.发送:

发送

2.读取

服务器调用命令请求处理器来:

  1. 读取套接字中协议格式的命令请求, 并将其保存到客户端状态的输入缓冲区里面。
  2. 对输入缓冲区中的命令请求进行分析, 提取出命令请求中包含的命令参数, 以及命令参数的个数, 然后分别将参数和参数个数保存到客户端状态的argv 属性和 argc 属性里面。
  3. 调用命令执行器, 执行客户端指定的命令。
3.命令执行器
  1. 查找命令实现:根据客户端状态的 argv[0] 参数, 在命令表(一个字典,key就是命令,value就是redisCommand结构,包含Redis 命令的实现信息)中查找参数所指定的命令, 并将找到的命令保存到客户端状态的 cmd 属性里面
  2. 执行预备操作:检查是否符合条件;回收内存和判断服务器正在执行的操作等
  3. 调用命令的实现函数:client->cmd->proc(client);被调用的命令实现函数会执行指定的操作, 并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里面(buf 属性和 reply 属性), 之后实现函数还会为客户端的套接字关联命令回复处理器
  4. 执行后续工作:一些收尾工作如慢查询,AOF写入和从服务器的复制等。
4.回复

当客户端套接字状态可写时,服务器执行命令回复处理器,将保存在客户端输出缓冲区中的命令发送给客户端,完毕后清空客户端状态的输出缓冲区。客户端接受到命令回复后打印。

2.serverCron函数

默认100ms执行一次,此函数负责管理服务器的资源,并保持服务器自身的良好运转。
1.更新服务器时间缓存
每次获取服务器的当前时间都要进行一次系统调用,为减少调用次数,服务器状态redisServer会缓存一个系统时间,serverCron()每次执行时刷新。时间缓存常用在对精确度要求不高的功能上,如打印日志,设置过期时间等。
2.更新LRU时钟
默认10s更新一次,可通过命令 INFO stats 查看
3.更新服务器每秒执行命令次数
可通过命令 INFO stats 查看
4.更新服务器内存峰值
5.处理SIGTERM峰值
在启动服务器时,redis会为服务器进程的SIGTERM信号关联处理器sigtermHandler(),功能是在服务器接收到SIGTERM时,打开服务器状态的shutdown_asap标识。每次serverCron函数运行时都会检查此属性值,判断是否关闭服务器。
6.管理客户端资源
serverCron函数调用clientsCron(),检查CS的连接是否超时(长时间没有互动);检查客户端的输入缓冲区的大小,当超过阈值时,释放后并创建一个默认大小的输入缓冲区,防止耗费过多的内存。
7.serverCron调用databasesCron(),检查过期键。
8.被延期执行的BGREWRITEAOF
BGSAVE在执行时,BGREWRITEAOF会被延迟,aof_rewrite_scheduled标识(1,表示有重写命令被延迟)。serverCron函数检查BGSAVE或者BGREWRITEAOF命令是否在执行,如果这两个命令都没在执行,并且上述标识为1,那么服务器就会执行之前被推迟的重写命令
9.检查持久化操作的运行状态

10.将AOF缓冲区中的内容写入到AOF文件中
每一个事件loop。
11.关闭异步客户端
软性限制
12.增加cronloops的值
服务器状态的此属性记录serverCron函数的执行次数。再复制模块中实现“每执行serverCron函数N次就执行一次指定代码”

3.初始化服务器

  1. 创建服务器状态实例initServerConfig(),初始化一般属性,为属性设置默认值。
  2. 载入配置选项。在启动服务器时,用户可以通过给定配置参数或者指定配置文件来修改服务器的默认配置,如:
    redis-server --port 2333
    redis-server redis.conf
  3. 初始化服务器的数据结构(客户端链表,数据库数组等),并进行一些设置(创建共享对象,为serverCron函数创建时间事件初始化服务器的I/O模块(BIO))。initServer()。初始化完成后,服务器用ASCII字符在日志中打印出Redis的版本信息号。
  4. 还原数据库状态,载入RDB或者AOF文件,来还原服务器的数据库状态。优先使用AOF。还原完成后,服务器将在日志中打印相关信息。
  5. 初始化完成,执行事件循环,可以接受客户端的连接请求。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值