一、前言
目前计算机世界中的数据库共有2种类型:关系型数据库、非关系型数据库。
常见的关系型数据库解决方案
MySQL、MariaDB(MySQL的代替品)、Percona Server(MySQL的代替品·)、Oracle、PostgreSQL、
Microsoft Access、Google Fusion Tables、SQLite、DB2、FileMaker、SQL Server、INFORMIX、Sybase、dBASE、Clipper、FoxPro、foshub。
几乎所有的数据库管理系统都配备了一个开放式数据库连接(ODBC)驱动程序,令各个数据库之间得以互相集成。
常见的非关系型数据库解决方案(NoSQL)
Not Only SQL
Redis、MongoDB、Memcache、HBase、BigTable、Cassandra、CouchDB、Neo4J。
目前小郭的工作经历里使用最多的就是Redis了。
关系型数据库和非关系型数据库的区别
区别就是一个叫关系型,一个叫非关系型~ 这么解释网友们会不会打残我? 哈哈哈 下面来个正经一点的解释。
关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织,有如下优缺点。
优点:
-
使用方便
SQL语言通用,可用于复杂查询;
-
易于维护
都是使用表结构,格式一致;
-
复杂操作
支持SQL,可用于一个表以及多个表之间非常复杂的查询。
-
支持事务控制
缺点:
-
高并发读写性能比较差
尤其是海量数据的高效读写场景性能比较差,因为硬盘I/O是一个无法避免的瓶颈。
-
灵活度低
表结构固定,DDL修改对业务影响大。
非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合,可以是文档或者键值对等。有如下优缺点。
优点:
-
读写速度快
IO快,可以使用内存、硬盘或者其它随机存储器作为数据载体,而关系型数据库只能使用硬盘
-
扩展性强
-
数据格式灵活
存储数据格式可以是kv、文档、图片等等,限制少,应用场景广,而关系型数据只能是内置的基础数据类型
-
成本低
部署简单,大部分开源免费,社区活跃。
缺点:
-
不支持join等复杂连接操作
-
事务处理能力弱
-
缺乏数据完整性约束
-
不提供SQL支持
暂时不提供SQL支持,会造成开发任意额外的学习成本
再来说说为什么需要非关系型数据库技术。
小郭结合自己的工作经验来总结一下(不喜轻点喷~),目前的软件产品从用户角度主要分为几个方向:
-
To C
这个方向的软件产品是目前市面上最多的,面向的主要是个人用户,遵循比较规范的产品流程。 通常这类软件的用户量基数巨大且增长快,比如美团的年度交易用户数已从2015年的2亿人,增长到2022年的6.87亿人(2022年Q3财报),平均每2个中国人就有一个在美团上花过钱。用户量大,对性能的要求也会比较高,有可能一瞬间成千上万的请求到来(抢购、促销活动场景不可控),需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是关系型数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题, 因此需要引入缓存解决方案,减少对关系型数据库的影响。 Redis就是一个不二选择。
-
To G
随着近年来不断倡导互联网+,政府也纷纷进行转型,寻求更好的商业模式。To G是从 To B衍生出来的一种特殊划分,面向的企业为政府或相关事业单位,主要是根据每年政府投入的财政预算,然后去做的一系列信息化项目,可以说是“指标驱动,为做项目而做项目”。这类项目有两个极端场景,一类是用户量极低,提供给内部使用的,本地化部署; 还有一类是面向老百姓的,用户量也大,但是对并发要求并不高,比如政务类软件。
举个栗子:智能考车系统,是典型的公安部主导的一款To G产品,使用对象是考官和公安部相关管理等内部人员,用户量不多;而“交管12123”是直接提供给老百姓使用的APP,可以自行预约考试,处理违章等,面向的是全国十几亿人,用户基数大,海量数据,完全依赖传统的关系型数据库肯定会降低应用访问性能,因此也需要引入新的非关系型数据库解决方案来开发程序功能。
-
To B
这个方向的产品一般是面向商业企业用户的,一般不向大众用户公开。用户量相对较少,通常情况对性能的要求比To C类产品要低一些,一般大部分场景都是直接使用关系型数据库进行数据存储,少部分场景也会额外依赖非关系型数据库。
一句话总结:To C使用场地是随时对地;ToB更多是内网;To G是内外网相结合(互联网+政务)
软件研发产品大体是包括这三大类(当然还有To VC , To P 的一些分法,也没错,只是立足点不同)
目前业界的技术选型原则基本是:核心数据存储选择关系型数据库,次要数据存储选择非关系型数据库。
本文接下来主要总结非关系型数据库中的Redis技术的相关知识。
二、Redis简介
redis的官网地址,是redis.io。(域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地),Vmware在资助着redis项目的开发和维护。
从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。
1、是什么
据说有一名意大利程序员,在 2004 年到 2006 年间主要做嵌入式工作,之后接触了 Web,2007 年和朋友共同创建了一个网站,并为了解决这个网站的负载问题(为了避免 MySQL 的低性能),于是亲自定做一个数据库,并于 2009 年开发完成,这个就是 Redis。这个意大利程序员就是 Salvatore Sanfilippo 江湖人称 Redis 之父,大家更习惯称呼他 Antirez。
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis属于非关系型数据库中的一种解决方案,目前也是业界主流的缓存解决方案组件。
数百万开发人员在使用Redis用作数据库、缓存、流式处理引擎和消息代理。
2、为什么用它
Redis是当前互联网世界最为流行的 NoSQL(Not Only SQL)数据库,它的性能十分优越,其性能远超关系型数据库,可以支持每秒十几万此的读/写操作,并且还支持集群、分布式、主从同步等配置,理论上可以无限扩展节点,它还支持一定的事务能力,这也保证了高并发的场景下数据的安全和一致性。
最重要的一点是:Redis的社区活跃,这个很重要。
Redis 已经成为 IT 互联网大型系统的标配,熟练掌握 Redis 成为开发、运维人员的必备技能。
3、优点
-
性能极高
官方的 Benchmark 数据:测试完成了 50 个并发执行 10W 个请求。设置和获取的值是一个 256 字节字符串。
测试结果:Redis读的速度是110000次/s,写的速度是81000次/s 。
-
数据类型丰富
支持 Strings, Lists, Hashes, Sets 及 Ordered Sets 数 据类型操作。
-
原子操作
所有的操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
-
功能丰富
提供了缓存淘汰策略、发布定义、lua脚本、简单事务控制、管道技术等功能特性支持
三、如何安装
1、版本管理
Redis 使用标准的做法进行版本管理: 主版本号.副版本号.补丁号。 偶数 副版本号 表示一个 稳定的 发布,像 1.2, 2.0, 2.2, 2.4, 2.6, 2.8。奇数副版本号表示 不稳定的 发布,例如 2.9.x 发布是一个不稳定版本,下一个稳定版本将会是Redis 3.0。
2、windows安装
Redis在Windows上不受官方支持。但是,我们还是可以按照下面的说明在Windows上安装Redis进行开发。
官方window安装教程这个照着安装是行不通的!!!!
但是有开源爱好者提供了window的免安装方式,操作非常简单,下面进行步骤说明。
1)获取安装包
直接下载开源的redis-window版本安装包:Releases · tporadowski/redis (github.com)
下载好后解压到本地磁盘某个目录,目录结构如下:
redis-server.exe和redis.windows.conf就是我们接下来要用到的重要文件了。
2)启动服务端
打开一个 cmd 窗口 使用 cd 命令切换目录到 D:Redis-x64-5.0.14.1 运行如下命令:
redis-server.exe redis.windows.conf
如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了,后面的那个 redis.windows.conf 可以省略,如果省略,会启用默认的。输入之后,会显示如下界面:
接下来保持这个cmd界面不要关闭,我们启动一个客户端去连接服务端。
3)客户端连接
服务端程序启动好之后,我们就可以用客户端去连接使用了,目前已经有很多开源的图形化客户端比如Redis-Desktop-Manager,redis本身也提供了命令行客户端连接工具,接下来我们直接用命令行工具去连接测试。
另启一个 cmd 窗口,原来的不要关闭,不然就无法访问服务端了。切换到目录D:Redis-x64-5.0.14.1,执行如下命令:
redis-cli.exe -h 127.0.0.1 -p 6379
设置一个key名为"hello",value是"world"的键值对:
上面的日志显示成功连接到了redis服务,并且设置了一个key名为"hello",value是"world"的键值对数据。
3、linux安装
linux安装redis的方式也有多种,小郭这里仅演示源码安装的方式。
1) 获取源码
使用wget命令从官网下载最新的源码包
root@xxx guoyd]# wget https://download.redis.io/redis-stable.tar.gz
2)编译
- 解压源码包并进入根目录:
root@xxx guoyd]# tar -xvf redis-stable.tar.gz
root@xxx guoyd]# cd redis-stable
root@xxx guoyd]# ll
[root@xxx redis-stable]# ll
total 244
-rw-rw-r-- 1 guoyd guoyd 18320 Sep 7 01:56 00-RELEASENOTES
-rw-rw-r-- 1 guoyd guoyd 51 Sep 7 01:56 BUGS
-rw-rw-r-- 1 guoyd guoyd 5027 Sep 7 01:56 CODE_OF_CONDUCT.md
-rw-rw-r-- 1 guoyd guoyd 2634 Sep 7 01:56 CONTRIBUTING.md
-rw-rw-r-- 1 guoyd guoyd 1487 Sep 7 01:56 COPYING
drwxrwxr-x 8 guoyd guoyd 4096 Sep 7 01:56 deps
-rw-rw-r-- 1 guoyd guoyd 11 Sep 7 01:56 INSTALL
-rw-rw-r-- 1 guoyd guoyd 151 Sep 7 01:56 Makefile
-rw-rw-r-- 1 guoyd guoyd 6888 Sep 7 01:56 MANIFESTO
-rw-rw-r-- 1 guoyd guoyd 22607 Sep 7 01:56 README.md
-rw-rw-r-- 1 guoyd guoyd 107512 Sep 7 01:56 redis.conf
-rwxrwxr-x 1 guoyd guoyd 279 Sep 7 01:56 runtest
-rwxrwxr-x 1 guoyd guoyd 283 Sep 7 01:56 runtest-cluster
-rwxrwxr-x 1 guoyd guoyd 1772 Sep 7 01:56 runtest-moduleapi
-rwxrwxr-x 1 guoyd guoyd 285 Sep 7 01:56 runtest-sentinel
-rw-rw-r-- 1 guoyd guoyd 1695 Sep 7 01:56 SECURITY.md
-rw-rw-r-- 1 guoyd guoyd 14700 Sep 7 01:56 sentinel.conf
drwxrwxr-x 4 guoyd guoyd 4096 Sep 7 01:56 src
drwxrwxr-x 11 guoyd guoyd 4096 Sep 7 01:56 tests
-rw-rw-r-- 1 guoyd guoyd 3628 Sep 7 01:56 TLS.md
drwxrwxr-x 9 guoyd guoyd 4096 Sep 7 01:56 utils
-
执行make命令进行源码编译
root@xxx redis-stable]# make
编译时间大概需要几分钟,如果编译成功,将看到如下输出日志:
.....日志太多,略...... Hint: It's a good idea to run 'make test' ;) make[1]: Leaving directory `/home/guoyd/redis-stable/src'
同时,在src目录中会生成几个 新的Redis 二进制文件:
redis-server: 代表redis服务本身的可执行程序 redis-cli:redis提供的命令行工具,用于和redis服务端进行交互
3)安装
编译成功后,我们继续在源码根目录下使用make install
将redis服务安装到默认目录usr/local/bin中:
[root@iZbp128dczen7roibd3xciZ redis-stable]# make install
cd src && make install
which: no python3 in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/java/jdk1.8.0_201/bin:/root/bin)
make[1]: Entering directory `/home/guoyd/redis-stable/src'
CC Makefile.dep
make[1]: Leaving directory `/home/guoyd/redis-stable/src'
which: no python3 in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/java/jdk1.8.0_201/bin:/root/bin)
make[1]: Entering directory `/home/guoyd/redis-stable/src'
Hint: It's a good idea to run 'make test' ;)
INSTALL redis-server
INSTALL redis-benchmark
INSTALL redis-cli
make[1]: Leaving directory `/home/guoyd/redis-stable/src'
[root@iZbp128dczen7roibd3xciZ redis-stable]#
4)启动服务端
redis安装好了,我们可以在任意目录下执行redis-server
命令启动服务端:
因为redis-server已经被配置到了环境变量中,所以可以在任意目录执行
[root@xxx ~]# redis-server
上面演示的是使用默认配置启动redis服务,如果我们想自定义配置,可以使用如下方式:
redis-server /xxx/xxx/redis.conf
redis.conf是redis的核心配置文件,我们可以按需进行配置修改。
在源码redis-stable的根目录中也提供了配置文件的模板redis.conf, 小郭会在本文后续章节中对这个配置文件做详细说明。
5)客户端连接
和window版本一样,有很多开源图形化客户端,我们这里还是使用redis自带的命令行工具去连接。
另启一个 linux终端,原来的不要关闭,不然就无法访问服务端了。重新打开一个linux终端,执行如下命令:
当然这里也有办法直接让redis在后台运行,重新打开一个终端的操作不是必要的。
[root@xxx ~]# redis-cli.exe -h 127.0.0.1 -p 6379
-bash: redis-cli.exe: command not found
[root@iZbp128dczen7roibd3xciZ ~]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379>
上面的操作成功连接到了redis服务,并且使用set命令设置了一个key名为"hello",value是"world"的键值对数据。
此时,我们的linux服务器上就同时存在一个redis服务端进程和一个redis客户端连接进程:
[root@xxx ~]# ps -ef|grep redis
root 21739 1 0 14:28 ? 00:00:00 redis-server *:6379 # redis服务端进程
root 22007 21982 0 14:33 pts/0 00:00:00 redis-cli -h 127.0.0.1 -p 6379 # redis客户端进程
root 22143 22121 0 14:35 pts/2 00:00:00 grep --color=auto redis
[root@xxx ~]#
redis支持许多客户端同时建立连接,接下来我们就可以在业务系统中同时开启多个客户端去访问redis了。
注意:本文接下来的笔记都基于Redis的版本7.2.1
四、基础知识-架构
Redis 支持单机、主从、哨兵、集群多种架构模式,本节来总结一下其中的区别。
Redis 集群架构 (史上最全) - 疯狂创客圈 - 博客园 (cnblogs.com)
最通俗易懂的 Redis 架构模式详解_redis_哈喽沃德先生_InfoQ写作社区
1、单机架构
单机模式是最原始的模式,非常简单,就是安装运行一个Redis实例,然后业务项目调用即可。一些非常简单的应用,并非必须保证高可用的情况下完全可以使用该模式。
优点
-
成本低,只需要一个实例
-
部署简单
-
数据天然一致性(不需要数据同步)
缺点
- 可用性保证差,单节点有宕机的风险。
- 高性能受限,单机高性能受限于 CPU 的处理能力。
单机 Redis 能够承载的 QPS(每秒查询速率)取决于业务操作的复杂性,Lua 脚本复杂性就极高。假如是简单的 key value 查询那性能就会很高,单机支持10W+的QPS。
但是在单机架构下,系统的最大瓶颈就出现在 Redis 单机问题上,此时我们可以通过将架构演化为主从架构解决该问题。
2、主从架构
我们可以部署多个 Redis 实例,单机架构模型就演变成了下面这样:
我们把同时接收读/写操作的节点称为Master(主节点), 接收读操作和数据同步的节点称为Slave(从节点)。
只要主从节点之间的网络连接正常,主节点就会将写入自己的数据同步更新给从节点,从而保证主从节点的数据一致性。
主从架构比较适合读高并发场景。
主从架构存在的问题是:当主节点宕机,需要在众多从节点中选一个作为新的主节点,同时需要修改客户端保存的主节点信息并重启客户端,还需要通知所有的从节点去复制新的主节点数据,从而保证服务的高可用性。整个切换过程需要人工干预,而这个过程很明显会造成服务的短暂不可用。
优点
-
方便水平扩展
当QPS 增加时,增加 从节点 即可
-
降低了主节点的读压力,分摊给了从节点
-
主节点宕机时,从节点可以顶上
缺点
-
主从切换过程需要人工干预
-
主节点写压力大
-
可靠性不高
一旦主节点挂掉,在人工做主从切换过程中,对外失去了写的能力
-
主节点的写能力受到单机的限制;
-
主节点的存储能力受到单机的限制。
-
数据大量冗余
每个从节点都有一份完整数据
3、哨兵架构
哨兵架构主要解决了主从架构中存在的高可用性问题,在主从架构的基础上,哨兵架构实现了自动化故障检测和恢复机制,全过程无需人工干预。
Redis 2.8 版本开始,引入哨兵(Sentinel)这个概念
如上图所示,哨兵架构由两部分集群组成,哨兵节点集群和数据节点集群:
-
哨兵节点集群
该集群中的节点不存储数据,是特殊的redis节点,主要完成监控、提醒、自动故障转移这三大功能。
1)监控(Monitoring):哨兵节点会不断地发送ping消息检测数据节点是否正常;
2)提醒(Notification):当监控到某个数据节点有问题时, 哨兵可以通过 API 向管理员或者其他应用程序发送通知
3)自动故障迁移(Automatic failover):当一个主数据节点不能正常工作时, 哨兵会开始一次自动故障迁移操作,将该主节点下线,选举一个从数据节点升级为主节点(这里也就是将主从架构中的人工干预过程自动化)
-
数据节点集群
该集群中的节点分为主从模式,都存储业务数据,这块其实就是之前的主从架构模式部分
哨兵模式工作原理
PING
命令;own-after-milliseconds
选项所指定的值,则这个实例会被 该哨兵节点标记为主观下线;INFO
命令;当 主节点被 哨兵标记为客观下线时,哨兵向已下线的 主节点的所有 从节点发送 INFO 命令的频率会从 10 秒一次改为每秒一次;优点
- 主从实现了发生故障时自动切换,无需人工干预,大大增强系统可用性
- 哨兵实时监控数据节点状态,发现问题可以通过API立即通知到管理员或者其它应用程序。
- 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都有
缺点
- 部署成本高,需要单独维护一套哨兵集群
- 依旧没有解决主数据节点的写压力,主节点写的能力和存储能力依旧受限单机限制
- 动态扩容变得更加复杂
4、集群架构
集群架构可以说是Redis的王炸方案了!一经推出,便深得广大开发者喜爱。
Redis 3.0 版本正式推出 Redis Cluster 集群模式,有效地解决了 Redis 分布式方面的需求。Redis Cluster 集群模式具有高可用、可扩展性、分布式、容错等特性。
如上图所示,该集群架构中包含 6 个 Redis 节点,3 主 3 从,分别为 M1,M2,M3,S1,S2,S3。除了主从 Redis 节点之间进行数据复制外,所有 Redis 节点之间采用 Gossip 协议进行通信,交换维护节点元数据信息。
客户端读请求分配给 Slave 节点,写请求分配给 Master,数据同步从 Master 到 Slave 节点。读写能力都可以快速进行横向扩展!!!
Redis的集群模式采用的是无中心结构,每个节点都可以保存部分数据和整个集群状态,每个节点都和其他所有节点连接。集群一般由多个节点组成,节点数量至少为 6 个才能保证组成完整高可用的集群,其中三个为主节点,三个为从节点。三个主节点会分配槽,处理客户端的命令请求,而从节点可用在主节点故障后,顶替主节点。
分片
单机、主从、哨兵的架构中每个数据节点都存储了全量的数据,从节点进行数据的复制。然而单个节点存储能力受限于机器资源,是存在上限的,集群模式就是把数据进行分片存储,当一个分片数据达到上限的时候,还可以分成多个分片。
Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,计算公式:HASH_SLOT = CRC16(key) % 16384
。每一个节点负责维护一部分槽以及槽所映射的键值数据。
槽是 Redis Cluster 管理数据的基本单位,集群扩缩容其实就是槽和数据在节点之间的移动。
假如,这里有 3 个节点的集群环境如下:
- 节点 A 哈希槽范围为 0 ~ 5500;
- 节点 B 哈希槽范围为 5501 ~ 11000;
- 节点 C 哈希槽范围为 11001 ~ 16383。
此时,我们如果要存储数据,按照 Redis Cluster 哈希槽的算法,假设结果是: CRC16(key) % 16384 = 3200。 那么就会把这个 key 的存储分配到 A节点。此时连接 A、B、C 任何一个节点获取 key,都会这样计算,最终是通过 B 节点获取数据。
假如这时我们新增一个节点 D,Redis Cluster 会从各个节点中拿取一部分 Slot 到 D 上,比如会变成这样:
- 节点 A 哈希槽范围为 1266 ~ 5500;
- 节点 B 哈希槽范围为 6827 ~ 11000;
- 节点 C 哈希槽范围为 12288 ~ 16383;
- 节点 D 哈希槽范围为 0 ~ 1265,5501 ~ 6826,11001 ~ 12287
这种特性允许在集群中轻松地添加和删除节点。同样的如果我想删除节点 D,只需要将节点 D 的哈希槽移动到其他节点,当节点是空时,便可完全将它从集群中移除。
redis分布式集群3种架构方案 - 菜菜聊架构 - 博客园 (cnblogs.com)
主从切换
Redis Cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点复制主节点数据备份,当这个主节点挂掉后,就会通过这个主节点的从节点选取一个来充当主节点,从而保证集群的高可用。
回到前面分片的例子中,集群有 A、B、C 三个主节点,如果这 3 个节点都没有对应的从节点,如果 B 挂掉了,则集群将无法继续,因为我们不再有办法为 5501 ~ 11000 范围内的哈希槽提供服务。
所以我们在创建集群的时候,一定要为每个主节点都添加对应的从节点。比如,集群包含主节点 A、B、C,以及从节点 A1、B1、C1,那么即使 B 挂掉系统也可以继续正确工作。
因为 B1 节点属于 B 节点的子节点,所以 Redis 集群将会选择 B1 节点作为新的主节点,集群将会继续正确地提供服务。当 B 重新开启后,它就会变成 B1 的从节点。但是请注意,如果节点 B 和 B1 同时挂掉,Redis Cluster 就无法继续正确地提供服务了。
以上几种架构模式,每种都有各自的优缺点,在实际场景中要根据业务特点去选择合适的模式使用。
五、基础知识-模块组成
1、网络访问框架
通过网络框架以 Socket 通信的形式对外提供键值对操作,包括socket服务,和协议解析,客户端发送命令时,命令会被封装到网络请求中传输给redis。
2、操作模块
主要对各种数据进行操作,如get 、put 、delete 、scan操作等。
3、索引模块
索引模块主要目的是为了通过key值快速定位value值,从而进行操作。 redis使用的索引模块为哈希表。redis存储内存的高性能随机访问特性可以很好地与哈希表 O(1) 的操作复杂度相匹配。
4、存储模块
主要完成保存数据的工作,存储数据模型为 key-value形式,value支持丰富的数据类型。包括字符串,列表 ,哈希表,集合等。不同的数据类型能支持不同的业务需求。
其中的持久化模块主要对数据进行持久化,当系统重启时,能够快速恢复服务。redis的持久化策略分为:日志(AOF)和快照(RDB)两种方式。
5、高可用模块
主从复制:主从架构中用到(一个Master至少一个slave),master -> slave 数据复制
哨兵:主从架构实现高可用(一个Master至少一个slave),在master故障的时候,快速将slave切换成master,实现快速的灾难恢复,实现高可用性;
6、高扩展模块
切片集群,也叫分片集群(集群分片),就是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。
Redis Cluster是Redis提供的分布式高可扩展解决方案。
7、其它模块
还有一些其他模块,例如:数据压缩、过期机制、数据淘汰策略、主从复制、集群化、高可用等功能,另外还可以增加统计模块、通知模块、调试模块、元数据查询等辅助功能。
六、基础知识-请求执行流程
1、概述
一个redis命令请求从用户端发起到获得结果的过程中,redis客户端和服务器端都需要完成一系列操作。
比如发送一个set命令:
set key value
这个过程中经历了如下的几个处理阶段:
1)客户端发送命令请求
2)服务器端读取命令请求
3)服务器端执行命令
4)服务器端将命令回复发送回客户端
5)客户端接收并打印命令回复
整体流程图如下:
下面小郭将针对图中的每个处理阶段展开说一说细节。
2、客户端发送命令请求
当用户在客户端中键入一个命令请求时, 客户端会将这个命令请求转换成约定的协议格式, 然后通过连接到服务器的套接字, 将协议格式的命令请求发送给服务器。
3、服务器端读取命令请求
当客户端与服务器之间的连接套接字因为客户端的写入而变得可读时, 服务器将调用命令请求处理器来执行以下操作:
1)读取套接字中协议格式的命令请求,并将其保存到客户端状态的输入缓冲区里面。
2)对输入缓冲区中的命令请求进行分析,提取出命令请求中包含的命令参数,以及命令参数的个数,然后分别将参数和参数个数保存到客户端状态的argv属性和argc属性里面。
3)调用命令执行处理器,执行客户端指定的命令。
4、服务器端执行命令
1)命令执行器:查找命令实现
命令执行器根据客户端状态的argv[0]参数,在命令表(command table)中查找参数所指定的命令,并将找到的命令保存到客户端状态的cmd属性里面。
命令表是一个字典,字典的键是一个个命令名字,比如"set"、"get"、"del"等等。而字典的值则是一个个redisCommand结构,每个redisCommand结构记录了一个Redis命令的实现信息。
需要注意的是命令名字的大小写不影响命令表的查找结果:
因为命令表使用的是大小写无关的查找算法, 无论输入的命令名字是大写、小写或者混合大小写, 只要命令的名字是正确的, 就能找到相应的 redisCommand 结构。
比如说, 无论用户输入的命令名字是 "SET" 、 "set" 、 "SeT" 又或者 "sEt" , 命令表返回的都是同一个 redisCommand 结构。下面是redisCommand结构 的属性描述:
属性名 | 类型 | 作用 |
---|---|---|
name | char * | 保存命令的名字,比如“set” |
proc | redisCommandProc * | 函数指针,指向命令的具体实现函数,比如setCommand。redisCommandProc类型的定义为typedef void redisCommandProc(redisClient *c); |
arity | int | 命令参数的个数,用于检查命令请求的格式是否正确。如果这个值为-N,那么表示参数的数量大于等于N。注意命令的名字本身也是一个参数,比如说SET msg "hello world"命令的参数分别是"SET"、"msg"、"hello world"。而不仅仅是"msg"和"hello world" |
sflags | char * | 字符串形式的标识值,这个值记录了命令的属性,比如这个命令是写命令还是读命令,这个命令是否允许在载入数据时使用,这个命令是否允许在Lua脚本中使用等等含义。接下来的表格有详细说明 |
flags | int | 对sflags标识进行分析得出的二进制标识,由程序自动生成。服务器对命令标识进行检查时使用的都是flags属性而不是sflags属性,因为对二进制标识的检查可以方便地通过&、^、~等操作来完成 |
calls | long long | 服务器总共执行了多少次这个命令 |
milliseconds | long long | 服务器执行这个命令所耗费的总时长 |
sflags标识 | 含义 | 带有这个标识的命令 |
---|---|---|
w | 这是一个写入命令,可能会修改数据库 | SET、RPUSH、DEL等等 |
r | 这是一个只读命令,不会修改数据库 | GET、STRLEN、EXISTS等等 |
m | 这个命令可能会占用大量内存,执行之前需要先检查服务器的内存使用情况,如果内存紧缺的话就可以禁止执行这个命令 | SET、APPEND、RPUSH、LPUSH、SADD等等 |
a | 这是一个管理命令 | SAVE、BGSAVE、SHUTDOWN等等 |
P | 这是一个发布与订阅功能方面的命令 | PUBLISH、SUBSCRIBE、PUBSUB等等 |
s | 这个命令不可以在Lua脚本中使用 | BRPOP、BLPOP、BRPOPLPUSH、SPOP等等 |
R | 这是一个随机命令,对于相同的数据集和相同的参数,命令返回的结果可能不同 | SPOP、SRANDMEMBER、SSCAN、RANDOMKEY等等 |
S | 当在Lua脚本中执行这个命令时,对这个命令的输出结果进行一次排序,使得命令的结果有序 | SINTER、SUNION、SDIFF、SMEMBERS、KEYS等等 |
l | 这个命令可以在服务器载入数据的过程中使用 | INFO、SHUTDOWN、PUBLISH等等 |
t | 这是一个允许从服务器在带有过期数据时使用的命令 | SLAVEOF、PING、INFO等等 |
M | 这个命令在监视器(monitor)模式下不会自动被传播(propagate) | EXEC |
举个栗子:
GET命令的名字为"get",实现函数为getCommand函数;命令的参数个数为2,表示命令只接受两个参数;命令的标识为"r",表示这是一个只读命令。
SET命令的名字为"set",实现函数为setCommand;命令的参数个数为-3,表示命令接受三个或以上数量的参数;命令的标识为"wm",表示SET命令是一个写入命令,并且在执行这个命令之前,服务器应该对占用内存状况进行检查,因为这个命令可能会占用大量内存。
2)命令执行器:执行校验
经过之前的阶段, 服务器端已经将执行请求命令所需的参数(保存在客户端状态的 argv 属性)、参数个数(保存在客户端状态的 argc 属性)、命令实现函数(保存在客户端状态的 cmd 属性)都解析齐了, 但是在真正执行命令之前, 还需要进行一些预备操作, 从而确保命令可以正确、顺利地被执行, 这些主要操作包括:
-
命令校验
检查客户端状态的cmd指针是否指向NULL。如果是的话, 那么说明用户输入的命令名字找不到相应的命令实现, 服务器不再执行后续步骤, 并向客户端返回一个错误。
-
参数校验
根据客户端cmd属性指向的redisCommand结构的arity属性,检查命令请求所给定的参数个数是否正确。当参数个数不正确时, 不再执行后续步骤, 直接向客户端返回一个错误。 比如说, 如果
redisCommand
结构的arity
属性的值为-3
, 那么用户输入的命令参数个数必须大于等于3
个才行。 -
权限校验
检查客户端是否已经通过了身份验证,未通过身份验证的客户端只能执行AUTH命令。如果未通过身份验证的客户端试图执行除 AUTH 命令之外的其他命令, 那么服务器将向客户端返回一个错误。
-
内存检测
如果服务器打开了maxmemory功能,那么在执行命令之前,先检查服务器的内存占用情况,并在有需要时进行内存回收,从而使得接下来的命令可以顺利执行。 如果内存回收失败, 那么不再执行后续步骤, 向客户端返回一个错误。
-
其它校验
Redis设计者是非常严谨的,每个环节都考虑的非常齐全,执行命令前还校验了许多方面,例如:
- 如果服务器上一次执行 BGSAVE 命令时出错, 并且服务器打开了 stop-writes-on-bgsave-error 功能, 而且服务器即将要执行的命令是一个写命令, 那么服务器将拒绝执行这个命令, 并向客户端返回一个错误。
- 如果客户端当前正在用 SUBSCRIBE 命令订阅频道, 或者正在用 PSUBSCRIBE 命令订阅模式, 那么服务器只会执行客户端发来的 SUBSCRIBE 、 PSUBSCRIBE 、 UNSUBSCRIBE 、 PUNSUBSCRIBE 四个命令, 其他别的命令都会被服务器拒绝。
- 如果服务器正在进行数据载入, 那么客户端发送的命令必须带有 l 标识(比如 INFO 、 SHUTDOWN 、 PUBLISH ,等等)才会被服务器执行, 其他别的命令都会被服务器拒绝。
- 如果服务器因为执行 Lua 脚本而超时并进入阻塞状态, 那么服务器只会执行客户端发来的 SHUTDOWN nosave 命令和 SCRIPT KILL 命令, 其他别的命令都会被服务器拒绝。
- 如果客户端正在执行事务, 那么服务器只会执行客户端发来的 EXEC 、 DISCARD 、 MULTI 、 WATCH 四个命令, 其他命令都会被放进事务队列中。
- 如果服务器打开了监视器功能, 那么服务器会将要执行的命令和参数等信息发送给监视器。
只有当通过了上面的所有校验,命令才会真正的开始执行。
3)命令执行器:调用命令实现函数
经过前面的步骤,服务器将要执行命令的实现保存到了客户端状态的cmd属性里面,并将命令的参数和参数个数分别保存到了客户端状态的argv属性和argv属性里面,当服务器执行命令时,只需要一个指向客户端状态的指针作为参数,调用实际执行函数。如下图所示:
命令实现函数内部会执行指定的操作,并产生相应的命令回复,这些回复会被保存在客户端状态的输出缓冲区里面(buf属性和reply属性),之后实现函数还会为客户端的套接字关联命令回复处理器,这个处理器负责将命令回复返回给客户端。
4)命令执行器:执行收尾工作
如在Redis.config里面有相关配置,则后续操作包含:慢日志记录、redisCommand结构属性更新、AOF持久化记录、主从复制命令传播等。
- 慢日志记录:如果服务器开启了慢查询日志功能, 那么慢查询日志模块会检查是否需要为刚刚执行完的命令请求添加一条新的慢查询日志。
- redisCommand结构属性更新:根据刚刚执行命令所耗费的时长, 更新被执行命令的 redisCommand 结构的 milliseconds 属性, 并将命令的 redisCommand 结构的 calls 计数器的值增一。
- AOF持久化记录:如果服务器开启了 AOF 持久化功能, 那么 AOF 持久化模块会将刚刚执行的命令请求写入到 AOF 缓冲区里面。
- 主从复制命令传播:如果有其他从服务器正在复制当前这个服务器, 那么服务器会将刚刚执行的命令传播给所有从服务器。
当以上操作都执行完了之后, 服务器对于当前命令的执行到此就告一段落了, 之后服务器就可以继续从文件事件处理器中取出并处理下一个命令请求了。
5、服务器端将命令回复发送回客户端
命令实现函数执行完成后会将命令回复保存到客户端的输出缓冲区里面, 并为客户端的套接字关联命令回复处理器, 当客户端套接字变为可写状态时, 服务器就会执行命令回复处理器, 将保存在客户端输出缓冲区中的命令回复发送给客户端。
当命令回复发送完毕之后, 回复处理器会清空客户端状态的输出缓冲区, 为处理下一个命令请求做好准备。
6、客户端接收并打印命令回复
当客户端接收到协议格式的命令回复之后, 它会将这些回复转换成人类可读的格式, 并打印给用户观看(Redis 自带的 客户端和开源的客户端都需要遵循redis的协议格式进行转换)
以上就是 Redis 客户端和服务器执行命令请求的整个过程了。