Redis开发运维实践高可用和集群架构与实践(五)

11.1.5 其他问题

11.1.5.1 只读性

主从复制架构下,默认Slave是只读的,如果写入则会报错:

127.0.0.1:6379> set foo bar
(error) READONLY You can't write against a read only slave.

注意这个行为是可以修改的,虽然这样的修改没有意义:

127.0.0.1:6379> CONFIG SET slave-read-only no
OK
127.0.0.1:6379> set foo bar
OK

11.1.5.1 事件通知

在sentinel中,如果出现warning以上级别的事件发生, 是可以通过如下配置进行脚本调用的(对于该脚本redis启动用户需要有执行权限):


比如说,我们希望在发生这些事件的时候进行邮件通知,那么,notify.py就是一个触发邮件调用的东东,传入第一个参数为事件类型,第二个参数为事件信息:


有两个注意事项: 1) 这个时候如果集群发生了切换会产生很多事件,此脚本是在每一个事件发生时调用一次,那么你将短时间收到很多封邮件,加上很多的邮件网关是不允许在一个短时间内发送太多的邮件的,因此这个仅仅是一个示例,并不具备实际上的作用。 2) 一般我们会采用多个sentinel,只需在一个sentinel上配置即可,否则将同一个消息会被多个sentinel多次处理。

附sendmail模块代码:


最佳实践:采用ELK(Elastic+Logstash+Kibana)进行日志收集告警(ElastAlert用起来不错),不启用这个事件通知功能。如果你的环境中没有ELK,或者启动一个Tcp Server进程,notify脚本将事件通过tcp方式吐给这个server,该Server收集一批事件后再做诸如发邮件的处理。

11.1.5.2 虚拟IP切换

在sentinel进行切换时还会自动调用一个脚本(如果设置的话),做一些自动化操作,比如如果我们需要一个虚拟IP永远飘在Master上(这个VIP可不是被应用用来连接redis 的,用过的人都知道连接redis sentinel并不依赖于VIP的),那么可以在sentinel配置文件中配置:


在发生主从切换,Master发生变化时,该脚本会被sentinel进行调用,调用的参数如其配置文件所描述的:


因此,我们可以在failover.sh中进行判断,如果该脚本所运行的主机IP等于新的Master IP,那么将VIP加上,如果不等于,则该机器为Slave,就去掉VIP。通过这种方式进行VIP的切换:


最早这样的用法是一个日本人写的blog,请参见:http://blog.youyo.info/blog/2014/05/24/redis-cluster/

11.1.5.3 持久化动态修改

其实相对于VIP的切换,动态修改持久化则是比较常见的一个需求,一般在一主多从多Sentinel的HA环境中,为了性能常常在Master上关闭持久化,而在Slave上开启持久化,但是如果发生切换就必须有人工干预才能实现这个功能。可以利用client-reconfig-script自动化该进程,无需人工守护,我们就以RDB的动态控制为例: Sentinel配置文件如下:


rdbctl.sh源代码:


原理和VIP切换一节基本一致,不再赘述。

11.1.5.4 Sentinel最大连接数

1. 问题描述

某准生产系统,测试运行一段时间后程序和命令行工具连接sentinel均报错,报错信息为:


此时应用创建redis新连接由于sentinel已经无法响应而无法找到master的IP与端口,因此无法连接redis,并且此时如果发生redis宕机亦无法进行生产切换。

2. 问题初步排查过程

首先,通过netstat对sentinel所监听端口26379进行连接数统计,此时连接则报错。如下图:


通过sentinel服务器端统计发现,redis sentinel 的连接中大部分都是来自于两台非同网段(中间有防火墙)的应用服务器连接(均为Established状态),并且来自其的连接也大约半个小时后稳步增加一次,而同网段的应用服务器连接sentinel的连接数基本保持一致。排除了应用的特殊性(采用的jedis版本和封装的工具类都是一样的)后,初步判断此问题与网络有关,更详细的说是连接数增加与防火墙切断连接后的重连有关。

3. 问题查证过程

此问题分为两个子问题: 1) 防火墙将TCP连接设置为无效时sentinel服务器为何没有断开连接,保持Established状态? 2) 为何连接数还会不断增加?

对于问题1) ,TCP在三次握手建立连接时OS会启动一个Timer来进行倒计时,经过一个设定的时间(这个时间建立socket的程序可以设置,如果没有设置则采用OS的参数tcp_keepalive_time,这个参数默认为7200s,即2小时)后这个连接还是没有数据传输,它就会以一定间隔(程序可以设定,如果没有设置则采用OS的参数tcp_keepalive_intvl,默认为75s)发出N(程序可以设定,如果没有设置则采用OS的参数tcp_keepalive_probes,默认为9次)次Keep Alive包。TCP连接就是通过上述的过程,在没有流量时是通过发送TCP Keep-Alive数据包,然后对方回应TCP Keep-Alive ACK来确定信道是否还在真实连接。通过查看Sentinel源代码,其默认是不开启Keepalive的(而jedis默认是开启的),并且默认对于不活动的连接也不会主动关闭的(timeout默认为0)。

对于防火墙,通过翻阅防火墙技术资料(详见下列描述,摘自:《Junos Enterprise Switching: A Practical Guide to Junos Switches and Certification》),我司采用的Juniper防火墙对于没有流量的TCP连接默认是30分钟,30分钟内没有流量就会断掉链路,而不会发送TCP Reset,同时在防火墙策略上并没有开长连接,使用的即为此默认设置。


因此在防火墙每半个小时将连接置为无效时,sentinel同时又禁止了Keepalive(因为默认设置Keepalive为0,即disable发送Keepalive包)。应用服务器的jedis虽然开启了keepalive,但是它发送的keepalive包由于防火墙已经将此链路标记为无效,而无法发送到sentinel端,而且jedis由于采用了OS默认参数,因此需要等待tcp_keepalive_time(2小时)后才启动发送Keep Alive包进行探活的,在tcp_keepalive_time+tcp_keepalive_intvl*tcp_keepalive_probes=7895s=131.58分钟后,jedis端才会认定这个连接断掉而清理掉这个连接。简单的说就是jedis会在很长一段时间后才会发keepalive包,并且这个包也是发不到sentinel上的,而sentinel本身也不会发送keepalive包,所以从sentinel这端看连接一直存在,而从jedis那端看7895s之后就会清理一次连接。这也解释了为什么防火墙将TCP连接断开后,sentinel端的连接并没有释放。

对于问题2) ,翻阅jedis源代码,jedis通过连接sentinel并pubsub来监听集群事件,以确定是否发生了切换,并且拿到新的master 地址和端口。如果断开则会5秒后尝试重连(JedisSentinelPool.java)。


因此,这是导致连接数不断上升的原因。 综上,防火墙相对频繁的断开和服务器不断重连导致在一个相对较短的时间内连接骤增,造成到达sentinel最大连接数,sentinel 的最大连接数在redis.h中定义,为10000:


4. 问题解决过程

此系统由于访问关系与网段规划间的安全问题,必须跨越防火墙,因此试图从配置角度解决此问题。

首先,联系网络相关同事,进行网络变更,开启从应用服务器到sentinel的链路相对的长连接,即无流量超时而断开的时间设置为8小时。以此手段降低断开频率,以便缓解短时间内不断重试连接造成的sentinel连接增长。

然后,通过阅读redis源代码(net.c),发现,sentinel也采用了redis 所有参数设置(通过config.c的函数void loadServerConfigFromString(char *config))。因此,通过设置redis 的下列两个参数可以解决这个问题,第一个参数是TCP Keepalive参数,此参数默认为0,也就是不发送keepalive。也就是改变OS默认的tcp_keepalive_time参数(在Unix C的socket编程中TCP_KEEPIDLE参数对应OS 的tcp_keepalive_time参数)。


该参数的官方解释为:


我们设置为tcp-keepalive 60,加快回收连接速度,从网络断开到连接清理时间缩短为60+75*9=12.25分钟。

同时,通过设置maxclients为65536,增大sentinel最大连接数,使得在上述12.25分钟即使有某种异常导致sentinel连接数增加也不至于到达最大限制。此参数的官方解释为:


对于redis 的timeout参数,由于启用这个参数有程序微小开销(会调用redis.c中的int clientsCronHandleTimeout(redisClient *c, mstime_t now_ms)),决定保持默认为0,而通过上述参数使用OS进行连接断开。

5. 问题解决结果

通过开发、网络和数据库团队的协同努力,配置上述参数和修改防火墙策略后,手动增加sentinel进程,超过原默认最大连接数10000后sentinel可以正常访问操作,并且通过tcpdump进行抓包,在指定时间内(1分钟),就有KeepAlive包对每个sentinel TCP连接进行探活,经过观察sentinel连接稳定,再未出现短时间内暴涨的情况。

6. 问题后续

在redis中默认不开启keepalive就是为了尽可能减小网络负载,榨干网络性能,尽可能达到redis的。在后续的程序运行中,如果发现网络是瓶颈时(在相当长的一段时间内不会),可以加大sentinel的keepalive参数,减小keepalive数据包的传输,这个修改是不影响redis对外服务的。

参考文档: http://www.tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/

附录:如何用TCPDUMP进行keep alive抓包


7. 问题再后续

我们后来在这个应用上发现一旦网络有抖动,sentinel的连接增加就回大幅度增加,后来通过jmap查看sentinelpool的实例竟然多达200多个,也就是说这个就是程序的问题,在sentinelpool上不应该多次实例化,而是采用已有连接进行重连。

时间: 2024-09-24 15:31:15

Redis开发运维实践高可用和集群架构与实践(五)的相关文章

Redis开发运维实践高可用和集群架构与实践(四)

11.1.4 高可用和异常测试 11.1.4.1 测试环境介绍 sentinel的消息可以通过sentinel日志(/redis/log/sentinel.log)以及sentinel:hello订阅此频道进行查看. 11.1.4.2 手动切换测试 集群情况,2.128为主 发起主动切换: 查看sentinel日志: 在2.129上看,集群已经切换过来: 11.1.4.3 主实例宕测试 接上,此时master为2.129,找出redis实例的pid,然后kill: 此时查看sentinel日志:

Redis开发运维实践高可用和集群架构与实践(二)

11.1.2 环境搭建 11.1.2.1 部署架构 部署架构上采用三台机器,一个Master接受写请求,两个Slave进行数据同步,三台机器上都部署sentinel(一般为奇数个,因为需要绝大部分进行投票才能failover).(官方示例)具体架构如下图: 注意:如果有条件可以将sentinel多部署几个在客户端所在的应用服务器上,而不是与从节点部署在一起,这样避免整机宕机后sentinel和slave都减少而导致的切换选举sentinel无法超过半数. 11.1.2.2 网络规划 redis高

Redis开发运维实践高可用和集群架构与实践(三)

11.1.3 维护操作 11.1.3.1 完整启动 supervisord -c /redis/conf/redis-supervisord.conf 会自动拉起本机的redis和sentinel 11.1.3.2 启停redis supervisorctl -c /redis/conf/redis-supervisord.conf start redis supervisorctl -c /redis/conf/redis-supervisord.conf stop redis supervi

Redis开发运维实践高可用和集群架构与实践(一)

11.1 主从复制-sentinel架构 11.1.1 高可用原理 11.1.1.1 发现原理 Sentinel发现分为发现从服务器和发现其他sentinel服务两类: Sentinel实例可以通过询问主实例来获得所有从实例的信息 Sentinel进程可以通过发布与订阅来自动发现正在监视相同主实例的其他Sentinel,每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的 __sentinel__:hello 频道, 查找之前未出现过的 sentinel进程. 当一个 Sentin

Redis开发运维实践开发者设计规范之典型使用场景参考

4.6 典型使用场景参考 下面是Redis适用的一些场景: 1. 取最新 N 个数据的操作 比如典型的取你网站的最新文章,通过下面方式,我们可以将最新的 5000条评论的ID放在Redis的List集合中,并将超出集合部分从数据库获取. 使用LPUSH latest.comments命令,向 list集合中插入数据 插入完成后再用 LTRIM latest.comments 0 5000 命令使其永远只保存最近5000个 ID 然后我们在客户端获取某一页评论时可以用下面的逻辑 FUNCTION

Redis开发运维实践上线部署规划之内存规划

5.1 内存.CPU规划 一定要设置最大内存maxmemory参数,否则物理内存用爆了就会大量使用Swap,写RDB文件时的速度很慢.注意这个参数指的是info中的used_memory,在一些不利于jmalloc的时候,内存碎片会很大. 多留55%内存是最安全的.重写AOF文件和RDB文件的进程(即使不做持久化,复制到Slave的时候也要写RDB)会fork出一条新进程来,采用了操作系统的Copy-On-Write策略(子进程与父进程共享Page.如果父进程的Page-每页4K有修改,父进程自

Redis开发运维实践上线部署规划之服务器部署位置

5.3 服务器部署位置 尽可能把client和server部署在同一台机器上,比如都部署在app server,或者一个网段中,减少网络延迟对于redis的影响. 如果是同一台机器,又想榨干redis性能可以考虑采用UNIX domain sockets配置方式,配置方式如下 这样的配置方式在没有大量pipeline下会有一定性能提升,具体请参见http://redis.io/topics/benchmarks: 另外,对于混合部署即redis和应用部署在同一台服务器上,那么可能会出现如下的情况

Redis开发运维实践开发者设计规范之延迟考虑

4.5 延迟考虑 1. 尽可能使用批量操作: mget.hmget而不是get和hget,对于set也是如此. lpush向一个list一次性导入多个元素,而不用lset一个个添加 LRANGE 一次取出一个范围的元素,也不用LINDEX一个个取出 2. 尽可能的把redis和APP SERVER部署在一个网段甚至一台机器. 3. 对于数据量较大的集合,不要轻易进行删除操作,这样会阻塞服务器,一般采用重命名+批量删除的策略: 排序集合: # Rename the key newkey = "gc

Redis开发运维实践上线部署规划之网卡rps设置

5.2 网卡RPS设置 RPS就是让网卡使用多核CPU的.传统方法就是网卡多队列(RSS,需要硬件和驱动支持),RPS则是在系统层实现了分发和均衡.如果对redis网络处理能力要求高或者在生产上发现cpu0的,可以在OS层面打开这个内核功能. 设置脚本: #!/bin/bash # Enable RPS (Receive Packet Steering) rfc=32768 cc=$(grep -c processor /proc/cpuinfo) rsfe=$(echo $cc*$rfc |