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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云防火墙,500元 1000GB
简介:

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启动用户需要有执行权限):

sentinel notification-script mymaster /redis/script/notify.py

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

#!/bin/python

from sendmail import send_mail
import sys

event_type = sys.argv[1]
event_desc = sys.argv[2]
mail_content = event_type + ":" + event_desc

send_mail("xxxx@qq.com",
 ["xxxxx@cmbc.com.cn","xxxx@gmail.com"],
 "Redis Sentinel Event Notification Mail",
 mail_content,
 cc=["xxx@gmail.com","xxx@139.com"],
 bcc=["xxxx@qq.com"]
 )

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

附sendmail模块代码:

import smtplib
import os
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import formatdate
from email import Encoders
from email.message import Message
import datetime

def send_mail(fromPerson,toPerson, subject="", text="",files=[], cc=[], bcc=[]):
 server = "smtp.qq.com"
 assert type(toPerson)==list
 assert type(files)==list
 assert type(cc)==list
 assert type(bcc)==list

 message = MIMEMultipart()
 message['From'] = fromPerson
 message['To'] = ', '.join(toPerson)
 message['Date'] = formatdate(localtime=True)
 message['Subject'] = subject
 message['Cc'] = ','.join(cc)
 message['Bcc'] = ','.join(bcc)
 message.attach(MIMEText(text))

 for f in files:
 part = MIMEApplication(open(f,"rb").read())
 part.add_header('Content-Disposition', 'attachment', filename=filename)
 message.attach(part)

 addresses = []
 for x in toPerson:
 addresses.append(x)
 for x in cc:
 addresses.append(x)
 for x in bcc:
 addresses.append(x)

 smtp = smtplib.SMTP_SSL(server)
 smtp.login("xxxx@qq.com","xxxx")
 smtp.sendmail(message['From'],addresses,message.as_string())
 smtp.close()

最佳实践:采用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配置文件中配置:

sentinel client-reconfig-script mymaster /redis/script/failover.sh

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

# The following arguments are passed to the script:
#
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#
# <state> is currently always "failover"
# <role> is either "leader" or "observer"
# 
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
# the old address of the master and the new address of the elected slave
# (now a master).

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

#!/bin/sh
_DEBUG="on"
DEBUGFILE=/tmp/sentinel_failover.log
VIP='192.168.2.120'
MASTERIP=${6}
MASK='24'
IFACE='eno33554960'
MYIP=$(ip -4 -o addr show dev ${IFACE}| grep -v secondary| awk '{split($4,a,"/");print a[1]}')

DEBUG () {
 if [ "$_DEBUG" = "on" ]; then
 echo `$@` >> ${DEBUGFILE}
 fi
}

set -e
DEBUG date 
DEBUG echo $@ 
DEBUG echo "Master: ${MASTERIP} My IP: ${MYIP}"
if [ ${MASTERIP} = ${MYIP} ]; then
 if [ $(ip addr show ${IFACE} | grep ${VIP} | wc -l) = 0 ]; then
 /sbin/ip addr add ${VIP}/${MASK} dev ${IFACE}
 DEBUG date
 DEBUG echo "/sbin/ip addr add ${VIP}/${MASK} dev ${IFACE}"
 DEBUG date
 DEBUG echo "IP Arp cleaning: /usr/sbin/arping -q -f -c 1 -A ${VIP} -I ${IFACE}" 
 /usr/sbin/arping -q -f -c 1 -A ${VIP} -I ${IFACE}
 DEBUG date 
 DEBUG echo "IP Failover finished!"
 fi
 exit 0
else
 if [ $(ip addr show ${IFACE} | grep ${VIP} | wc -l) != 0 ]; then
 /sbin/ip addr del ${VIP}/${MASK} dev ${IFACE}
 DEBUG echo "/sbin/ip addr del ${VIP}/${MASK} dev ${IFACE}"
 fi
 exit 0
fi
exit 1

最早这样的用法是一个日本人写的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配置文件如下:

sentinel client-reconfig-script mymaster /redis/script/rdbctl.sh

rdbctl.sh源代码:

#!/bin/bash

_DEBUG="on"
DEBUGFILE="/smsred/redis-3.0.4/log/sentinel_failover.log"
MASTERIP=${6}
MASTERPORT=${7}
SLAVEIP=${4}
SLAVEPORT=${5}
MASK='24'
IFACE='bond0'
MYIP=$(ip -4 -o addr show dev ${IFACE}| grep -v secondary| awk '{split($4,a,"/");print a[1]}')


DEBUG () {
 if [ "$_DEBUG" = "on" ]; then
 echo `$@` >> ${DEBUGFILE}
 fi
}



set -e
DEBUG date
DEBUG echo $@ 
DEBUG echo "===Begin Failover==="
#If Master

if [ ${MASTERIP} = ${MYIP} ]; then
 #Disable RDB
 redis-cli -h ${MYIP} -p ${MASTERPORT} -a c1m2b3c4 config set save ""
 DEBUG echo ${MYIP}
 DEBUG echo "Disable Master RDB:" ${MYIP} ${MASTERPORT}
 DEBUG echo "===End Failover==="
 exit 0

#Or Slave
else
 echo "test5" >> $DEBUGFILE
 redis-cli -h ${MYIP} -p ${SLAVEPORT} -a c1m2b3c4 config set save "900 1 300 10 60 100000000"
 DEBUG echo ${MYIP}
 DEBUG echo "Enable Slave RDB:" ${MYIP} ${SLAVEPORT}
 DEBUG echo "===End Failover==="
 exit 0
fi

exit 1

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

11.1.5.4 Sentinel最大连接数

1. 问题描述

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

jedis.exceptions.JedisDataException: ERR max number of clients reached

此时应用创建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.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
# equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 60 seconds.

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

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

################################### LIMITS ####################################

# Set the max number of connected clients at the same time. By default
# this limit is set to 10000 clients, however if the Redis server is not
# able to configure the process file limit to allow for the specified limit
# the max number of allowed clients is set to the current file limit
# minus 32 (as Redis reserves a few file descriptors for internal uses).
#
# Once the limit is reached Redis will close all the new connections sending
# an error 'max number of clients reached'.
#
maxclients 10000

对于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抓包

tcpdump -pni bond0 -v "src port 26379 and ( tcp[tcpflags] & tcp-ack != 0 and ( (ip[2:2] - ((ip[0]&0xf)<<2) ) - ((tcp[12]&0xf0)>>2) ) == 0 ) "

7. 问题再后续

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

本文为《Redis开发运维实践指南》内容,该书作者为黄鹏程,已授权云栖社区转载。
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
人工智能 云计算 网络架构
阿里云引领智算集群网络架构的新一轮变革
11月8日~10日在江苏张家港召开的CCF ChinaNet(即中国网络大会)上,众多院士、教授和业界技术领袖齐聚一堂,畅谈网络未来的发展方向,聚焦智算集群网络的创新变革。
阿里云引领智算集群网络架构的新一轮变革
|
16天前
|
存储 负载均衡 监控
揭秘 Elasticsearch 集群架构,解锁大数据处理神器
Elasticsearch 是一个强大的分布式搜索和分析引擎,广泛应用于大数据处理、实时搜索和分析。本文深入探讨了 Elasticsearch 集群的架构和特性,包括高可用性和负载均衡,以及主节点、数据节点、协调节点和 Ingest 节点的角色和功能。
37 0
|
2月前
|
人工智能 运维 网络架构
阿里云引领智算集群网络架构的新一轮变革
11月8日至10日,CCF ChinaNet(中国网络大会)在江苏张家港召开,众多院士、教授和技术领袖共聚一堂,探讨网络未来发展方向。阿里云研发副总裁蔡德忠发表主题演讲,展望智算技术发展趋势,提出智算网络架构变革的新思路,发布高通量以太网协议和ENode+超节点系统规划,引起广泛关注。阿里云HPN7.0引领智算以太网生态蓬勃发展,成为业界标杆。未来,X10规模的智算集群将面临新的挑战,Ethernet将成为主流方案,推动Scale up与Scale out的融合架构,提升整体系统性能。
|
2月前
|
存储 缓存 NoSQL
【赵渝强老师】Memcached集群的架构
Memcached 是一个高性能的分布式内存对象缓存系统,通过在内存中维护一个巨大的 Hash 表来存储各种格式的数据,如图像、视频、文件及数据库检索结果等。它主要用于减轻数据库压力,提高网站系统的性能。Memcached 不支持数据持久化,因此仅作为缓存技术使用。其数据分布式存储由客户端应用程序实现,而非服务端。
【赵渝强老师】Memcached集群的架构
|
2月前
|
调度 Docker 容器
【赵渝强老师】Docker Swarm集群的体系架构
Docker Swarm自1.12.0版本起集成至Docker引擎,无需单独安装。它内置服务发现功能,支持跨多服务器或宿主机创建容器,形成集群提供服务。相比之下,Docker Compose仅限于单个宿主机。Docker Swarm采用主从架构,Swarm Manager负责管理和调度集群中的容器资源,用户通过其接口发送指令,Swarm Node根据指令创建容器运行应用。
|
3月前
|
运维 Linux Apache
,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具
【10月更文挑战第7天】随着云计算和容器化技术的发展,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具,通过定义资源状态和关系,确保系统始终处于期望配置状态。本文介绍Puppet的基本概念、安装配置及使用示例,帮助读者快速掌握Puppet,实现高效自动化运维。
70 4
|
2天前
|
人工智能 运维 监控
AI辅助的运维流程自动化:实现智能化管理的新篇章
AI辅助的运维流程自动化:实现智能化管理的新篇章
37 22
|
2月前
|
机器学习/深度学习 运维 监控
智能化运维:从自动化到AIOps的演进之路####
本文深入探讨了IT运维领域如何由传统手工操作逐步迈向高度自动化,并进一步向智能化运维(AIOps)转型的过程。不同于常规摘要仅概述内容要点,本摘要将直接引入一个核心观点:随着云计算、大数据及人工智能技术的飞速发展,智能化运维已成为提升企业IT系统稳定性与效率的关键驱动力。文章详细阐述了自动化工具的应用现状、面临的挑战以及AIOps如何通过预测性分析和智能决策支持,实现运维工作的质变,引领读者思考未来运维模式的发展趋势。 ####
|
2月前
|
机器学习/深度学习 数据采集 人工智能
智能化运维:从自动化到AIOps的演进与实践####
本文探讨了智能运维(AIOps)的崛起背景,深入分析了其核心概念、关键技术、应用场景及面临的挑战,并对比了传统IT运维模式,揭示了AIOps如何引领运维管理向更高效、智能的方向迈进。通过实际案例分析,展示了AIOps在不同行业中的应用成效,为读者提供了对未来智能运维趋势的洞察与思考。 ####
96 1
|
2月前
|
机器学习/深度学习 数据采集 人工智能
智能运维:从自动化到AIOps的演进与实践####
本文探讨了智能运维(AIOps)的兴起背景、核心组件及其在现代IT运维中的应用。通过对比传统运维模式,阐述了AIOps如何利用机器学习、大数据分析等技术,实现故障预测、根因分析、自动化修复等功能,从而提升系统稳定性和运维效率。文章还深入分析了实施AIOps面临的挑战与解决方案,并展望了其未来发展趋势。 ####