yield-bytes

沉淀、分享与无限进步

基于Sentinel模式部署高可用Redis

  在本博客前面的文章给出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 the redis-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
2
3
4
5
6
7
8
9
10
11
       +----+
| M1 |
| S1 |
+----+
|
+----+ | +----+
| R2 |----+----| R3 |
| S2 | | S3 |
+----+ +----+

Configuration: quorum = 2

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
2
3
4
5
6
7
8
9
10
11
         +----+
| M1 |
| S1 | <- C1 (writes will be lost)
+----+
|
/
/
+------+ | +----+
| [M2] |----+----| R3 |
| S2 | | S3 |
+------+ +----+

  以上情况可通过以下两个配置实现数据丢失最小化。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
2
min-replicas-to-write 1
min-replicas-max-lag 10

3、一主两从的redis架构配置

  第2部分的sentinel 2 高可用的前提是基于1主2两从的架构基础上实现的,如下架构,故首先得让一主两从的redis小集群跑起来

1
2
3
4
5
6
7
8
9
10
11
       +----+
| M1 |
| S1 |
+----+
|
+----+ | +----+
| R2 |----+----| R3 |
| S2 | | S3 |
+----+ +----+

Configuration: quorum = 2

  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
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
################################## NETWORK 
# 绑定本机IP
bind 182.0.0.11
# 另外一台从的IP为182.0.0.12
################################# GENERAL

# 后台守护进程运行
daemonize yes

################################ SNAPSHOTTING
# 存放快照(数据日志文件的目录)dump.rdb
dir /opt/redis-5.0.7/data

################################# REPLICATION
# 告诉从服务器主服务器的认证密码以及IP端口号,新版redis不再使用slave争议词,原版是slaveof
replicaof 182.0.0.10 6379
masterauth foo123

# 1主两从架构里,至少一个有个从服务器在线且在10秒以内延迟,主redis才能对外提供写服务器,否则客户端无法写
min-replicas-to-write 1
min-replicas-max-lag 10
################################## SECURITY
# 从redis需要密码认证
requirepass foo123

################################### CLIENTS
# 按默认
############################## MEMORY MANAGEMENT
# 按默认
3.2 启动和测试主从

启动所有主从redis-server,后台守护进程运行

1
[root@p1 opt]# redis-server /opt/redis-5.0.7/redis.conf 

注意:
如果只启动master,从服务器还未启动,提示没有足够的从服务器在线,无法对外提供写服务。
1
2
127.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一个key
1
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> INFO Replication
# Replication
role:master
connected_slaves:2
min_slaves_good_slaves:2
slave0:ip=182.0.0.11,port=6379,state=online,offset=1958,lag=0
slave1:ip=182.0.0.12,port=6379,state=online,offset=1958,lag=1
master_replid:1f69dd42ecea58d245859fd716c4eaee83a6e753
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1958
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1958

注:INFO [section]命令可以查看多个部分信息,也可指定查看某个section的信息
以上说明1主两从redis架构已经构建,该模式下,只有master才能写数据,replica只能get数据。如果尝试在replica上写数据,将提示readonly:

1
2
127.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
15
bind 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启动命令,需要带上选项 ——sentinel

1
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
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
127.0.0.1:26379> SENTINEL slaves mymaster
1) 1) "name"
2) "182.0.0.11:6379"
3) "ip"
4) "182.0.0.11"
5) "port"
6) "6379"
7) "runid"
8) "b7a9000c355584be472fe2409406b772b755b0ed"
9) "flags"
10) "slave"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "430"
19) "last-ping-reply"
20) "430"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "9197"
25) "role-reported"
26) "slave"
27) "role-reported-time"
28) "169854"
29) "master-link-down-time"
30) "0"
31) "master-link-status"
32) "ok"
33) "master-host"
34) "182.0.0.10"
35) "master-port"
36) "6379"
37) "slave-priority"
38) "100"
39) "slave-repl-offset"
40) "24585"
2) 1) "name"
2) "182.0.0.12:6379"
3) "ip"
4) "182.0.0.12.5"
5) "port"
6) "6379"
7) "runid"
8) "39edb70865b99916b1fd0013740e457135fe42e4"
9) "flags"
10) "slave"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "430"
19) "last-ping-reply"
20) "430"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "9197"
25) "role-reported"
26) "slave"
27) "role-reported-time"
28) "169855"
29) "master-link-down-time"
30) "0"
31) "master-link-status"
32) "ok"
33) "master-host"
34) "182.0.0.10"
35) "master-port"
36) "6379"
37) "slave-priority"
38) "100"
39) "slave-repl-offset"
40) "24585"

高可用测试:

kill 掉master的redis-server进程和redis-sentinel进程,模拟master服务器宕机情况:

1
2
3
4
5
6
[root@nn redis-5.0.7]# ps -ef |grep redis
root 7385 1 0 * 00:02:35 redis-server 0.0.0.0:6379
root 20367 20127 0 * 00:00:00 redis-cli -a foo123
root 23760 1 0 * 00:00:03 redis-sentinel 0.0.0.0:26379 [sentinel]
[root@nn redis-5.0.7]# kill -9 7385
[root@nn redis-5.0.7]# kill -9 23760

在replica 1查看目前是否已转移到:可以看到两个replica已经选举出新的master

1
2
3
4
5
6
7
8
9
[root@localhost 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.11"
5) "port"
6) "6379"

也可通过redis-cli上查看:目前182.0.0.11已成为新的master,有个正常连接的replica

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> info Replication
# Replication
role:master
connected_slaves:1
min_slaves_good_slaves:1
slave0:ip=182.0.0.12,port=6379,state=online,offset=146353,lag=1
master_replid:4c1679086e6e4bb0bdd4956bcf979a6b964a8503
master_replid2:ff0d944d22232a7b3489a8544c3109350aac6cd5
master_repl_offset:146494
second_repl_offset:145062
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:130700
repl_backlog_histlen:15795

在新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
2
3
4
5
6
7
8
9
10
11
12
13
14
# Replication
role:master
connected_slaves:2
min_slaves_good_slaves:2
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
master_replid:4c1679086e6e4bb0bdd4956bcf979a6b964a8503
master_replid2:ff0d944d22232a7b3489a8544c3109350aac6cd5
master_repl_offset:211840
second_repl_offset:145062
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:130700
repl_backlog_histlen:81141

以上完成redis 1主2从的高可用配置和测试,下面将在实际项目中引入。

5、在python项目或者django的项目引入sentinel集群

5.1 python项目连接sentinel集群
1
2
3
4
5
6
7
8
9
import redis
from redis.sentinel import Sentinel
In [4]: st=Sentinel([('182.0.0.10',26379),('182.0.0.11',26379),('182.0.0.12',26379)])

In [7]: st.discover_master('mymaster')
Out[7]: ('182.0.0.10', 6379)
In [8]: st.discover_slaves('mymaster')
Out[8]: [('182.0.0.11', 6379), ('182.0.0.12', 6379)]

查看用法

1
2
3
4
5
6
7
8
9
10
11
In [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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 注意别漏了sentinel集群设了密码
In [9]: master_rd=st.master_for(service_name='mymaster',password='foo123',db=0)
In [10]: replica_rd=st.slave_for(service_name='mymaster',password='foo123',db=0)

In [19]: master_rd
Out[19]: Redis<SentinelConnectionPool<service=mymaster(master)>

# 向master写数据,不仅实现高可用,而且还实现读写分离
In [23]: master_rd.set('redis-HA','good')
Out[23]: True

In [24]: master_rd.get('redis-HA')
Out[24]: b'good'

# 如果尝试向replica写数据则出错提示:replica只能读数据
In [25]: replica_rd.set('foo','HA')
ReadOnlyError: You can't write against a read only replica.

# 从replica读数据,不仅实现高可用,而且还实现读写分离
In [28]: replica_rd.get('redis-HA')
Out[28]: b'good'

在服务器上把当前10节点master kill掉,再看看python取值是否被影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 经过3秒左右,再次获取最新的master可以看到已转移到11节点上,所以这3秒时间实际也是丢失数据的时间窗口
In [43]: st.discover_master('mymaster') Out[43]: ('182.0.0.11', 6379)

# 当前集群仅有一个replica
In [44]: st.discover_slaves('mymaster')
Out[44]: [('182.0.0.12', 6379)]

# 主从切换后,不影响程序获取原有数据
In [45]: master_rd.get('redis-HA')
Out[45]: b'good'

In [47]: master_rd.set('bar','test')
Out[47]: True

In [48]: replica_rd.get('bar')
Out[48]: b'test'
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
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
# 将redis设为django缓存
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': ['redis://182.0.0.10:6379/0'], //连接redis url
'KEY_PREFIX': 'dj', //前缀名
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'CONNECTTON_POOL_KWARGS': {
'max_connections': 128,
},
'PASSWORD': 'foo123',
},
},
'hp': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': ['redis://182.0.0.10:6379/1'],
'KEY_PREFIX': 'dj:bi',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'CONNECTTON_POOL_KWARGS': {
'max_connections': 128,
},
'PASSWORD': 'foo123',
},
}
}
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
#django自身运行上下文使用默认数据库redis缓存
SESSION_CACHE_ALIAS = 'default'

启动django shell测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@nn hp]# python manage.py shell

In [3]: from django.core.cache import cache,caches
In [4]: cache.set('name','fofo')
Out[4]: True

# 使用default 库
In [5]: cache.get('name')
Out[5]: 'fofo'

# 使用hp 库
In [11]: caches['hp'].set('code',133)
Out[11]: True

In [12]: caches['hp'].get('code')
Out[12]: 133

注意:如果使用cache方法,则默认使用default数据库,若需要使用其他名称的数据,需要用caches方法,并通过settings里面redis数据库名称来索引

在redis服务器查看default库相应的key:

1
2
3
4
127.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号库的key
1
2
3
4
5
6
7
8
127.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
2
3
4
5
6
7
8
In [13]: from django_redis import get_redis_connection
In [14]: conn = get_redis_connection()
In [15]: conn.set('fb',12)
Out[15]: True

In [16]: conn.get('fb')
Out[16]: b'12'

5.2.2 django项目的cache引入sentinel模式

该模式需要安装新的django插件,github地址

1
2
pip install django-redis-sentinel
#或者pip install django-redis-sentinel-redux 0.2.0

这个插件其实封装了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
2
3
4
5
6
7
8
9
10
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "mymaster/188.0.0.10:26379,188.0.0.11:26379,188.0.0.12:26379/0"
"OPTIONS": {
"PASSWORD": 'foo123',
"CLIENT_CLASS": "django_redis_sentinel.SentinelClient",
}
}
}

该 django-redis-sentinel的插件代码实现不到120行,主要看看connect方法,写数据时用sentinel.discover_master(master_name)获取master去写,读数据时,随机取一个replica 读数据random.choice(sentinel.discover_slaves(master_name))

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
def connect(self, write=True, SentinelClass=None):
"""
Creates a redis connection with connection pool.
"""
if SentinelClass is None:
SentinelClass = Sentinel
self.log.debug("connect called: write=%s", write)
master_name, sentinel_hosts, db = self.parse_connection_string(self._connection_string)

sentinel_timeout = self._options.get('SENTINEL_TIMEOUT', 1)
password = self._options.get('PASSWORD', None)
sentinel = SentinelClass(sentinel_hosts,
socket_timeout=sentinel_timeout,
password=password)

if write:
host, port = sentinel.discover_master(master_name)
else:
try:
host, port = random.choice(sentinel.discover_slaves(master_name))
except IndexError:
self.log.debug("no slaves are available. using master for read.")
host, port = sentinel.discover_master(master_name)

if password:
connection_url = "redis://:%s@%s:%s/%s" % (password, host, port, db)
else:
connection_url = "redis://%s:%s/%s" % (host, port, db)
return self.connection_factory.connect(connection_url)

注意:如果使用sentinel这个插件,那么需使用django_redis的get_redis_connection,而不是使用django.core.cache的cache

1
2
3
4
5
6
7
8
In [1]: from django_redis import get_redis_connection   
In [2]: conn = get_redis_connection()
In [3]: conn.set('apple','airpods')
Out[3]: True

In [4]: conn.get('apple')
Out[4]: b'airpods'

注意: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
2
In [8]: conn.sentinel_master('mymaster') 
ResponseError: unknown command `SENTINEL`, with args beginning with: `MASTER`, `mymaster`,

测试以上出错信息也简单:连接redis-server的6379端口client

1
2
3
[root@localhost redis-5.0.7]# redis-cli -a foo123 
127.0.0.1:6379> sentinel master mymaster
(error) ERR unknown command `sentinel`, with args beginning with: `master`, `mymaster`,

由于sentinel集群监听的是26379端口来执行有关查询命令(端口号在sentinel.conf文件配置),而get_redis_connection()使用的是6379,用此报错。

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost redis-5.0.7]# redis-cli -a foo123 -p 26379
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "188.0.0.10"
5) "port"
6) "6379"
7) "runid"
8) "5537ec765629633406942061f5993e475c42df8e"
9) "flags"
10) "master"

解决办法有三种种:
第一种:修改redis源码,改get_redis_connection,也不难
第二种:sentinel监听端口设为默认6379,redis-server设为其他端口号即可
第三种:不需要使用django redis插件,直接用redis原生库自行封装相关sentinel的操作

  综上完成基于sentinel模式的redisHA配置以及详细解释了在python项目和django项目中如何使用sentinel集群模式,个人认为,目前该方案足够支持大部分中小企业内部自行开发项目。