在本博客前面的文章给出redis-cluster模式的配置和测试《一篇文章掌握redis-cluster原理及其部署、测试》,redis还有另外一种failover自动切换的部署方式,也即是本文给出的——Sentinel模式(哨兵模式),这两种方式部署的redis服务其实在普通的项目完全够用,例如个人在Django项目使用的Sentinel模式保证了”查询缓存服务以及一些频繁读取配置参数服务“的高可用。对于并发量大的需求,可以使用国内知名Codis——分布式Redis集群代理中间件,可配置规模更大的redis集群服务。
1、安装redis
为保持文章内容完整,这里给出redis的安装过程。两种方式,一种为yum 安装,另外一种下载包安装。这里选择bin包下载安装。目前redis稳定版为5.0.7,tar包为仅为1.7M,不愧为缓冲界的宠儿。安装包放在opt下,个人喜好将所有关开发的相关组件安装包放置于/opt目录,例如前面大数据各个组件的安装包,还是为了方便记忆、管理和查找。1
2
3
4
5
6
7[root@nn redis-5.0.7]# pwd
/opt/redis-5.0.7
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar xzf redis-5.0.7.tar.gz
cd redis-5.0.7
make
将redis启动命令所在的src路径加入系统变量1
2[root@nn redis-5.0.7]# source ~/.bash_profile
PATH=$PATH:$HOME/bin:/opt/redis-5.0.7/src/
查看版本1
2[root@nn opt]# redis-server -v
Redis server v=5.0.7 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=864a7319aeb56c9b
启动redis-server后台进程:1
2
3
4
5
6
7
8
9[root@nn opt]# redis-server &
[root@nn opt]# ps -ef |grep redis
root 91054 60098 0 11:47 pts/0 00:00:00 redis-server *:6379
[root@nn opt]# redis-cli
127.0.0.1:6379> set msg 1
OK
127.0.0.1:6379> get msg
"1"
2、Sentinel 的配置说明
2.1 官网有关Sentinel模式的基本信息
The current version of Sentinel is called Sentinel 2,A stable release of Redis Sentinel is shipped since Redis 2.8.
Sentinels by default run listening for connections to TCP port 26379,
If you are using theredis-sentinel
executable, you can run Sentinel
with the following command line:redis-sentinel /path/to/sentinel.conf
redis要求启动Sentinel服务时必须带上其配置文件,否则直接返回启动失败。
启动Sentinel模式前的基本要求:
- You need at least three Sentinel instances for a robust deployment.(至少3个Sentinel实例,多数票选举)
- 最好在不同物理机上或者虚拟机上启动每个Sentinel 实例(在测试环境下,当然也可在同一台服务器里面,启动不同端口的多个实例也可完成测试。)
- Sentinel + Redis distributed system does not guarantee that acknowledged writes are retained during failures,since Redis uses asynchronous replication.(Sentinel+redis分布式集群环境下,节点出现故障时,不保证写一致性,因redis异步复制方式实现集群数据同步)
2.2 redis官网Sentinel模式说明
有些缩写需要说明:
- Masters are called M1, M2, M3, …, Mn.
- replicas are called R1, R2, R3, …, Rn (R stands for replica).(replica也就是slave角色,因为slave有歧视语义,很多中间件不再使用该词描述副角色,例如kafka备份分区的:replica)
- Sentinels are called S1, S2, S3, …, Sn.
- Clients are called C1, C2, C3, …, Cn.
首先看官方首推的 basic setup with three boxes:It is based on three boxes, each box running both a Redis process and a Sentinel process. 每个box代表一个redis节点,确认master失败的选票数为2
1 | +----+ |
If the master M1 fails, S2 and S3 will agree about the failure and will
be able to authorize a failover, making clients able to continue.
如果主redis M1宕机(哨兵S1当然也会挂掉),那么其他节点上哨兵S2和哨兵S3发现与S1心跳失败,两者一致同意此时进入故障转移,选举R2为新的master M2。
redis sentinel实现高可用,但也会在某种程度下有丢失有些写数据。例如下面的情况:客户端C1原来与M1连接,写入M1,当M1挂了,到M2起来的这个过程,C1在这一过程写的部分数据会丢失。
1 | +----+ |
以上情况可通过以下两个配置实现数据丢失最小化。that allows to stop accepting writes if a master detects thatit is no longer able to transfer its writes to the specified number of replicas。
这里用到replica关键字单词,在本博客前面kafka文章里面,kafka也有自己的replica名词,不过kafka的replica是指top 分区后的副本,redis这里replica是从服务器(开源界不建议使用slave这个带有歧视的单词)。通过以下设置,只有当前master主机至少还有一个alive的replica才准许外部客户端写入数据。
1 | min-replicas-to-write 1 |
3、一主两从的redis架构配置
第2部分的sentinel 2 高可用的前提是基于1主2两从的架构基础上实现的,如下架构,故首先得让一主两从的redis小集群跑起来
1 | +----+ |
M1为1主,两从:R2、R3,在此基础上,每个节点运行sentinel进程,即可实现redis高可用架构。
3.1 配置主从的redis.conf文件
redis的配置文件的注释有分段说明,这里列出仅需修改的地方:
”一主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################################# NETWORK
绑定本机IP
bind 182.0.0.10
################################ GENERAL
后台守护进程运行
daemonize yes
############################### SNAPSHOTTING
存放快照(数据日志文件的目录)dump.rdb
dir /opt/redis-5.0.7/data
################################ REPLICATION
1主两从架构里,至少一个有个从服务器在线且在10秒以内延迟,主redis才能对外提供写服务器,否则客户端无法写
min-replicas-to-write 1
min-replicas-max-lag 10
这里也需设置,因为当该master挂了再重启,变成replica后,需要密码去认证新的master
masterauth foo123
################################# SECURITY
为master设置认证密码
requirepass foo123
################################## CLIENTS
按默认
############################# MEMORY MANAGEMENT
按默认
”2个replica“节点的redis.conf配置
1 | ################################# NETWORK |
3.2 启动和测试主从
启动所有主从redis-server,后台守护进程运行1
[root@p1 opt]# redis-server /opt/redis-5.0.7/redis.conf
注意:
如果只启动master,从服务器还未启动,提示没有足够的从服务器在线,无法对外提供写服务。1
2127.0.0.1:6379> set test 1
(error) NOREPLICAS Not enough good replicas to write.
这是因为min-replicas-to-write 1
要求最少1个从redis在线后master才能接收客户端写数据。
在master set一个key1
2
3
4
5
6
7
8[root@p1 opt]# redis-cli -a foo123
127.0.0.1:6379> set foo 1
```shell
在两个从服务器get key
```shell
[root@p2 redis-5.0.7]# redis-cli -a foo123
127.0.0.1:6379> get foo
"1"1
2
3[root@p3 redis-5.0.7]# redis-cli -a foo123
127.0.0.1:6379> get foo
"1"
通过在master 查看主从信息:
1 | 127.0.0.1:6379> INFO Replication |
注:INFO [section]命令可以查看多个部分信息,也可指定查看某个section的信息
以上说明1主两从redis架构已经构建,该模式下,只有master才能写数据,replica只能get数据。如果尝试在replica上写数据,将提示readonly:1
2127.0.0.1:6379> set bar 2
(error) READONLY You can't write against a read only replica.
4、sentinel 高可用配置
sentinel 高可用是基于主-从-从正常运行情况下配置,经过前面2.3点,相信很容易理解该sentinel的逻辑,
4.1 配置sentinel.conf
主、从的sentinel.conf都一样,而且也很简单,更改两项属性即可,其他可以按默认值,如果需要调优,可自行参考conf的说明设置相应值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15bind 0.0.0.0
port 26379
daemonize yes
sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 182.0.0.10 6379 2
如果master设置了密码,那么也告诉sentinel密码
sentinel auth-pass mymaster foo123
<master-name> 自行定义名称,这里使用mymaster默认值,后面的配置项都用了mymaster这个名,在django项目的settings,redis缓存设置也需要用到该master-name,无特殊需求不用改。
<quorum> 裁定master挂了的最低通过票数
Tells Sentinel to monitor this master, and to consider it in O_DOWN
(Objectively Down) state only if at least <quorum> sentinels agree.
告诉Sentinel通监控master,如果有两个sentinel认为master挂了,说明master真的挂了
4.2 测试redis高可用
启动所有主从的sentinel 服务,注意如果用 redis-server启动命令,需要带上选项 ——sentinel1
2
3[root@nn opt]# redis-server /opt/redis-5.0.7/sentinel.conf --sentinel
或者使用
[root@nn opt]# redis-sentinel /opt/redis-5.0.7/sentinel.conf
在master上查看sentinel状态,只需连接sentinel的工作端口即可,可以看到该master下带了两个replica,状态正常: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
30
31
32
33
34
35
36
37
38
39
40
41
42
43[root@nn redis-5.0.7]# redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "182.0.0.10"
5) "port"
6) "6379"
7) "runid"
8) "7cdc7e518b592168c94268f7d55fc2d237449118"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "516"
19) "last-ping-reply"
20) "516"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "3335"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "113804"
29) "config-epoch"
30) "0"
31) "num-slaves"
32) "2"
33) "num-other-sentinels"
34) "1"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"
查看两个replica上的sentinel服务也正常运行
1 | 127.0.0.1:26379> SENTINEL slaves mymaster |
高可用测试:
kill 掉master的redis-server进程和redis-sentinel进程,模拟master服务器宕机情况:
1 | [root@nn redis-5.0.7]# ps -ef |grep redis |
在replica 1查看目前是否已转移到:可以看到两个replica已经选举出新的master
1 | [root@localhost redis-5.0.7]# redis-cli -p 26379 |
也可通过redis-cli上查看:目前182.0.0.11已成为新的master,有个正常连接的replica
1 | 127.0.0.1:6379> info Replication |
在新maser set 值,可在剩余的一个replica get到相应的key
将原宕机的master恢复redis进程和sentinel进程,在新的master:182.0.0.11上,查看10节点已加入到replicas列表:
slave0:ip=182.0.0.12,port=6379,state=online,offset=211840,lag=1
slave1:ip=182.0.0.10,port=6379,state=online,offset=211840,lag=1
1 | Replication |
以上完成redis 1主2从的高可用配置和测试,下面将在实际项目中引入。
5、在python项目或者django的项目引入sentinel集群
5.1 python项目连接sentinel集群
1 | import redis |
查看用法1
2
3
4
5
6
7
8
9
10
11In [5]: ?st.master_for
Signature:
st.master_for(
service_name,
redis_class=<class 'redis.client.Redis'>,
connection_pool_class=<class 'redis.sentinel.SentinelConnectionPool'>,
**kwargs,
)
Docstring:
Returns a redis client instance for the ``service_name`` master.
创建连接实例,kwargs参数跟redis.Redis入参一致
1 | # 注意别漏了sentinel集群设了密码 |
在服务器上把当前10节点master kill掉,再看看python取值是否被影响
1 | # 经过3秒左右,再次获取最新的master可以看到已转移到11节点上,所以这3秒时间实际也是丢失数据的时间窗口 |
5.2 django项目中使用引入sentinel集群
5.2.1 单redis实例
在Django项目开发中,一般可以redis作为django后端cache的中间件,很多需求可以满足:例如验证码、session、缓存查询数据等。
首先需要django-redis这个库支持:pip install django-redis,具体用法参考官方doc
如果是单实例redis,在setting的设置相对简单:redis的0号db作为默认缓存,1号db作为hp这个App的数据缓存
1 | # 将redis设为django缓存 |
启动django shell测试:
1 | [root@nn hp]# python manage.py shell |
注意:如果使用cache方法,则默认使用default数据库,若需要使用其他名称的数据,需要用caches方法,并通过settings里面redis数据库名称来索引
在redis服务器查看default库相应的key:1
2
3
4127.0.0.1:6379> keys *
1) "bar"
2) "redis-HA"
3) "dj:1:name"
因为在cache里面设置key的前缀为dj,加上cache会给key再打上:1:
这个默认前缀,故实际存储的完整key名称:”dj:1:name”
同理查看db1号库的key1
2
3
4
5
6
7
8127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> keys *
1) "dj:bi:1:code"
127.0.0.1:6379[1]> get dj:bi:1:code
"133"
若Django项目中需要使用更多进阶的原生client功能连接redis(支持redis所有方法)),需使用get_redis_connection,建议在实际项目使用该方法获取redis连接对象。
1 | In [13]: from django_redis import get_redis_connection |
5.2.2 django项目的cache引入sentinel模式
该模式需要安装新的django插件,github地址
1 | pip install django-redis-sentinel |
这个插件其实封装了redis.sentinel将其作为django_redis的插件之一,注意,该插件已不再维护,如果在重要项目上,不建议使用。(重要项目直接用codis,或者redis-cluster,以便可以pip install到相关的redis连接插件,或者自己参考模板写一个)
在setting.py中cache的设置:
Location 格式: master_name/sentinel_server:port,sentinel_server:port/db_id
1 | CACHES = { |
该 django-redis-sentinel的插件代码实现不到120行,主要看看connect方法,写数据时用sentinel.discover_master(master_name)获取master去写,读数据时,随机取一个replica 读数据random.choice(sentinel.discover_slaves(master_name))
1 | def connect(self, write=True, SentinelClass=None): |
注意:如果使用sentinel这个插件,那么需使用django_redis的get_redis_connection,而不是使用django.core.cache的cache
1 | In [1]: from django_redis import get_redis_connection |
注意:get_redis_connection()有bug,当使用conn实例获取哨兵集群的master或者replica,get_redis_connection()调用了your pythonpath/python3.7/site-packages/redis/client.py
里面的self.execute_command('SENTINEL MASTER', service_name)
,然而该方法是在6379端口连接的client执行SENTINEL MASTER mymaster
,由于所有的SENTINEL 命令只能在26379端口下启动的client才能执行,因此直接用get_redis_connection()获取sentinel信息会提示未知命令:
1 | In [8]: conn.sentinel_master('mymaster') |
测试以上出错信息也简单:连接redis-server的6379端口client
1 | [root@localhost redis-5.0.7]# redis-cli -a foo123 |
由于sentinel集群监听的是26379端口来执行有关查询命令(端口号在sentinel.conf文件配置),而get_redis_connection()使用的是6379,用此报错。
1 | [root@localhost redis-5.0.7]# redis-cli -a foo123 -p 26379 |
解决办法有三种种:
第一种:修改redis源码,改get_redis_connection,也不难
第二种:sentinel监听端口设为默认6379,redis-server设为其他端口号即可
第三种:不需要使用django redis插件,直接用redis原生库自行封装相关sentinel的操作
综上完成基于sentinel模式的redisHA配置以及详细解释了在python项目和django项目中如何使用sentinel集群模式,个人认为,目前该方案足够支持大部分中小企业内部自行开发项目。