REDIS哨兵
通过docker模拟redis环境
一.为了可以指定各个容器的ip段可以实现数据通讯,创建这自定义网络以便容器工作在该网络中。
#docker network create --subnet=172.10.0.0/100 redisnetwork
查看本机docker网络
# docker network ls NETWORK ID NAME DRIVER SCOPE cdcff0a52e6e bridge bridge local 9468833f9da1 host host local 1df7988af3ee redisnetwork bridge local 5fbdc3d708e4 none null local
二.使用Dockerfile创建镜像
FROM centos:latest RUN groupadd -r redis && useradd -r -g redis redis RUN yum -y update && yum -y install epel-release && yum -y install redis && yum -y install net-tools RUN mkdir -p /workdir && chown -R redis:redis /workdir VOLUME /workdir WORKDIR /workdir EXPOSE 6379
进入Dockerfile所在文件夹执行以下命令创建一个镜像
# docker build -t redis . # docker images REPOSITORY TAG IMAGE ID CREATED SIZE redis latest a63b9de92df2 7 days ago 370MB centos latest 75835a67d134 2 months ago 200M
三.建立redis主从配置
使用新增的镜像创建1个master、2个slave、3个sentinel容器,创建完使用docker ps查看是否成功
# docker run -itd --name redis-master -v /workdir:/workdir -network=redisnetwork -p 6380:6379 -ip 172.10.0.2 redis
# docker run -itd --name redis-slave1 -v /workdir:/workdir -network=redisnetwork -p 6381:6379 -ip 172.10.0.3 redis
# docker run -itd --name redis-slave2 -v /workdir:/workdir -network=redisnetwork -p 6382:6379 -ip 172.10.0.4 redis
# docker run -itd --name redis-sentinel1 -v /workdir:/workdir -network=redisnetwork -p 26380:26379 -ip 172.10.0.5 redis
# docker run -itd --name redis-sentinel2 -v /workdir:/workdir -network=redisnetwork -p 26381:26379 -ip 172.10.0.6 redis
# docker run -itd --name redis-sentinel3 -v /workdir:/workdir -network=redisnetwork -p 26382:26379 -ip 172.10.0.6 redis
进入redis-master容器设置对应的redis.conf配置文件,redis默认安装后会在/etc目录下有两个配置问价分别为redis.conf、redis-sentinel.conf。分别是redis服务器模式配置文件及redis哨兵模式配置文件。
# ll /etc/ | grep redis -rw-r-----. 1 redis root 7642 Oct 26 07:05 redis-sentinel.conf -rw-r-----. 1 redis root 46729 Oct 26 07:05 redis.conf
redis.conf修改以下主要配置,由于是测试所以只设置基础配置。
注意:如果redis服务需要设置密码的话尽量所有的redis服务连接密码都设置为统一的。以便以后redis哨兵做故障转移时可以使用同一个密码连接所有redis服务节点
################################## NETWORK ##################################### bind 0.0.0.0 #允许所有ip的请求 ################################# REPLICATION ################################# masterauth 123456 #主节点密码,这个很重要。不设置的话当当前主节点降级为从节点的时候会连接不到新的主节点 ################################## SECURITY ################################### requirepass 123456 #设置连接密码
进入其他两个redis-slave容器修改配置文件redis.conf
################################## NETWORK #####################################
bind 0.0.0.0 #允许所有ip的请求
################################# REPLICATION #################################
slaveof 172.10.0.2 6379 #绑定主节点ip
masterauth 123456 #主节点密码,这个很重要。不设置的话当当前主节点降级为从节点的时候会连接不到新的主节点
################################## SECURITY ###################################
requirepass 123456 #设置连接密码
三个redis容器以/etc/redis.conf配置文件启动服务模式
# redis-server /etc/redis.conf
至此redis的主从环境配置完毕!
测试主从配置。redis-master容器内修改数据、redis-slave1容器查询数据:
redis-master容器
# redis-cli -a 123456 #进入本地客户端 127.0.0.1:6379> get master-name "1544348478" 127.0.0.1:6379> get time "2018-12-09 19:00:18" 127.0.0.1:6379> SET company tg OK
redis-slave1容器
# redis-cli -a 123456 #进入slave1客户端 127.0.0.1:6379> SET company ttt #向slave1客户端写入数据 (error) READONLY You can't write against a read only slave. #因为是从服务中,默认不能写入数据。参考配置文件中的slave-read-only yes配置 127.0.0.1:6379> GET company #查询company值,已经同步到本地 "tg" 127.0.0.1:6379>
主从复制的相关配置数据
#从库中的replication信息,描述的是redis关系信息 127.0.0.1:6379> INFO replication # Replication role:slave #角色slave master_host:172.10.0.3 #主库地址 master_port:6379 #主库端口 master_link_status:up #与主库的连接状态 master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:14261662 #当前库的偏移量 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 #主库中的replication信息 127.0.0.1:6379> info replication # Replication role:master #角色主库 connected_slaves:2 #连接了两台从库 slave0:ip=172.10.0.4,port=6379,state=online,offset=14300743,lag=1 #从库1信息 slave1:ip=172.10.0.2,port=6379,state=online,offset=14300879,lag=1 #从库2信息 master_repl_offset:14300879 #主库偏移量 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:13252304 repl_backlog_histlen:1048576 127.0.0.1:6379>
偏移量描述的是库中的数据量修改进度,当有数据更新时,偏移量就会增加。当从库与主库偏移量相同时表示从库数据与主库一致。所以后面当配置了哨兵后从哨兵中获取的从库列表中取偏移量最高的作为读取数据的redis节点会比较靠谱。
四.建立哨兵
三个哨兵容器中修改/etc/redis-sentinel.conf配置文件主要配置如下
# *** IMPORTANT *** bind 0.0.0.0 #允许其他ip地址链接 protected-mode no #关闭保护模式 sentinel monitor mymaster 172.10.0.2 6379 2 #监听指定ip的redis服务为主节点并且命名为mymaster指定当不少于2个哨兵投票进行主节点故障转移时才允许进行故障转移 sentinel auth-pass mymaster 123456 #指定redis主节点的连接密码
分别在三个哨兵容器中启动哨兵服务
# redis-sentinel /etc/redis-sentinel.conf
查看哨兵配置是否执行成功
# vi /var/log/redis/sentinel.log #查看哨兵日志 _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 3.2.12 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 32 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 32:X 08 Dec 15:25:46.676 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 32:X 08 Dec 15:25:46.679 # Sentinel ID is 077a2b90c1f1ba10c736e1e2427969dfe73235a4 32:X 08 Dec 15:25:46.679 # +monitor master mymaster 172.10.0.2 6379 quorum 2 #主库信息 32:X 08 Dec 15:25:46.680 * +slave slave 172.10.0.3:6379 172.10.0.3 6379 @ mymaster 172.10.0.2 6379 #从库信息 32:X 08 Dec 15:25:46.682 * +slave slave 172.10.0.4:6379 172.10.0.4 6379 @ mymaster 172.10.0.2 6379 #从库信息
至此哨兵部署完毕!!!
测试效果:
master容器杀死redis进程
[root@9b7c27bd5e62 workdir]# netstat -apn | grep redis tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 85/redis-server 0.0 tcp 0 0 172.10.0.2:6379 172.10.0.9:49566 ESTABLISHED 85/redis-server 0.0 tcp 0 0 172.10.0.2:41422 172.10.0.3:6379 ESTABLISHED 85/redis-server 0.0 tcp 0 0 172.10.0.2:6379 172.10.0.9:33964 ESTABLISHED 85/redis-server 0.0 tcp 0 0 172.10.0.2:6379 172.10.0.11:58957 ESTABLISHED 85/redis-server 0.0 tcp 0 0 172.10.0.2:6379 172.10.0.10:55111 ESTABLISHED 85/redis-server 0.0 tcp 0 0 172.10.0.2:6379 172.10.0.11:34605 ESTABLISHED 85/redis-server 0.0 tcp 0 0 172.10.0.2:6379 172.10.0.10:51200 ESTABLISHED 85/redis-server 0.0 [root@9b7c27bd5e62 workdir]# kill 85
再次查看哨兵日志会发现一系列的选举及切换主库的记录
46:X 08 Dec 17:20:18.918 # +switch-master mymaster 172.10.0.2 6379 172.10.0.3 6379 46:X 08 Dec 17:24:50.227 # +sdown master mymaster 172.10.0.3 6379 46:X 08 Dec 17:24:50.373 # +elected-leader master mymaster 172.10.0.3 6379 46:X 08 Dec 17:27:51.564 # +failover-end-for-timeout master mymaster 172.10.0.3 6379 46:X 08 Dec 17:27:51.565 * +slave slave 172.10.0.3:6379 172.10.0.3 6379 @ mymaster 172.10.0.4 6379 46:X 08 Dec 17:28:21.602 # +sdown slave 172.10.0.3:6379 172.10.0.3 6379 @ mymaster 172.10.0.4 6379 46:X 08 Dec 17:33:00.425 # -sdown slave 172.10.0.3:6379 172.10.0.3 6379 @ mymaster 172.10.0.4 6379 46:X 08 Dec 17:33:03.180 # +sdown master mymaster 172.10.0.4 6379 46:X 08 Dec 17:33:03.263 # +odown master mymaster 172.10.0.4 6379 #quorum 2/2 46:X 08 Dec 17:33:03.263 # +new-epoch 3 46:X 08 Dec 17:33:03.263 # +try-failover master mymaster 172.10.0.4 6379 46:X 08 Dec 17:33:03.275 # +vote-for-leader 077a2b90c1f1ba10c736e1e2427969dfe73235a4 3 46:X 08 Dec 17:33:03.283 # 7b924a2f5fd97f29f0d6ee973449bafe89d74271 voted for 077a2b90c1f1ba10c736e1e2427969dfe73235a4 3 46:X 08 Dec 17:33:03.283 # 496374ec01f0f4e913708bb5eb16fa6e952e02fc voted for 077a2b90c1f1ba10c736e1e2427969dfe73235a4 3 46:X 08 Dec 17:33:03.352 # +elected-leader master mymaster 172.10.0.4 6379 46:X 08 Dec 17:33:03.352 # +failover-state-select-slave master mymaster 172.10.0.4 6379 46:X 08 Dec 17:33:03.442 # -failover-abort-no-good-slave master mymaster 172.10.0.4 6379 46:X 08 Dec 17:33:03.532 # Next failover delay: I will not start a failover before Sat Dec 8 17:39:03 2018 46:X 08 Dec 17:33:12.347 * +reboot slave 172.10.0.3:6379 172.10.0.3 6379 @ mymaster 172.10.0.4 6379 46:X 08 Dec 17:39:03.562 # +new-epoch 4 46:X 08 Dec 17:39:03.564 # +vote-for-leader 496374ec01f0f4e913708bb5eb16fa6e952e02fc 4 46:X 08 Dec 17:39:03.592 # Next failover delay: I will not start a failover before Sat Dec 8 17:45:03 2018 46:X 08 Dec 17:39:04.464 # +config-update-from sentinel 496374ec01f0f4e913708bb5eb16fa6e952e02fc 172.10.0.10 26379 @ mymaster 172.10.0.4 6379 46:X 08 Dec 17:39:04.464 # +switch-master mymaster 172.10.0.4 6379 172.10.0.3 6379 46:X 08 Dec 17:39:04.464 * +slave slave 172.10.0.2:6379 172.10.0.2 6379 @ mymaster 172.10.0.3 6379 46:X 08 Dec 17:39:04.464 * +slave slave 172.10.0.4:6379 172.10.0.4 6379 @ mymaster 172.10.0.3 6379
在新选举出来的主库中查看信息
发现这个原来的从库的角色已经变为master了并且另外的从库已经连接到当前master节点
127.0.0.1:6379> info replication # Replication role:master connected_slaves:1 slave0:ip=172.10.0.4,port=6379,state=online,offset=14300743,lag=1 master_repl_offset:14300879 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:13252304 repl_backlog_histlen:1048576
再次启动原来的主库
#redis-server /etc/redis.conf #还是使用原来的主配置启动,这时实际上是不会连接到新的主从配置节点的。必须进入redis客户端使用SLAVEOF命令让当前服务加入到新的主从配置中
#redis-cli -a 123456 127.0.0.1:6379> SLAVEOF 172.10.0.3 6379 #重新指派主从配置
如果开始的时候主库所在容器的redis.conf中没有配置masterauth 123456这个配置的话这时候就算从新指派了加入新的主从配置也不会连接成功。查看redis日志会找到类似的Master does not understand REPLCONF listening-port: -NOAUTH Authentication required.错误。这时候就需要登录客户端命令模式中执行CONFIG SET masterauth 123456命令来修改该配置
#vi /var/log/redis/redis.log
85:S 08 Dec 17:23:22.522 * Master replied to PING, replication can continue... 85:S 08 Dec 17:23:22.522 * (Non critical) Master does not understand REPLCONF listening-port: -NOAUTH Authentication required.
#redis-cli -a 123456
127.0.0.1:6379> CONFIG SET masterauth 123456
至此redis服务模式下测试完毕!
五.使用PHP代码模拟实际情况使用哨兵
实际流程为,使用phpredis扩展连接哨兵使用以下命令获取从库信息并且使用从节点读取数据、主节点写入数据。
/** * 解析单个从节点数据返回主节点及从节点配置 * @param array $slave_info * @return array */ function parse_redis_config($slave_info = []){ $slave_config = [ 'ip'=>'', 'port'=>'' ]; $master_config = [ 'ip'=>'', 'port'=>'' ]; foreach ($slave_info as $k=>$value){ switch ($value){ case 'ip': $slave_config['ip'] = $slave_info[$k+1]; break; case 'port': $slave_config['port'] = $slave_info[$k+1]; break; case 'master-host': $master_config['ip'] = $slave_info[$k+1]; break; case 'master-port': $master_config['port'] = $slave_info[$k+1]; break; } } return['slave'=>$slave_config,'master'=>$master_config]; } /** * redis配置类 * Class RedisConfig */ class RedisConfig{ public $ip; public $port; /** * @var Redis */ private $redis; function __construct($ip,$port) { $this->ip = $ip; $this->port = $port; } public function getClient(){ if ($this->redis) return $this->redis; $this->redis = new Redis(); $this->redis->connect($this->ip,$this->port); $this->redis->auth(1); return $this->redis; } } $sentinel = [ ['ip'=>'172.10.0.9','port'=>'26379'], ['ip'=>'172.10.0.10','port'=>'26379'], ['ip'=>'172.10.0.11','port'=>'26379'], ]; $sentinel_info = $sentinel[array_rand($sentinel)]; $redis = new Redis(); $redis->connect('172.10.0.9',26379); $slaves_info = $redis->rawCommand('SENTINEL','slaves','mymaster'); if (empty($slaves_info)) exit('nothing slaves info');
/**
* 借助swoole定时器随机选择从节点
*/ swoole_timer_tick(600,function()use ($slaves_info){ $slave_info = $slaves_info[array_rand($slaves_info)]; $configs = parse_redis_config($slave_info); var_dump($configs); $master = new RedisConfig($configs['master']['ip'],$configs['master']['port']); $slave = new RedisConfig($configs['slave']['ip'],$configs['slave']['port']); $master = $master->getClient(); $slave = $slave->getClient(); echo $slave->get('time').PHP_EOL; echo $master->set('time',date('Y-m-d H:i:s')).PHP_EOL; echo $slave->get('time').PHP_EOL; });
测试代码
[root@test redis-sentinel]# php client.php array(2) { ["slave"]=> array(2) { ["ip"]=> string(10) "172.10.0.4" ["port"]=> string(4) "6379" } ["master"]=> array(2) { ["ip"]=> string(10) "172.10.0.3" ["port"]=> string(4) "6379" } } 2018-12-09 19:00:18 1 2018-12-09 22:33:39 array(2) { ["slave"]=> array(2) { ["ip"]=> string(10) "172.10.0.2" ["port"]=> string(4) "6379" } ["master"]=> array(2) { ["ip"]=> string(10) "172.10.0.3" ["port"]=> string(4) "6379" } } 2018-12-09 22:33:39 1 2018-12-09 22:33:39 array(2) { ["slave"]=> array(2) { ["ip"]=> string(10) "172.10.0.2" ["port"]=> string(4) "6379" } ["master"]=> array(2) { ["ip"]=> string(10) "172.10.0.3" ["port"]=> string(4) "6379" } } 2018-12-09 22:33:39 1 2018-12-09 22:33:40 array(2) { ["slave"]=> array(2) { ["ip"]=> string(10) "172.10.0.4" ["port"]=> string(4) "6379" } ["master"]=> array(2) { ["ip"]=> string(10) "172.10.0.3" ["port"]=> string(4) "6379" } } 2018-12-09 22:33:40 1 2018-12-09 22:33:41
参考资料:
http://redisdoc.com/