熔断限流

熔断限流,第1张

限流: 原理是监控应用流量的QPS或并发线程数等指标,当达到指定阈值时对流量进行控制,避免系统被瞬时的流量高峰冲垮,保障应用高可用性。保护自身系统防止被外部调垮。

熔断: 调用远程服务,后端服务不可避免的会产生调用失败(超时或者异常),防止应用程序不断地尝试可能超时和失败的服务,能达到应用程序执行而不必等待下游服务修正错误服务。

降级: 是指牺牲非核心的业务功能,保证核心功能的稳定运行。在后台通过开关控制,降级部分非主流程的业务功能,减轻系统依赖和性能损耗,从而提升集群的整体吞吐率。

 Hystrix 的关注点在于以隔离和 熔断 为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。Hystrix 提供两种隔离策略: 线程池隔离(Bulkhead Pattern) 和 信号量隔离。

线程池隔离: 针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。好处是隔离度比较高,单独处理某个资源;代价就是线程上下文切换的 overhead 比较大,会让机器资源碎片化,特别是对低延时的调用有比较大的影响。

信号量隔离: 限制对某个资源调用的并发数,更为轻量,开销更小。但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。

Sentinel 和 Hystrix 的熔断降级功能本质上都是基于熔断器模式(Circuit Breaker Pattern)。Sentinel 与 Hystrix 都支持基于 失败比率(异常比率) 的熔断降级, 在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断 ,此时所有对该资源的调用都会被 block,直到过了指定的时间窗口后才启发性地恢复。 Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断 ,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。

Hystrix 和 Sentinel 的实时指标数据统计实现都是基于滑动窗口的。

Sentinel:轻量级和高性能, 可以针对不同的调用关系,以 不同的运行指标(如 QPS、并发调用数、系统负载等) 为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状。

熔断策略: 平均响应时间 (DEGRADE_GRADE_RT):时间窗口内接口调用RT超过一定时间,下一时间窗口自动熔断; 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO) :每秒调用时间异常比例超过一定阈值,自动熔断; 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。

流量控制(Flow Control),原理是监控应用流量的QPS或并发线程数等指标,当达到指定阈值时对流量进行控制,避免系统被瞬时的流量高峰冲垮,保障应用高可用性。

流量整形策略: 直接拒绝模式 :即超出的请求直接拒绝。

慢启动预热模式 :当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

匀速器模式: 利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。

常用限流算法:

①计数器限流算法

通过一个计数器,限制每一秒钟能够接收的请求数。周期内,超过阈值后的请求就会被全部拒绝。

②滑动窗口算法(sentinel使用)

滑动窗口算法是将时间周期分为N个小周期(窗口),分别记录每个小周期内访问次数,然后根据时间将窗口往前滑动,统计时间窗口内调用次数。

③漏桶限流算法

实现一个容器,容量固定,访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。实际上消息中间件就使用了漏桶限流的思想,不管生产者的请求量有多大, 消息的处理能力取决于消费者。

④令牌桶限流算法

令牌桶是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。对于每一个请求,都需要从令牌桶中获得一个令牌,如果没有获得令牌,则需要触发限流策略。

参照以下使用步骤了解

1下载启动jar包

__ithub上 sentinel下载jar包

2 可以通过jvm配置参数启动jar。

_ava -Dserverport=8849 -Dcspsentineldashboardserver=localhost:8849 -Dprojectname=sentinel-dashboard -jar sentinel-dashboardjar

_⒔缑

1、登录界面

_系锹颊撕琶苈刖_entinel

2、首页

3、实时监控:实时查看各个资源的QPS

4、簇点链路:查看>

你好,Redis哨兵

Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方式是无法接受的。可喜的是Redis从28开始正式提供了Redis Sentinel (哨兵)架构来解决这个问题,本章会对Redis Sentinel进行详细分析。

基本概念

名词 逻辑结构 物理结构 主节点 Redis主服务 一个独立的Redis进程 从节点 Redis从服务 一个独立的Redis进程Redis数据节点 主节点和从节点 主节点和从节点的进程 Sentinel节点 监控Redis数据节点 一个独立的Sentinel进程 Sentinel节点集合 若干个Sentinel节点的抽象组合 若干个Sentinel节点进程 Redis Sentinel Redis高可用方案Sentinel节点集合和Redis数据及诶单进程

主从复制的问题

Redis的主从复制模式可以将主节点的数据改变同步给从节点,这样从节点就可以起到两个作用:第一,作为主节点的一个备份,一旦主节点出了故障不可达的情况,从节点可以作为后备“顶”上来,并且保证数据尽量不丢失(主从复制是最终一致性)。 第二,从节点可以扩展主节点的读能力,一旦主节点不能支撑住大并发量的读 *** 作,从节点可以在一定程度上帮助主节点分担读压力。 但是主从复制也带来了以下问题: (1)一旦主节点出现故障,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预。 (2)主节点的写能力受到单机的限制。 (3)主节点的存储能力受到单机的限制。

高可用

Redis主从复制模式下,一旦主节点出现了故障不可达,需要人工干预进行故障转移无论对于Redis的应用方还是运维方都带来了很大的不便。对于应用方来说无法及时感知到主节点的变化,必然会造成一定的写数据丢失和读数据错误,甚至可能造成应用方服务不可用。对于Redis的运维方来说,整个故障转移的过程是需要人工来介入的,故障转移实时性和准确性上都无法得到保障,一个1主2从的Redis主从复制模式下的主节点出现故障后,是如何进行故障转移的。 1)主节点发生故障后,客户端(client)连接主节点失败,两个从节点与主节点连接失败造成复制中断。 2)如果主节点无法正常启动,需要选出一个从节点(slave-1),对其执行slaveof no one命令使其成为新的主节点。 3)原来的从节点(slave-1)成为新的主节点后,更新应用方的主节点信息,重新启动应用方 4)客户端命令另一个从节点(slave-2)去复制新的主节点(new-master) 5)待原来的主节点恢复后,让它去复制新的主节点。上述处理过程就可以认为整个服务或者架构的设计不是高可用的,因为整个故障转移的过程需要人为介入。考虑到这点,有些公司把上述流程自动化了,但是仍然存在如下问题:第一,断节点不可达的机制是否健全和标准。第二,如果有多个从节点,怎样保证只有一个被晋升为主节点。第三,通知客户端新的主节点机制是否足够强壮。Redis Sentinel正是用于解决这个问题。

Redis Sentinel的高可用性当主节点出现故障时, Redis Sentinel能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。Redis Sentinel是一个分布式架构,其中包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他Sentinel节点进行“协商”,当大多数Sentinel节点都认为主节点不可达时,它们会选举出一个Sentinel节点来完成第六章sentinelmd2020/3/132 / 4自动故障转移的工作,同时会将这个变化实时通知给Redis应用方。整个过程完全是自动的,不需要人工来介入,所以这套方案很有效地解决了Redis的高可用问题。Redis Sentinel与Redis主从复制模式只是多了若于Sentinel节点,所以Redis Sentinel并没有针对Redis节点做了特殊处理,这里是很多开发和运维人员容易混淆的。从逻辑架构上看, Sentinel节点集合会定期对所有节点进行监控,特别是对主节点的故障实现自动转移。假设我们现在有一个主节点,两个从节点和三个Sentinel节点组成的Redis Sentinel的例。 整个故障转移的处理逻辑有下面4个步骤: 1)主节点出现故障,此时两个从节点与主节点失去连接,主从复制失败 2)每个Sentinel节点通过定期监控发现主节点出现了故障。 3)多个Sentinel节点对主节点的故障达成一致,选举出其中一个sentinel节点作为领导者负责故障转移。 4)Sentinel领导者节点执行了故障转移。通过上面介绍的Redis Sentinel逻辑架构以及故障转移的处理,可以看出Redis Sentinl具有以下几个功能: (1)监控: Sentinel节点会定期检测Redis数据节点、其余Sentinel节点是否可达。 (2)通知: Sentinel节点会将故障转移的结果通知给应用方。 (3)主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系。 (4)配置提供者:在Redis Sentinel结构中,客户端在初始化的时候连接的是Sentinel节占 集合,从中获取主节点信息。同时看到, Redis Sentinel包含了若干个Sentinel节点,这样做也带来了两个好处: (1)对于节点的故障判断是由多个Sentinel节点共同完成,这样可以有效地防止误判。 (2)Sentinel节点集合是由若干个Sentinel节点组成的,这样即使个别Sentinel节点不可用,整个Sentinel节点集合依然是健壮的。 但是Sentinel节点本身就是独立的Redis节点,只不过它们有一些特殊,它们不存储数据,只支持部分命令。

一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效

resource: 资源名称

limitApp: 来源应用

grade: 阈值类型,0表示线程,1表示QPS

count: 单机阈值

strategy: 流控模式,0表示直接,1表示关联,2表示链路

controlBehavior: 流控效果,0表示快速失败,1表示Warm Up,2表示排队等待

clusterMode: 是否集群

熔断降级对调用链路中不稳定的资源进行熔断降级是保障高可用的重要措施之一。

由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)

熔断降级规则包含下面几个重要的属性:

我们通常用以下几种降级策略:

当资源的平均响应时间超过阈值(DegradeRule 中的 count,以 ms 为单位)之后,资源进入准降级状态。如果接下来 1s 内持续进入 5 个请求(即 QPS >= 5),它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。

当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。

当资源近 1 分钟的异常数目超过阈值之后会进行熔断。

可以通过调用 DegradeRuleManagerloadRules() 方法来用硬编码的方式定义流量控制规则。

配置

参数

Hystrix常用的线程池隔离会造成线程上下切换的overhead比较大;Hystrix使用的信号量隔离对某个资源调用的并发数进行控制,效果不错,但是无法对慢调用进行自动降级;

Sentinel通过并发线程数的流量控制提供信号量隔离的功能;此外,Sentinel支持的熔断降级维度更多,可对多种指标进行流控、熔断,且提供了实时监控和控制面板,功能更为强大。

在 《追踪Redis Sentinel的CPU占有率长期接近100%的问题》 一文中,通过结合Redis Sentinel的源码,发现由于出现了"Too many open files"问题,致使Sentinel的acceptTcpHandler事件处理函数会被频繁并快速调用,最终导致了CPU长期接近100%的现象。但对于为什么会出现“Too many open files”这个问题,本文将在上一篇的基础上,继续探讨和分析。

“Too many open files”这个错误其实很常见,想必大家早已对其有一定的了解,这里打算再简单的介绍一下。

很明显,“Too many open files”即说明打开的文件(包括socket)数量过多,已经超出了系统设定给某个进程最大的文件描述符的个数,超过后即无法继续打开新的文件,并且报这个错误。

首先,我们需要了解有关open files的基本知识。详细的概念大家可以谷歌,网上也有各种各样的解决办法,这里只对open files做简单的介绍和总结。

我们在linux上运行ulimit -a 后,出现:

如图open files的个数为1024,很明显这个值太小了(linux默认即为1024),当进程耗尽1024个文件或socket后,就会出现“Too many open files”错误。现实生产环境中这个值很容易达到,所以一般都会进行相应修改。

最简单的修改方式是ulimit -n 65535,但这样重启系统后又会恢复。永久生效的方法是修改/etc/security/limitsconf 文件,在最后加入:

修改完成后,重启系统或者运行sysctl -p使之生效。

soft 和hard,表示对进程所能打开的文件描述符个数限制,其概念为:

他们的区别就是软限制可以在程序的进程中自行改变(突破限制),而硬限制则不行(除非程序进程有root权限)。

上面的规则大家最好自己尝试一下,以增加印象。

重启后,运行ulimit -a,可以看到open files 的值改变为:

简单介绍完open files,我们再来了解下file-max。这个参数相信有很多人经常与ulimit中的open files混淆,他们的区别我们必须了解。

file-max 从字面意思就可以看出是文件的最大个数,运行cat /proc/sys/fs/file-max,可以看到:

这表示当前系统所有进程一共可以打开的文件数量为387311。请务必注意”系统所有进程"这几个字。

运行 vim /etc/sysctlconf ,有时候你会看到类似:fsfile-max = 8192。出现这个则表示用户手动设置了这个值,没有则系统会有其默认值。手动设置的话,只需要在sysctlconf 中加上上述语句即可。

回到ulimit中的open files,它与file-max的区别就是:opem filese表示当前shell以及由它启动的进程的文件描述符的限制,也就是说ulimit中设置的open files,只是当前shell及其子进程的文件描述符的限定。是否清楚?可以简单的理解为:

好了,对于 file-max与open files的简单介绍到此为止。现在的问题就是,“Too many open files”到底是碰到哪个设置的雷区造成的。

结合上一篇,我们知道sentinel主要在执行accept函数时出现了“Too many open files”错误,熟悉accept这个系统调用的朋友很清楚,accept会接收客户端的请求,成功的话会建立连接,并返回新的socket描述符。所以,我们确定这里的“Too many open files”指的即是socket的数目过多。

我们猜测,是否是有大量的Jedis连接同时存在,耗尽服务器的socket资源,导致新的连接请求无法建立。所以,我们查看一下sentinel服务器的TCP连接,运行:netstat -anp | grep 26379,得到:

由上图可以发现,有非常多处于ESTABLISHED状态的TCP连接,运行 netstat -anp | grep 118:26379 | wc -l查看他们的个数:

可以看到,Sentinel同时维持了4071个TCP连接,而且过了很久之后,仍然是这么多,不会有大幅变化。

这时,也许你会想到,是否因为系统文件描述符的限制导致Sentinel无法建立更多的Socket,从而产生“Too many open files”的错误。所以,马上在Sentinel服务器上运行cat /proc/sys/fs/file-max,发现:

这个值很大,看似一切正常。继续运行sudo cat /proc/5515/limits,发现:

看到上图,我们似乎发现了端倪,这里的hard和soft都是4096,与前面的4072比较接近。为什么会这么低?我们继续查看一下ulimit,运行ulimit -a :

可以看到,ulimit中open files 的设置为64000,为什么会不一致?按理说sentinel应该最大有64000+的open files。

对于这个矛盾,一开始我怎么也想不明白。最后我推测:应该是在最早启动sentinel启动的时候,系统的设置为4096,sentinel启动之后,又在某个时间又改为64000,而sentinel进程确保持原有设置,从而导致很快达到限制。

我马上查看了进程的启动时间,运行ps -eo pid,lstart,etime | grep 5515:

发现进程启动于2015年12月2日,接着再次运行 ll /etc/security/limitsconf:

发现确实在2016年4月改动过, 然后我咨询了运维人员,他们告知我,确实改动过,但由多少改动到多少,他们也忘了。。。

为了了解Sentinel启动时的状况,紧接着查看了Sentinel的日志,下面是Sentinel启动时打印的画面:

上图说明了进程是2015年12月2日启动的,特别注意最开头的几行,非常关键:

这几句的意思是:

问题很清楚了,redis sentinel最大可以支持10000个客户端,也就是10032个文件描述符,但由于当前被人为限制到4096 了,所以,自动降低了标准。

因此,我猜测,最早open files的限制为4096时,Sentinel已经启动了,只要进程启动,改多少都没有用。很明显,在生产环境上,4096个连接请求很快就会达到。

接着,我继续查看了Sentinel的配置文件,如下图所示:

上图中, “Generated by CONFIG REWRITE”之前的都是是人工配置,其后为Sentinel自动重写的配置。

熟悉Redis的朋友都知道。Sentinel可能会对其配置文件进行更新重写:

我们很快注意到了maxclients 4064这个配置项,此时我很迷惑。我们知道,在Sentinel中是无法手动运行config set命令的,那这个4096必然不是来自于人工配置,Sentinel为什么要自动重写4064这个值。其实,仔细发现,这里Sentinel限制了最多4064个连接,加上32个预留,刚好为4096。

于是,综上,我猜测,Sentinel在启动的时候发现自己的10032个open files的预期与事实设置的4096不符,所以被迫遵守4096,减去预留的32,最终maxclients 只有4064,并且之后因为某些原因重写了配置,所以输出了这个值。

好吧,我的一贯作风,先猜测,再让源码说话。

我们可以通过异常信息定位异常所处源码,所以我搜索了前面提到的Sentinel在启动时打印的关于maxclients 的日志信息中的文本,如“You requested maxclients of ”。

这个异常出现在adjustOpenFilesLimit函数,通过函数名可以清楚它的作用,然后发现它的调用链只是:

``main()->initServer()->adjustOpenFilesLimit()```

所以,可以确定,在Sentinel服务器启动并进行初始化的时候,会调用adjustOpenFilesLimit函数对open files个数进行调整。调整策略是什么呢?我们查看源码:

在第1行中,REDIS_MIN_RESERVED_FDS即预留的32,是Sentinel保留的用于额外的的 *** 作,如listening sockets, log files 等。同时,这里读取了servermaxclients的值,看来servermaxclients具有初始化值,通过经过定位源码,发现调用链:

即Sentinel在启动时,调用initServerConfig()初始化配置,执行了servermaxclients = REDIS_MAX_CLIENTS(REDIS_MAX_CLIENTS为10000),所以servermaxclients就有了初始值10000。

回到adjustOpenFilesLimit()函数,adjustOpenFilesLimit最终目的就是得到适合的soft,并存在servermaxclients中,因为该函数比较重要,下面专门作出解释:

1 先得到maxfiles的初始值,即Sentinel的期望10032

2 然后获取进程当前的soft和hard,并存入limit ,即执行getrlimit(RLIMIT_NOFILE,&limit) :

调整过程为:

1 先用oldlimit变量保存进程当前的soft的值(如4096)

2 然后,判断oldlimit<maxfiles ,如果真,表示当前soft达不到你要求,需要调整。调整的时候,策略是从最大值往下尝试,以逐步获得Sentinel能申请到的最大soft。

尝试过程为:

1 首先f保存要尝试的soft值,初始值为maxfiles (10032),即从10032开始调整。

2 然后开始一个循环判断,只要f大于oldlimit,就执行一次setrlimit(RLIMIT_NOFILE,&limit) ,然后f减16:

这样,用这种一步一步尝试的方法,最终可用得到了Sentiel能获得的最大的soft值,最后减去32再保存在servermaxclients中。

另外,当得到Sentinel能获得的最合适的soft值f后,还要判断f与oldlimit(系统最初的soft限制,假设为4096),原因如下:

也许会直到f==4096才设置成功,但也会出现f<4096的情况,这是因为跨度为16,最后一不小心就减多了,但最后的soft值不应该比4096还小。所以,f=oldlimit就是这个意思。

最后,还有一个判断:

上面的过程我们用简单的表示为:

adjustOpenFilesLimit()的分析到此结束。但有一点一定要明确,adjustOpenFilesLimit()只会在Sentinel初始化的时候执行一次,目的就是将最合适的soft保存到了servermaxclients (第xx行),以后不会再调用。这样,一旦设置了servermaxclients ,只要Sentinel不重启,这个值就不会变化,这也就解释了为什么Sentinel启动之后再改变open files没有效果的原因了。

那什么时候发生了重写呢?即“Generated by CONFIG REWRITE”这句话什么时候会输出?接着上面,我又在源码里搜索了“Generated by CONFIG REWRITE”这句话,发现了常量REDIS_CONFIG_REWRITE_SIGNATURE,通过它继而发现如下调用链:

上面的调用链中,表示有很多地方会调用sentinelFlushConfig()。

什么时候调用sentinelFlushConfig()呢?经过查找,发现有很多条件都可以触发sentinelFlushConfig函数的调用,包括Leader选举、故障转移、使用Sentinel set 设置命令、Sentinel处理info信息等等。

而sentinelFlushConfig()则会利用rewriteConfig(),针对具体的配置项,分别进行重写,最终将Sentinel所有的状态持久化到了配置文件中。如下所示,在rewriteConfig()中,可以看到非常多的重写类型, 这些重写类型都是与redis的各个配置选项一一对应的:

当然,我们只需要找到其中关于max clients的重写即可,所以在该函数中,我们找到了调用:

可以看到,该函数传入了Sentinel当前的servermaxclients(已经在启动时调整过了,前面分析过),以及默认的REDIS_MAX_CLIENTS即10032。该函数作用就是将当前的servermaxclients的值重写到配置文件中去。什么时候重写呢,即当默认值与当前值不同的时候(也就是force==true的时候),具体可以查看其源码,篇幅限制我们不做详细介绍。

通过前面一大堆的分析,我们可以得出结论:

讲到这里,还有一个问题就是,为什么Sentinel服务器会长期持有4000多个Established状态的TCP连接而不释放。按目前生产环境的规模,正常情况下业务客户端使用的Jedis建立的TCP连接不应该有这么多。

经过查看,发现Sentinel上的很多连接在对应的客户端中并没有存在。如红框所示IP10XX74上:

总计有992个连接:

而实际上在10XX74上,只有5个与Sentinel的连接长期存在:

也就是说,在Sentinel中有大量的连接是无效的,客户端并没有持有,Sentinel一直没有释放。这个问题, 就涉及到了TCP保活的相关知识。

我们首先要了解, *** 作系统通常会自身提供TCP的keepalive机制,如在linux默认配置下,运行sysctl -a |grep keep,会看到如下信息:

上面表示如果连接的空闲时间超过 7200 秒(2 小时),Linux 就发送保持活动的探测包。每隔75秒发一次,总共发9次,如果9次都失败的话,表示连接失效。

TCP提供这种机制帮助我们判断对端是否存活,当TCP检测到对端不可用时,会出错并通知上层进行处理。keepalive机制默认是关闭的,应用程序需要使用SO_KEEPALIVE进行启用。

了解到这个知识之后,我们开始分析。在Redis的源码中,发现有如下调用链:

还记得acceptTcpHandler吗,acceptTcpHandler是TCP连接的事件处理器,当它为客户端成功创建了TCP连接后,会通过调用createClient函数为每个连接(fd)创建一个redisClient 实例,这个redisClient 与客户端是一一对应的。并且,还会设置一些TCP选项,如下所示。

如果用户在Redis中没有手动配置tcpkeepalive的话,servertcpkeepalive = REDIS_DEFAULT_TCP_KEEPALIVE,默认为0。

由第x-x行我们可以明确,Redis服务器与客户端的连接默认是关闭保活机制的,因为只有当servertcpkeepalive不为0(修改配置文件或config set)时,才能调用anetKeepAlive方法设置TCP的keepalive选项。

我们知道,Sentinel是特殊模式的Redis,我们无法使用config set命令去修改其配置,包括tcpkeepalive 参数。所以,当Sentinel启动后,Sentinel也使用默认的tcpkeepalive ==0这个设置,不会启用tcpkeepalive ,与客户端的TCP连接都没有保活机制。也就是说,Sentinel不会主动去释放连接,哪怕是失效连接。

但是,TCP连接是双向的,Sentinel无法处理失效连接,那Jedis客户端呢?它是否可以主动断掉连接?我们定位到了Jedis建立连接的函数connect(),如下所示:

由第x行可以看到,Jedis启用了TCP的keepalive机制,并且没有设置其他keepalive相关选项。也就是说,Jedis客户端会采用linux默认的TCP keepalive机制,每隔7200秒去探测连接的情况。这样,即使与Sentinel的连接出问题,Jedis客户端也能主动释放掉,虽然时间有点久。

但是,实际上,如前面所示,Sentinel服务器上有很多失效连接持续保持,为什么会有这种现象?

对于上面的问题,能想到的原因就是,在Jedis去主动释放掉TCP连接前,该连接被强制断掉,没有进行完整的四次挥手的过程。而Sentinel却因为没有保活机制,没有感知到这个动作,导致其一直保持这个连接。

能干掉连接的元凶,马上想到了防火墙,于是我又询问了运维,结果,他们告知了我一个噩耗:

目前,生产环境上防火墙的设置是主动断掉超过10分钟没有数据交换的TCP连接。

好吧,绕了一大圈,至此,问题已经很清楚了。

终于,我们得出了结论:

有了前面的分析,其实解决办法很简单:

关于“追踪Redis Sentinel的CPU占有率长期接近100%的问题”到此就结束了,在写这两篇博文的时候,我收货了很多自己没有掌握的知识和技巧。现在觉得,写博文真的是一件值得坚持和认真对待的事情,早应该开始。不要问我为什么,当你尝试之后,也就和我一样明白了。

这两篇文章的分析过程肯定有疏漏和不足之处,个人能力有限,希望大家能够理解,并多多指教,非常感谢!我会继续进步!

前面提到过,在每个客户端上,都可以发现5个正常的TCP连接,他们是什么呢?让我们重新回到Jedis。

在《追踪Redis Sentinel的CPU占有率长期接近100%的问题 一》中,我们提到Jedis SentinelPool会为每一个Sentinel建立一个MasterListener线程,该线程用来监听主从切换,保证客户端的Jedis句柄始终对应在Master上。在这里,即会有5个MasterListener来对应5个Sentinel。

其实,MasterListener的监听功能根据Redis的pub sub功能实现的。MasterListener线程会去订阅+switch-master消息,该消息会在master节点地址改变时产生,一旦产生,MasterListener就重新初始化连接池,保证客户端使用的jedis句柄始终关联到Master上。

如下所示为MasterListener的线程函数,它会在一个无限循环中不断的创建Jedis句柄,利用该句柄去订阅+switch-master消息,只要发生了主从切换,就会触发onMessage。

如何实现订阅功能呢,我们需要查看subscribe函数的底层实现,它实际使用clientsetTimeoutInfinite()->connect建立了一个TCP连接,然后使用JedisPubSub的proceed方法去订阅频道,并且无限循环的读取订阅的信息。

在procee的方法中,实际先通过subscribe订阅频道,然后调用process方法读取订阅信息。

其实,subscribe函数就是简单的向服务器发送了一个SUBSCRIBE命令。

而process函数,篇幅较长,此处省略,其主要功能就是以无限循环的方式不断地读取订阅信息

综上,MasterListener线程会向Sentinel创建+switch-master频道的TCP订阅连接,并且会do while循环读取该频道信息。如果订阅或读取过程中出现Tcp连接异常,则释放Jedis句柄,然后等待5000ms 后重新创建Jedis句柄进行订阅。当然,这个过程会在一个循环之中。

至此,也就解释了为何每个业务客户端服务器和Sentinel服务器上,都有5个长期保持的、状态正常的TCP连接的原因了。

不同的物理存储中心,例如consul、nacos、redis、mongo都是no-sql存储,即存储k-v形式的数据。甚至mysql中也可以存储json格式的数据。虽然有各种各样形式的client实现。但若是想嵌入到咱sentinel作为存储中心,必须满足咱定义的“接口”规范。通过“组合”的形式将存储中心实际client嵌入到接口子类中,对外提供服务。

接口如下:

没错,只需要简单的实现put、get、remove等方法,就可以无痛的替换存储中心。

配置文件中修改: sentineldashboardstore 属性完成动态切换。

当value是map类型时,就出现了一个问题:

接口关系如下所示: 实现如下接口满足map的扁平化 *** 作。

存储实体的基本 *** 作

map扁平化接口:

规则的分布式存储实现类——可以存储allRules规则以及appRules规则:

对应FastJson来说,在反序列中传入type是可以得到泛型对象的。

但实际上得到的却是JsonObject对象。无法转换为 RuleEntity 对象。

使用Jackson也遇见了此种情况:

自定义Jackson的ObjectMapper对象,在序列化的时候将属性的类型也序列化即可:

使用的锁机制,实现了consul分布式锁以及默认的内存锁。保证“扁平化” *** 作时的线程安全性。

借助ConcurrentHashMap将ReentrantLock存储起来。

——“这么骚的 *** 作是在seata源码中借鉴的。”

在13小节的“动态切换”中,可以根据配置文件来动态切换锁。当然以后去除consul使用redis等存储中心,可以实现定义的Lock接口,实现Redis的分布式锁。

以上就是关于熔断限流全部的内容,包括:熔断限流、sentinel控制台请求链路很多数据是什么、访问redis哨兵读数据等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9805213.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-02
下一篇2023-05-02

发表评论

登录后才能评论

评论列表(0条)

    保存