Redis 属于非关系型数据库,也被称之为 NoSQL 数据库,主要以 Key-Value 形式存储数据,是目前用得较多的一种非关系型数据库。
Redis 主要有如下特点:
- 为纯内存数据库,操作速度非常快;
- 可以通过持久化(如 RDB、AOF)保证数据基本不丢失;
- 数据类型丰富,常用数据类型有字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Sorted set)等;
- 功能丰富,支持发布订阅、Lua 脚本、事务、Pipeline(管道,类似于关系型数据库的批量操作);
- 为单线程;
- 实现高可用比较灵活(主从复制、哨兵模式、集群);
- 易实现可扩展。
Redis 所包涵的内容非常多,无法在本课中一一讲解,为了让大家更高效地了解 Redis,我将从以下几个方面来介绍:
- Redis 快的主要原因;
- Redis 主从复制过程;
- Redis 数据分片原理及集群搭建;
- Redis 数据迁移;
- Redis、Lua 整合;
Redis 快的主要原因
从 Redis 官网,我们可以清楚看到,Redis 单机读速度可达每秒 11万笔每秒,而写速度可达每秒8.1万。Redis 有如此快的读写速度,究其原因主要有以下三方面。
首先,Redis 为纯内存数据库,其操作均基于内存数据,这是 Redis 读写速度快的第一个原因。
同时,Redis 自身的数据结构也比较简单,主要使用 Hash 结构。同时优化了一些特殊数据的存储(如压缩表),并对短数据进行了压缩存储等,这是它读写速度快的第二个原因。
此外,Redis 使用了 I/O 多路复用模型。该模型可以让单个线程高效地处理多个连接请求,尽量减少网络 I/O 的时间消耗,这是它读写速度快的第三个原因。
上面提到 I/O 多路复用模型,有些朋友可能对它还不太了解。“多路”指的是多个网络连接,而“复用”指的是复用同一个线程。该模型利用 select、poll、epoll 可以同时监察多个流的 I/O 事件。事件处理器空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,程序会轮询一遍所有的流(epoll 只轮询那些真正发出了事件的流),并依次处理已就绪的流,这种做法避免了大量无用操作。
I/O 多路复用模型如下图:
Redis 主从复制过程
生产环境中使用 Redis,保证其高可用至关重要。不管利用 Redis 的主从、哨兵、还是集群模式实现高可用,了解其主从复制实现原理是很有必要的。
Redis 2.8 之后版本对主从复制的实现较之前版本已有较大区别。接下来,我们就来了解下它们之前的区别。为了方便叙述,下面将2.8之前的版本称为“旧版本”,2.8(包括2.8)之后的版本称为“新版本”。
旧版本复制功能实现
Redis 的复制功能分为同步(Sync)和命令传播(Command Propagate)两个操作。
同步操作
同步操作的主要作用是保证主、从数据库状态一致,即当主服务器数据库中数据更新后,从服务器数据需在很短时间内也做到同步更新。
同步操作时,客户端首先手动向从服务器发送 SLAVEOF 命令(为当前从服务器指定 Master 节点),从服务器接收到命令后,则向主服务器发送 SYNC 命令,从而实现主、从服务器数据状态的一致。
下面是 SYNC 命令执行的主要步骤:
- 从服务器向主服务器发送 SYNC 命令;
- 主服务器接收到 SYNC 命令请求后,执行 BGSAVE 命令生成 RDB 文件,同时开启一个缓冲区对从现在开始执行的所有写操作进行备份;
- 主服务器执行 BGSAVE 命令后,立即将生成的 RDB 文件发送给从服务器,从服务器接收并载入此 RDB 文件,将自己的数据库状态同步为主服务器执行 BGSAVE 命令时的数据库状态;
- 最后,主服务器将备份在缓冲区内的所有写操作命令发送给从服务器,从服务器执行写操作命令保证主、从服务器数据库中数据一致。
执行 SYNC 命令过程如下图所示:
命令传播操作
同步操作执行完毕后,主、从服务器上数据库中的数据将达到一致状态,但该状态只是暂时的,当主服务器执行写操作命令时,主服务器的数据库数据被修改,从而导致主、从数据库数据不一致。为了让主、从服务器上数据库的数据再次达到一致性,此时必须执行“命令传播操作”。
为了便于大家理解这一过程,请看下面图示说明。
(1)状态一致的主、从服务器:
(2)主服务器执行 set key5 value
命令后,主、从服务器的状态就不一致了:
(3)为了使主、从服务器数据库状态再次达到一致状态,主服务器需要对从服务器执行命令传播操作,即主服务器将自己执行的写命令发送给从服务器执行,当从服务器执行相同写命令之后,主、从服务器数据库再次达到一致状态:
旧版复制功能缺陷
旧版复制功能实现可以分为初次复制和断线后重复制。
初次复制,即从服务器数据库当前没有数据,或者从服务器当前复制的主服务器和上一次复制的主服务器不同。
断线后重复制,表示处于命令传播阶段的主、从服务器因网络原因而中断了复制操作,从服务器通过自动重连与主服务器再次建立连接,并继续复制主服务器写操作,从而使主、从服务器数据库数据最终达到一致性状态。
对于初次复制而言,旧版复制功能能够很好的完成复制任务,但对于“断线后重复制”来说,该功能效率是非常低下的,主要因为旧版的“断线后重复制”每次都需要通过从服务器向主服务器发送 SYNC 命令而实现对主服务器数据进行全量复制,而全量复制是非常耗时的一个操作。
新版本复制功能实现
为了改善旧版断线后重复制的功能,新版本将 SYNC 命令升级为 PSYNC 命令,以实现复制时的同步操作。PSYNC 命令具有完整重同步(Full Resynchronization)和部分重同步(Partial Resynchronization)两种模式。
完整重同步功能与旧版的初次复制功能一样,都是通过让主服务器执行 BGSAVE 命令创建并发送 RDB 文件,同时向从服务器发送备份在缓冲区内的写操作命令来同步数据,最终使得主、从服务器数据库数据状态一致。
部分重同步则用于处理断线后重复复制的问题,当从服务器断线后重新与主服务器建立连接后,主服务器将主、从服务器断线期间执行的写操作命令发送给从服务器,从服务器接收并执行这些写操作命令就可以使主、从服务器数据库状态达到一致。因为部分重同步属于增量同步,因此和旧版断线后重复制相比效率有较大的改善。
主、从服务器执行部分重同步的通信过程如下:
受篇幅所限,部分重同步及 PSYNC 命令的底层源码实现就不在此处分享了,有兴趣的读者可以自行研究 Redis 源码。
Redis 数据分片原理及集群环境搭建
实际生产环境中,利用 Redis 做缓存、排行榜应用、消息中间件、计数器及分布式锁等功能时,我们通常通过搭建 Redis 集群方式,来保证 Redis 的高可用。
为了帮助大家在生产环境中更好使用 Redis,下面将分别介绍 Redis 集群分片概念及如何搭建 Redis 集群。
Redis 数据分片
Redis 集群的整个数据库被分为16384个槽(Slot),数据库中的每个键都属于16384个槽中的一个,集群中的每个 Master 节点可以处理0个或者16384个槽。将集群整个数据库的16384个槽按照某种规则分配给集群中所有 Master 处理的过程称为 Redis 数据分片。
Redis 数据分片完成,意味着为集群中各个 Master 节点分别指定了要具体处理的槽。此时如果客户端向 Master 节点发送数据库键相关命令时,接收命令的节点利用算法 CRC16(Key) & 16383
(槽号是从0至16383)计算出命令要处理的数据库键属于哪个槽,并检查该槽是否指派给了自己,如果不是,便指引客户端将命令发送到正确的节点。整个命令执行过程如下图所示:
Redis 集群环境搭建
实际工作中,Redis 3.0以后版本通常会选用搭建集群的方式来实现 Redis 的高可用。
安装 Redis
搭建集群前,我们首先需要在 Linux 服务器上安装 Redis,主要安装步骤如下。
Redis 使用 C 语言编写,第一步首先需要在 CentOS 7上执行命令安装 gcc:
yum install gcc
第二步,下载 Redis 3.0 安装包 redis-3.0.0-rc2.tar.gz
,上传到 Linux 服务器的 /usr/local/software/
目录下,并执行命令解压 Redis 安装包:
tar -zxvf redis-3.0.0-rc2.tar.gz
第三步,进入 redis-3.0.0-rc2
目录下执行 make 命令编译 Redis 安装文件。
第四步,进入 src 目录执行命令安装 Redis,并检查 Redis 是否安装成功(src目录下有 redis-server
、redis-cli
):
make install
第五步,执行下面两个命令,新建两个文件用于存放 Redis 命令、配置文件:
mkdir -p /usr/local/redis/etc
mkdir -p /usr/local/redis/bin
第六步,执行如下命令将 redis-3.0.0-rc2
的 redis.conf 配置文件移动到 /usr/local/redis/etc
目录下:
cp redis.conf /usr/local/redis/etc/
第七步,执行如下命令将 redis-3.0.0-rc2/src
下的 mkreleasehdr.sh
、redis-benchmark
、redis-check-aof
、redis-check-dump
、redis-cli
、redis-server
文件移动到 bin 目录下:
mv mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server /usr/local/redis/bin
第八步,修改 Redis 配置文件 /usr/local/redis/etc/redis.conf
,将 daemonize 改为 yes。
第九步,进入目录 /usr/local/redis/bin
,并执行命令启动 Redis:
./redis-server /usr/local/redis/etc/redis.conf
第十步,执行命令验证 Redis 是否安装成功:
ps -ef | grep redis
如果输出如上图所示,说明 Redis 安装成功。
搭建 Redis 集群
接下来我们再看如何搭建 Redis 集群。
第一步,执行命令建立一个文件夹:
mkdir -p /usr/local/redis-cluster
第二步,进入目录 redis-cluster
,执行命令分别建立 7001、7002、7003、7004、7005、7006 文件夹:
mkdir 7001 7002 7003 7004 7005 7006
注意:一个高可用、健壮的分布式 Redis Cluster 集群至少由3个 Master 组成,同时每个 Master 至少有一个 Slave,也就是说3个 Master、3个 Slave 是最少的要求,即需要6台物理机器或者虚拟机。本课为了节约时间,在一台机器上开了6个端口来搭建集群并演示其功能。
第三步,进入目录 /usr/local/software/redis-3.0.0-rc2
,分别复制 redis.conf
配置文件到 7001、7002、7003、7004、7005、7006 文件夹中:
cp redis.conf /usr/local/redis-cluster/7001
cp redis.conf /usr/local/redis-cluster/7002
cp redis.conf /usr/local/redis-cluster/7003
cp redis.conf /usr/local/redis-cluster/7004
cp redis.conf /usr/local/redis-cluster/7005
cp redis.conf /usr/local/redis-cluster/7006
第四步,分别修改 7001、7002……7006这6个文件夹中的 redis.conf 配置文件,主要修改内容如下:
1. 将 daemonize 修改为 yes。
2. 修改端口 port 700*
。
注意:700*
分别对应每台机器的 Redis 监听端口号。
3. bind 修改为当前服务器 IP:
注意:我服务器 IP 是192.168.1.121,读者应该修改为您自己的服务器 IP。
4. dir 修改为 /usr/local/redis-cluster/700*
。
注意:/usr/local/redis-cluster/700*
表示 Redis 所在服务器存储数据的位置。
5. cluster-enabled
设置为 yes。
6. 设置 cluster-config-file
为 nodes-700*.conf
。
注意:此处 700*
最好设置为 Redis 监听端口。
7. 设置集群节点超时时间:cluster-node-timeout 5000
。
8. 设置 appendonly 为 yes 开启数据库以 AOF 方式持久化数据。
第五步,Redis 集群需要使用 Ruby 命令,因此该步我们安装 Ruby,安装命令如下:
yum install -y ruby
yum install -y rubygems
gem install redis
注意:假如在执行 gem install redis
命令时报如下错误:
那是因为 Redis 要求 Ruby 的最低版本为 2.2.2,而 CentOS yum 库仅提供 2.0.0 之前版本的 Ruby,所以无法满足需求。下面我们来介绍升级 Ruby 版本的具体步骤。
1. 安装 RVM:
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -L get.rvm.io | bash -s stable
find / -name rvm -print
2. 安装2.3.3版本 Ruby:
rvm install 2.3.3
3. 使用2.3.3版本 Ruby:
rvm use 2.3.3
4. 设置2.3.3版本 Ruby 为 Linux 默认 Ruby:
rvm use 2.3.3 --default
5. 卸载 2.0.0 版本 Ruby:
rvm remove 2.0.0
6. 确认 Ruby 版本:
ruby --version
7. 再次执行命令 gem install redis
安装 Redis 和 Ruby 接口即可。
第六步,执行如下命令,分别启动6个 Redis 实例:
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7001/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7002/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7003/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7004/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7005/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7006/redis.conf
执行命令 ps -ef | grep redis
看到 Redis 实例都已启动成功:
第七步,进入目录 /usr/local/software/redis-3.0.0-rc2/src
,执行命令构建集群:
./redis-trib.rb create --replicas 1 192.168.1.121:7001 192.168.1.121:7002 192.168.1.121:7003 192.168.1.121:7004 192.168.1.121:7005 192.168.1.121:7006
执行命令后输出如下内容说明集群搭建成功:
由图可以看出搭建的集群有3个 Master 节点,3个 Slave 节点,并且三个 Master 节点分别处理槽的数量为:5461、5462、5461,而从节点是没有槽的概念的。
注意:执行命令中 --replicas 1
表示集群中主、从节点的比列为1:1,即 Master、Slave 节点都为3个。
第八步,现在集群已经搭建成功,进入目录 /usr/local/redis/bin/
,执行如下命令连接客户端,并查看集群信息:
./redis-cli -c -h 192.168.1.121 -p 7001
cluster info
注意:-c
表示以集群方式连接 Redis 服务端,-h
表示 Redis 服务端 IP,-p
表示 Redis 服务端监听端口。
执行命令 cluster nodes
查看集群节点信息:
假如想关闭集群,只需要执行如下命令:
/usr/local/redis/bin/redis-cli -c -h 192.168.1.121 -p 7001 shutdown
/usr/local/redis/bin/redis-cli -c -h 192.168.1.121 -p 7002 shutdown
/usr/local/redis/bin/redis-cli -c -h 192.168.1.121 -p 7003 shutdown
/usr/local/redis/bin/redis-cli -c -h 192.168.1.121 -p 7004 shutdown
/usr/local/redis/bin/redis-cli -c -h 192.168.1.121 -p 7005 shutdown
/usr/local/redis/bin/redis-cli -c -h 192.168.1.121 -p 7006 shutdown
注意:集群关闭之后,下次若想再次重启,只需将集群实例节点再次启动即可,而无需再次执行构建集群相关的步骤。
上面关闭集群后,想再次打开集群,只需执行下面命令即可:
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7001/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7002/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7003/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7004/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7005/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7006/redis.conf
从上面的命令中可以看出集群已经搭建成功。
接下来将继续为大家演示在 Redis 集群中如何实现数据迁移(重新分片)。
Redis 数据迁移
为了演示数据迁移功能,我们再增加两个 Redis 节点(一主、一从),添加步骤如下。
第一步,在目录 /usr/local/redis-cluster/
下再新建两个文件夹 7007、7008,执行如下命令:
mkdir 7007 7008
此时 redis-cluster
目录下的文件夹如下图所示:
第二步,进入 /usr/local/redis-cluster/7006/
目录下,复制 redis.conf
配置文件到 7007、7008 文件夹下,并将配置文件中将端口信息相应地修改为7007和7008:
cp redis.conf ../7007
cp redis.conf ../7008
第三步,分别执行命令启动 7007、7008 Redis 节点:
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7007/redis.conf
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/7008/redis.conf
第四步,进入目录 /usr/local/software/redis-3.0.0-rc2/src
,执行命令将7007加入集群:
./redis-trib.rb add-node 192.168.1.121:7007 192.168.1.121:7001
注意:192.168.1.121:7007
表示即将加入节点的 IP 地址和端口信息,192.168.1.121:7001
表示集群中已经存在 Master 节点下的 IP 地址和端口信息。
第五步,选定一个已经分配槽的 Master 节点,执行命令:
./redis-trib.rb reshard 192.168.1.121:7001
显示如下:
-
在上图中输入3000回车,3000表示新加入的7007 Redis 实例分配槽的数量;
-
继上一步操作执行完后,页面继续提示“what is the receiving node ID?”,此处输入新加入 Redis 节点7007对应的 ID,然后按回车;
-
继上一步执行完后,页面继续提示输入被分配 Redis Master 实例节点的 ID,如下图所示:
上图 Source node 处,我输入的是 all,即表示从当前所有 Redis Master 节点中分配数据槽给7007 Redis 实例节点,分配完之后执行命令查看当前 Redis 实例槽分配情况,如下图所示:
从图中可以看出7007负责处理的槽分别是0-998、5461-6461、10923-11921。
第六步,继续执行命令将 Redis 7008 实例加入集群中:
./redis-trib.rb add-node 192.168.1.121:7008 192.168.1.121:7001
第七步,进入目录 /usr/local/redis/bin/
,执行命令连接到 Redis 7008实例上,再执行命令为该实例指定 Master 节点:
/redis-cli -c -h 192.168.1.121 -p 7008
cluster replicate c1a057dc1b4acb2ec1fbd2cf96398fb95fc6ee67
其中 c1a057dc1b4acb2ec1fbd2cf96398fb95fc6ee67
表示 Master Redis 7007实例的 ID,执行完上面命令后,利用 cluster nodes
命令查看当前集群信息。如下图所示:
到目前为止,我已为大家演示了如何在 Redis 集群不停服的情况下实现数据槽的迁移,而数据存在 Redis 数据槽中,以上操作也便实现了 Redis 集群中数据的迁移。
Redis 与 Lua 整合
Lua 是由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于1993年开发的一种轻量、小巧的脚本语言,用标准 C 语言编写,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Redis 在 2.6 版本中推出了脚本功能,允许开发者将 Lua 语言编写的脚本传到 Redis 中执行。使用 Lua 脚本的优点有如下几点:
- 减少网络开销:本来需要多次请求的操作,可以一次请求完成,从而节约网络开销;
- 原子操作:Redis 会将整个脚本作为一个整体执行,中间不会执行其它命令;
- 复用:客户端发送的脚本会存储在 Redis 中,从而实现脚本的复用。
下面为各位演示下如何在 Lua 脚本中操作 Redis。
1. 执行命令,新建目录 /usr/local/scripts/
:
mkdir -p /usr/local/scripts/
cd /usr/local/scripts/
2. 执行如下命令,在目录 /usr/local/scripts/
下建立一个 test.lua 脚本:
touch test.lua
然后在该 Lua 脚本中添加如下内容:
(1)获取第一个参数名称对应的值:
local name=redis.call("get",KEYS[1])
(2)获取第二个参数对应的值:
local age=redis.call("get",KEYS[2])
(3)如果第一个参数的值是 Brett
,第二个参数的值增加1:
if name=="Brett" then
redis.call("set",KEYS[1],ARGV[1])
redis.call("incr",KEYS[2])
end
3. 进入目录 /usr/local/redis/bin/
,然后执行命令启动 Redis 实例:
./redis-server /usr/local/etc/redis.conf
4. 执行命令连接 Redis,并查看用户名 Brett 的年龄(age):
./redis-cli
get age
5. 执行如下命令执行 test.lua 脚本:
/usr/local/redis/bin/redis-cli -h 192.168.1.121 -p 6379 --eval /usr/local/script/test.lua name age , Brett
6. 并查看用户名 Brett 的年龄(age):
get age
执行第5步命令后,查看 年龄信息(age)从之前的33变为34,说明 Redis 和 Lua 脚本整合成功。