第10课:Redis

第10课:Redis 高级技术点解析

Redis 属于非关系型数据库,也被称之为 NoSQL 数据库,主要以 Key-Value 形式存储数据,是目前用得较多的一种非关系型数据库。

Redis 主要有如下特点:

  1. 为纯内存数据库,操作速度非常快;
  2. 可以通过持久化(如 RDB、AOF)保证数据基本不丢失;
  3. 数据类型丰富,常用数据类型有字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Sorted set)等;
  4. 功能丰富,支持发布订阅、Lua 脚本、事务、Pipeline(管道,类似于关系型数据库的批量操作);
  5. 为单线程;
  6. 实现高可用比较灵活(主从复制、哨兵模式、集群);
  7. 易实现可扩展。

Redis 所包涵的内容非常多,无法在本课中一一讲解,为了让大家更高效地了解 Redis,我将从以下几个方面来介绍:

  1. Redis 快的主要原因;
  2. Redis 主从复制过程;
  3. Redis 数据分片原理及集群搭建;
  4. Redis 数据迁移;
  5. 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 多路复用模型如下图:

enter image description here

Redis 主从复制过程

生产环境中使用 Redis,保证其高可用至关重要。不管利用 Redis 的主从、哨兵、还是集群模式实现高可用,了解其主从复制实现原理是很有必要的。

Redis 2.8 之后版本对主从复制的实现较之前版本已有较大区别。接下来,我们就来了解下它们之前的区别。为了方便叙述,下面将2.8之前的版本称为“旧版本”,2.8(包括2.8)之后的版本称为“新版本”。

旧版本复制功能实现

Redis 的复制功能分为同步(Sync)和命令传播(Command Propagate)两个操作。

同步操作

同步操作的主要作用是保证主、从数据库状态一致,即当主服务器数据库中数据更新后,从服务器数据需在很短时间内也做到同步更新。

同步操作时,客户端首先手动向从服务器发送 SLAVEOF 命令(为当前从服务器指定 Master 节点),从服务器接收到命令后,则向主服务器发送 SYNC 命令,从而实现主、从服务器数据状态的一致。

下面是 SYNC 命令执行的主要步骤:

  1. 从服务器向主服务器发送 SYNC 命令;
  2. 主服务器接收到 SYNC 命令请求后,执行 BGSAVE 命令生成 RDB 文件,同时开启一个缓冲区对从现在开始执行的所有写操作进行备份;
  3. 主服务器执行 BGSAVE 命令后,立即将生成的 RDB 文件发送给从服务器,从服务器接收并载入此 RDB 文件,将自己的数据库状态同步为主服务器执行 BGSAVE 命令时的数据库状态;
  4. 最后,主服务器将备份在缓冲区内的所有写操作命令发送给从服务器,从服务器执行写操作命令保证主、从服务器数据库中数据一致。

执行 SYNC 命令过程如下图所示:

enter image description here

命令传播操作

同步操作执行完毕后,主、从服务器上数据库中的数据将达到一致状态,但该状态只是暂时的,当主服务器执行写操作命令时,主服务器的数据库数据被修改,从而导致主、从数据库数据不一致。为了让主、从服务器上数据库的数据再次达到一致性,此时必须执行“命令传播操作”。

为了便于大家理解这一过程,请看下面图示说明。

(1)状态一致的主、从服务器:

enter image description here

(2)主服务器执行 set key5 value 命令后,主、从服务器的状态就不一致了:

enter image description here

(3)为了使主、从服务器数据库状态再次达到一致状态,主服务器需要对从服务器执行命令传播操作,即主服务器将自己执行的写命令发送给从服务器执行,当从服务器执行相同写命令之后,主、从服务器数据库再次达到一致状态:

enter image description here

旧版复制功能缺陷

旧版复制功能实现可以分为初次复制和断线后重复制。

初次复制,即从服务器数据库当前没有数据,或者从服务器当前复制的主服务器和上一次复制的主服务器不同。

断线后重复制,表示处于命令传播阶段的主、从服务器因网络原因而中断了复制操作,从服务器通过自动重连与主服务器再次建立连接,并继续复制主服务器写操作,从而使主、从服务器数据库数据最终达到一致性状态。

对于初次复制而言,旧版复制功能能够很好的完成复制任务,但对于“断线后重复制”来说,该功能效率是非常低下的,主要因为旧版的“断线后重复制”每次都需要通过从服务器向主服务器发送 SYNC 命令而实现对主服务器数据进行全量复制,而全量复制是非常耗时的一个操作。

新版本复制功能实现

为了改善旧版断线后重复制的功能,新版本将 SYNC 命令升级为 PSYNC 命令,以实现复制时的同步操作。PSYNC 命令具有完整重同步(Full Resynchronization)和部分重同步(Partial Resynchronization)两种模式。

完整重同步功能与旧版的初次复制功能一样,都是通过让主服务器执行 BGSAVE 命令创建并发送 RDB 文件,同时向从服务器发送备份在缓冲区内的写操作命令来同步数据,最终使得主、从服务器数据库数据状态一致。

部分重同步则用于处理断线后重复复制的问题,当从服务器断线后重新与主服务器建立连接后,主服务器将主、从服务器断线期间执行的写操作命令发送给从服务器,从服务器接收并执行这些写操作命令就可以使主、从服务器数据库状态达到一致。因为部分重同步属于增量同步,因此和旧版断线后重复制相比效率有较大的改善。

主、从服务器执行部分重同步的通信过程如下:

enter image description here

受篇幅所限,部分重同步及 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)计算出命令要处理的数据库键属于哪个槽,并检查该槽是否指派给了自己,如果不是,便指引客户端将命令发送到正确的节点。整个命令执行过程如下图所示:

enter image description here

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-serverredis-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.shredis-benchmarkredis-check-aofredis-check-dumpredis-cliredis-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

enter image description here

如果输出如上图所示,说明 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:

enter image description here

注意:我服务器 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-filenodes-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 命令时报如下错误:

enter image description here

那是因为 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 实例都已启动成功:

enter image description here

第七步,进入目录 /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

执行命令后输出如下内容说明集群搭建成功:

enter image description here

由图可以看出搭建的集群有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 服务端监听端口。

enter image description here

执行命令 cluster nodes 查看集群节点信息:

enter image description here

假如想关闭集群,只需要执行如下命令:

/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 目录下的文件夹如下图所示:

enter image description here

第二步,进入 /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

显示如下:

enter image description here

  1. 在上图中输入3000回车,3000表示新加入的7007 Redis 实例分配槽的数量;

  2. 继上一步操作执行完后,页面继续提示“what is the receiving node ID?”,此处输入新加入 Redis 节点7007对应的 ID,然后按回车;

  3. 继上一步执行完后,页面继续提示输入被分配 Redis Master 实例节点的 ID,如下图所示:

enter image description here

上图 Source node 处,我输入的是 all,即表示从当前所有 Redis Master 节点中分配数据槽给7007 Redis 实例节点,分配完之后执行命令查看当前 Redis 实例槽分配情况,如下图所示:

enter image description here

从图中可以看出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 命令查看当前集群信息。如下图所示:

enter image description here

到目前为止,我已为大家演示了如何在 Redis 集群不停服的情况下实现数据槽的迁移,而数据存在 Redis 数据槽中,以上操作也便实现了 Redis 集群中数据的迁移。

Redis 与 Lua 整合

Lua 是由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于1993年开发的一种轻量、小巧的脚本语言,用标准 C 语言编写,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Redis 在 2.6 版本中推出了脚本功能,允许开发者将 Lua 语言编写的脚本传到 Redis 中执行。使用 Lua 脚本的优点有如下几点:

  1. 减少网络开销:本来需要多次请求的操作,可以一次请求完成,从而节约网络开销;
  2. 原子操作:Redis 会将整个脚本作为一个整体执行,中间不会执行其它命令;
  3. 复用:客户端发送的脚本会存储在 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)如果第一个参数的值是 huangchaobing,第二个参数的值增加1:

if name=="huangchaobing" 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,并查看用户名 huangchaobing 的年龄(age):

./redis-cli
get age

enter image description here

5. 执行如下命令执行 test.lua 脚本:

/usr/local/redis/bin/redis-cli -h 192.168.1.121 -p 6379 --eval /usr/local/script/test.lua name age , huangchaobing

6. 并查看用户名 huangchaobing 的年龄(age):

get age

enter image description here

执行第5步命令后,查看 huangchaobing 年龄信息(age)从之前的33变为34,说明 Redis 和 Lua 脚本整合成功。

有关 Redis 相关内容,我就为大家介绍到这里,如果各位读者有任何问题欢迎在读者圈和我交流。

上一篇
下一篇
目录