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
相关文章
|
13天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
10天前
|
运维 监控
构建高效运维体系:从理论到实践
在当今快速发展的信息化时代,高效的运维体系是保障企业信息系统稳定运行的关键。本文旨在探讨如何构建一个高效、可靠的运维体系,通过分析当前运维面临的挑战,提出相应的解决策略,并结合实际案例,展示这些策略的实施效果。文章首先介绍了高效运维的重要性,接着分析了运维过程中常见的问题,然后详细阐述了构建高效运维体系的策略和步骤,最后通过一个实际案例来验证这些策略的有效性。
|
10天前
|
机器学习/深度学习 数据采集 人工智能
智能运维:从自动化到AIOps的演进与实践####
本文探讨了智能运维(AIOps)的兴起背景、核心组件及其在现代IT运维中的应用。通过对比传统运维模式,阐述了AIOps如何利用机器学习、大数据分析等技术,实现故障预测、根因分析、自动化修复等功能,从而提升系统稳定性和运维效率。文章还深入分析了实施AIOps面临的挑战与解决方案,并展望了其未来发展趋势。 ####
|
18天前
|
人工智能 运维 监控
构建高效运维体系:理论与实践的深度融合####
本文旨在探讨高效IT运维体系的构建策略,通过理论框架与实际案例并重的方式,深入剖析了现代企业面临的运维挑战。文章开篇概述了当前运维领域的新趋势,包括自动化、智能化及DevOps文化的兴起,随后详细阐述了如何将这些先进理念融入日常运维管理中,形成一套既灵活又稳定的运维机制。特别地,文中强调了数据驱动决策的重要性,以及在快速迭代的技术环境中保持持续学习与适应的必要性。最终,通过对比分析几个典型企业的运维转型实例,提炼出可复制的成功模式,为读者提供具有实操性的指导建议。 ####
|
20天前
|
机器学习/深度学习 人工智能 运维
智能运维:AIOps在大型系统运维中的实践与挑战
【10月更文挑战第28天】随着云计算、大数据和人工智能的发展,AIOps(人工智能运维)应运而生,旨在通过算法和机器学习提高运维效率和质量。本文探讨了AIOps在大型系统运维中的实践与挑战,包括数据质量、模型选择和团队协作等方面,并通过一个异常检测案例展示了其应用。尽管面临挑战,AIOps仍有望成为未来运维的重要方向。
48 5
|
17天前
|
运维 负载均衡 Ubuntu
自动化运维的利器:Ansible入门与实践
【10月更文挑战第31天】在当今快速发展的信息技术时代,高效的运维管理成为企业稳定运行的关键。本文将引导读者了解自动化运维工具Ansible的基础概念、安装步骤、基本使用,以及如何通过实际案例掌握其核心功能,从而提升工作效率和系统稳定性。
|
18天前
|
运维 资源调度 监控
提升运维效率的关键技术与实践
在当今快速发展的信息技术时代,运维工作面临着前所未有的挑战和机遇。本文旨在探讨如何通过采用先进的技术和实施最佳实践来提高IT运维的效率和效果。我们将深入分析自动化工具、监控策略、灾难恢复计划以及持续集成/持续部署(CI/CD)等关键领域,展示它们如何协同工作以优化运维流程。此外,文章还将提供一些实际案例研究,帮助读者更好地理解这些概念的应用。无论是对于初创公司还是大型企业,掌握这些技术都将是提升竞争力的关键。
|
27天前
|
运维 应用服务中间件 持续交付
自动化运维的利器:Ansible入门与实践
【10月更文挑战第21天】在现代IT基础设施的管理中,自动化运维已成为提升效率、降低错误率的关键。Ansible,作为一种简单而强大的自动化工具,正被广泛应用于配置管理、应用部署和任务自动化等领域。本文将引导你了解Ansible的基本概念,通过实际案例展示如何利用Ansible简化日常运维工作,并探讨其在现代IT运维中的应用价值。无论你是新手还是有经验的系统管理员,这篇文章都将为你开启Ansible的高效之旅提供指导。
|
1月前
|
运维 自然语言处理 开发者
作为一名运维人员,使用通义灵码个人版处理日常工作中的代码相关任务,极大地提升了我的工作效率。以下是我使用通义灵码的具体实践场景、效果和心得,以及相应的截图。
作为一名运维人员,我使用通义灵码处理日常工作中的代码任务,效率提升了30%。通义灵码帮助我快速理解复杂代码、生成准确的代码注释,并能从自然语言生成代码示例,大幅减少了代码编写和理解的时间。
57 3
|
1月前
|
运维 Prometheus 监控
运维之眼:监控的艺术与实践
在信息技术飞速发展的今天,运维监控已成为保障系统稳定运行的关键。本文将探讨运维监控的重要性,介绍常用的监控工具和方法,并通过实际案例分析,展示如何有效地实施监控策略,以确保系统的高可用性和性能。