在本博客前面的文章给出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 # # 绑定本机IP bind 182.0.0.10 # # 后台守护进程运行 daemonize yes # # 存放快照(数据日志文件的目录)dump.rdb dir /opt/redis-5.0.7/data # # 1主两从架构里,至少一个有个从服务器在线且在10秒以内延迟,主redis才能对外提供写服务器,否则客户端无法写 min-replicas-to-write 1 min-replicas-max-lag 10 # 这里也需设置,因为当该master挂了再重启,变成replica后,需要密码去认证新的master masterauth foo123 # # 为master设置认证密码 requirepass foo123 # # 按默认 # # 按默认
”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 # # 绑定本机IP bind 182.0.0.11 # 另外一台从的IP为182.0.0.12 # # 后台守护进程运行 daemonize yes # # 存放快照(数据日志文件的目录)dump.rdb dir /opt/redis-5.0.7/data # # 告诉从服务器主服务器的认证密码以及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 # # 从redis需要密码认证 requirepass foo123 # # 按默认 # # 按默认
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 redisfrom 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 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)> In [23 ]: master_rd.set ('redis-HA' ,'good' ) Out[23 ]: True In [24 ]: master_rd.get('redis-HA' ) Out[24 ]: b'good' 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 In [43 ]: st.discover_master('mymaster' ) Out[43 ]: ('182.0.0.11' , 6379 ) 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 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' 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] In [3 ]: from django.core.cache import cache,caches In [4 ]: cache.set ('name' ,'fofo' ) Out[4 ]: True In [5 ]: cache.get('name' ) Out[5 ]: 'fofo' 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集群模式,个人认为,目前该方案足够支持大部分中小企业内部自行开发项目。