Redis实践-主从部署

最近跟几位前辈交流技术方面的问题,收获颇丰,认识到了很多高层面的东西,但也暴露了自身的不足,思想局限于平时常见的业务范围,没有积极深入地思考各类场景,对分布式集群架构这块内容的理解还是比较浅。本着学习与求知的心态,在未来的学习计划中,自己将完善这块的知识内容,弥补这方面的不足,同时也将把学习和实践过程记录在博客中,算是一个备忘录吧,哈哈。

本文主要介绍了利用基于CentOS的Docker镜像自定义Redis镜像,并在Redis镜像的基础上实例化出多个容器配置形成主从架构,最后利用本地的PHP服务器对Redis进行测试。

Redis是一个性能极高的内存数据库,它与Memcached相比,支持更加丰富的数据类型,还有多种场景方案,比如消息队列,订阅发布模型等,此外它还提供了持久化解决方案,可以把内存中的数据持久化到硬盘上,也可以重新恢复到内存中。

主从配置是常见的一种分压和数据容灾的策略,配置多台从服务器来减缓主服务器的压力,有必要时,进行读写分离,同样,多台从服务器也可以应对单台从服务器奔溃的情况。

定制Redis的Docker镜像

由于本人的笔记本配置有限,开多台虚拟机有点吃力呀,所以使用了Docker方案来搭建Redis服务器,当然使用虚拟机也是可以实现的。

首先,我需要把Redis的环境部署在Linux中,为此,我选择了CentOS镜像最为Linux环境,采用了网易的CentOS 6.5镜像。

拉取CentOS镜像:

1
docker pull hub.c.163.com/public/centos:6.5

拉取成功后,需要基于这个镜像定制一个Redis镜像,可通过dockerfile来配置构建,以下是我的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Redis Image

# IMAGES
FROM hub.c.163.com/public/centos:6.5

# MAINTAINER
MAINTAINER Oopsguy 474608426@qq.com

# CMD
RUN yum update; \
yum upgrade; \
yum install -y gcc; \
yum install -y vim; \
yum install -y tar; \
yum install -y wget; \
mkdir /home/tools; \
cd /home/tools; \
wget http://download.redis.io/releases/redis-3.2.9.tar.gz; \
tar -zxvf redis-3.2.9.tar.gz; \
cd redis-3.2.9; \
make;

# PORT
EXPOSE 6379

简单说一下dockerfile中的配置:

  • FROM 指定本镜像制作基于的镜像,这里是基于网易的CentOS 6.5镜像制作的
  • RUN 镜像在构建时执行的命令,由于此CentOS 镜像中缺少很多需要的库,本地需要在Linux中编译安装Redis,需要使用wget下载Redis源码,使用tar解压源码压缩包,使用GCC编译,使用VIM编辑器编辑Redis的配置文件。
  • EXPOSE 镜像曝露的内部端口,我们需要容器把Redis的端口映射到本地环境。

更多的dockerfile配置说明,请自行查阅。

以上的dockerfile文件定义了一个镜像,把Redis安装在了/home/tools/目录下,之后用Docker来构建此镜像:

1
docker build -t redis-master-slave .

以上命令构建的是当前目录下的dockerfile,所以此时终端的工作目应该是在dockerfile所在目录。

当看到下图中类似的结束语时,说明镜像构建成功了,可以使用docker images命令来查看。

Redis实践-主从部署

配置Redis主从方案

先启动一个容器进行配置测试:

1
docker run -d -p 6379:6379 --name redis-master-slave-test redis-master-slave:latest

启动后,可以使用docker ps来查看正在运行的容器:

1
2
3
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c5495e00e1d3 redis-master-slave "/usr/sbin/sshd -D" 18 seconds ago Up 17 seconds 22/tcp, 0.0.0.0:6379->6379/tcp redis-master-slave-test

进入c5495e00e1d3容器启动redis:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ docker exec -it c5495e00e1d3 bash
[root@c5495e00e1d3 /]# cd /home/tools/redis-3.2.9/src/
[root@c5495e00e1d3 src]# ./redis-server ../redis.conf --loglevel debug
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.9 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 23
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

23:M 03 Jun 14:53:23.217 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is s
et to the lower value of 128.
23:M 03 Jun 14:53:23.217 # Server started, Redis version 3.2.9
23:M 03 Jun 14:53:23.217 # WARNING you have Transpaent Huge Pages (THP) support enabled in your kernel. This will create laten
cy and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enab
led' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP
is disabled.
23:M 03 Jun 14:53:23.217 * The server is now ready to accept connections on port 6379

接下来尝试在本地使用PHP连接到容器中的Redis,本人使用的操作系统为Windows10,PHP套件环境为WAMPServer,具体安装PHP的Redis扩展可参照博客中的PHP与Memcached文章,可到http://pecl.php.net/package/redis下载。

可以通过查看php信息来确认当前是否安装了Redis扩展:

Redis扩展

建立一个PHP脚本文件,index.php;

1
2
3
4
5
<?php
$redis = new Redis();
$redis->connect('192.168.0.101', 6379);
$redis->set('demo', 'Oopsguy');
echo $redis->get('demo') , '<br/>';

运行之后,程序发生错误。

1
Fatal error: Uncaught exception 'RedisException' with message 'read error on connection'

经过排查,是Redis的配置文件redis.conf中配置了bind参数指定的主机,在这种情形下,只有当主机的IP为bind所指定的ip才能连接Redis,为了方便,先把它关注释。之后,重新启动redis,又发生了错误。

1
RedisException: protocol error, got 'n' as reply type byte

仔细查阅后,发现原因是redis.conf配置文件中配置了protected-mode为开启,即开启保护模式,如果开启了保护模式,redis将采取安全策略,需要通过参数指定的主机或者通过设置密码才能连接到Redis,由于我注释了bind参数,现在直接把protected-mode设置为no,之后重新启动redis。

可以看到脚本的运行结果成功了,浏览器上输出了Oopsguy,说明Reids确实存储了demo

接下来开始主从部署。虽然很多配置和启动命令是可以写在dockerfiledocker run命令里,但是为了更加清晰和便于修改,我还是喜欢在内容里配置redis.conf文件。

我从新开启了新的容器来进行部署,以下是简单的服务器方案:

  • redis-master-slave-master容器 IP 172.17.0.2 端口6379 主服务器
  • redis-master-slave-salve1容器 IP 172.17.0.3 端口6479 从服务器
  • redis-master-slave-slave2容器 IP 172.17.0.4 端口6579 从服务器

首先启动所有的容器:

1
2
3
4
5
6
7
8
9
10
11
Oopsguy@DESKTOP-6J9URKM MINGW64 ~
$ docker run -d -p 6379:6379 --name redis-master-slave-master redis-master-slave:latest
f2c03e85f3aacb15c11dfd3ded9de50faa5a3a9151ae3116e5fa4f52b214c825

Oopsguy@DESKTOP-6J9URKM MINGW64 ~
$ docker run -d -p 6479:6379 --name redis-master-slave-slave1 redis-master-slave:latest
6b23406c51ff002da1248ef523cf072894bb5c646e932d4ea8fe1e95f6fad905

Oopsguy@DESKTOP-6J9URKM MINGW64 ~
$ docker run -d -p 6579:6379 --name redis-master-slave-slave2 redis-master-slave:latest
058704fbf5ea291712ad12fa63418e50f28d0ab682f14e2ac4a7ad720da2d670

首先对所有服务器的redis.conf配置文件注释掉bind参数和关闭protected-mode,并开启daemon模式后台运行。

1
2
3
daeminize yes
#bind 127.0.0.1
protected-mode no

之后对两台从服务器配置从属的主服务器地址和端口,同样是在redis.conf配置文件中:

1
slaveof 172.17.0.2 6379

具体docker内部ip可以使用ifconfig命令来查。

之后在各个服务器的src目录中依次启动主服务器和从服务器,并开启调试日志:

1
./redis-server ../redis.conf --loglevel debug

为了检测redis服务是否启动=,可以使用ps命令查看相关进程:

1
2
[root@f2c03e85f3aa src]# ps -ef | grep redis
root 22 0 0 16:20 ? 00:00:00 ./redis-server *:6379

启动之后,我们可以在主服务器上使用redis-cli来查看主服务器情况。

1
2
3
4
5
6
7
8
9
10
11
12
[root@f2c03e85f3aa src]# ./redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=172.17.0.3,port=6379,state=online,offset=141,lag=0
slave1:ip=172.17.0.4,port=6379,state=online,offset=141,lag=0
master_repl_offset:141
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:140

之后观察从服务器:

redis-master-slave-slave1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@f2c03e85f3aa src]# ./redis-cli -h 172.17.0.3 -p 6379
172.17.0.3:6379> info replication
# Replication
role:slave
master_host:172.17.0.2
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:463
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

redis-master-slave-slave2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
172.17.0.4:6379> info replication
# Replication
role:slave
master_host:172.17.0.2
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:561
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

配置一切正常,接下来测试在主服务器中设置数据,数据能否同步到从服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@f2c03e85f3aa src]# ./redis-cli
127.0.0.1:6379> set blog 'oopsguy.com'
OK
127.0.0.1:6379> get blog
"oopsguy.com"
127.0.0.1:6379> exit
[root@f2c03e85f3aa src]# ./redis-cli -h 172.17.0.4 -p 6379
172.17.0.4:6379> get blog
"oopsguy.com"
172.17.0.4:6379> exit
[root@f2c03e85f3aa src]# ./redis-cli -h 172.17.0.3 -p 6379
172.17.0.3:6379> get blog
"oopsguy.com"
172.17.0.3:6379> exit
[root@f2c03e85f3aa src]# ./redis-cli
127.0.0.1:6379> del blog
(integer) 1
127.0.0.1:6379> get blog
(nil)
127.0.0.1:6379> exit
[root@f2c03e85f3aa src]# ./redis-cli -h 172.17.0.3 -p 6379
172.17.0.3:6379> get blog
(nil)
172.17.0.3:6379> exit
[root@f2c03e85f3aa src]# ./redis-cli -h 172.17.0.4 -p 6379
172.17.0.4:6379> get blog
(nil)

最后一道测试,本地使用PHP连接容器进行操作:

master.php:

1
2
3
4
5
<?php
$redis = new Redis();
$redis->connect('192.168.0.101', 6379);
echo 'MASTER SET "demo" => "redis-master-salve"<br/>';
$redis->set('demo', 'redis-master-slave');

slave.php:

1
2
3
4
5
6
7
8
<?php
$redis = new Redis();
$redis->connect('192.168.0.101', 6479);
echo 'SLAVE1 GET "demo" => ';
echo $redis->get('demo') . '<br>';
$redis->connect('192.168.0.101', 6579);
echo 'SLAVE2 GET "DEMO" => ';
echo $redis->get('demo') . '<br>';

观察运行结果,master服务器设置的数据,都能在slave1和slave2服务器中获取。

master.php运行结果

slave.php运行结果

总结

以上介绍了利用docker部署redis主从环境,场景比较简单,只有一台主服务器和两台从服务器,但在真实的线上环境,面对各种复杂的问题,光是知道怎么简单的配置还是难以应对,还需要自己灵活多变,之后,我将会对redis进行更加深入地了解,并记录、分享给大家关于redis主从和集群方面的学习心得。