MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Redis限流熔断机制的阈值精准设定

2023-02-202.7k 阅读

1. 限流与熔断机制概述

限流和熔断机制是保障系统稳定性和可靠性的重要手段,尤其在高并发场景下,它们能有效防止系统因过载而崩溃。

1.1 限流机制

限流,简单来说,就是对系统的请求流量进行限制。当系统的请求量达到一定阈值时,后续的请求将被拒绝或者等待处理。常见的限流算法有令牌桶算法和漏桶算法。

  • 令牌桶算法:系统以固定速率生成令牌,并将令牌放入桶中。每个请求到达时尝试从桶中获取一个令牌,如果桶中有足够的令牌,则请求被处理;如果桶中没有令牌,则请求被限流。例如,一个系统以每秒 10 个令牌的速率生成令牌,桶的容量为 100 个令牌。当请求以每秒 15 个的速率到达时,开始的 100 个请求可以正常获取令牌被处理,后续每秒只有 10 个请求能获取令牌,其余 5 个请求将被限流。

  • 漏桶算法:漏桶算法中,请求就像水一样流入桶中,而桶以固定的速率将水漏出(处理请求)。如果桶满了,新流入的水(请求)将被丢弃。例如,一个漏桶以每秒 10 个请求的速率处理请求,桶的容量为 100 个请求。当请求以每秒 15 个的速率到达时,开始桶会逐渐被填满,当桶满后,新到达的请求将被丢弃。

1.2 熔断机制

熔断机制主要用于处理系统依赖故障。当系统调用的某个下游服务出现故障(如响应时间过长、错误率过高)时,为了防止整个系统被拖垮,系统会暂时切断对该下游服务的调用,就像电路中的保险丝熔断一样。在熔断状态下,对该下游服务的请求不再实际调用,而是直接返回一个预设的默认值或者错误信息。经过一段时间的“冷却”后,系统会尝试恢复对下游服务的调用,如果恢复成功,则熔断状态解除;如果仍然失败,则继续保持熔断状态。

例如,一个电商系统调用物流服务查询订单配送信息。如果物流服务出现故障,大量请求超时,电商系统为了避免自身资源耗尽,会开启熔断机制,对后续查询物流信息的请求直接返回“物流信息查询暂时不可用”的提示,而不再尝试调用物流服务。经过一段时间(如 1 分钟)后,电商系统会尝试调用一次物流服务,如果成功获取到信息,则解除熔断;如果仍然失败,则继续保持熔断。

2. Redis 在限流熔断中的应用

Redis 作为一款高性能的键值对数据库,在实现限流熔断机制方面具有诸多优势。

2.1 Redis 用于限流

Redis 可以通过原子操作轻松实现限流逻辑。例如,使用 INCR 命令可以原子性地递增一个计数器的值。我们可以基于此实现简单的计数器限流。

import redis

r = redis.Redis(host='localhost', port=6379, db = 0)

def limit_request(key, limit, period):
    current = r.incr(key)
    if current == 1:
        r.expire(key, period)
    if current > limit:
        return False
    return True

在上述 Python 代码中,limit_request 函数接收三个参数:key 用于标识限流的对象(比如某个接口或者某个用户),limit 是允许的请求上限,period 是限流的时间周期。每次请求到达时,通过 r.incr(key) 原子性地增加计数器的值。如果是第一次请求(current == 1),设置该键的过期时间为 period 秒,这样在 period 秒后计数器会自动重置。如果当前请求数超过了 limit,则返回 False 表示请求被限流,否则返回 True 表示请求可以继续处理。

如果要实现基于令牌桶算法的限流,Redis 可以使用 SETNX 命令(Set if Not eXists)和 INCRBY 命令。SETNX 用于初始化令牌桶的容量,INCRBY 可以按固定速率增加令牌。

import time

def token_bucket_limit(key, capacity, rate):
    now = int(time.time())
    last_refill = r.get(key + ':last_refill')
    if not last_refill:
        r.set(key + ':last_refill', now)
        r.set(key + ':tokens', capacity)
        return True
    last_refill = int(last_refill)
    tokens = int(r.get(key + ':tokens'))
    new_tokens = tokens + (now - last_refill) * rate
    if new_tokens > capacity:
        new_tokens = capacity
    r.set(key + ':last_refill', now)
    r.set(key + ':tokens', new_tokens)
    if new_tokens < 1:
        return False
    r.decr(key + ':tokens')
    return True

在这个代码示例中,token_bucket_limit 函数实现了令牌桶限流。capacity 是令牌桶的容量,rate 是每秒生成令牌的速率。函数首先获取当前时间和上次填充令牌的时间。根据时间差计算出新的令牌数量,并更新上次填充时间和当前令牌数量。如果当前令牌数量小于 1,则请求被限流;否则,减少一个令牌并允许请求通过。

2.2 Redis 用于熔断

Redis 可以用来存储熔断状态。例如,我们可以使用一个 Redis 键来表示某个服务是否处于熔断状态。当服务错误率达到一定阈值时,设置该键的值为表示熔断的标识;在熔断期间,每次请求该服务时,先检查 Redis 中该键的值,如果处于熔断状态,则直接返回预设的默认值。

def check_fallback(key):
    return r.get(key) is not None

def set_fallback(key, value, duration):
    r.setex(key, duration, value)

def clear_fallback(key):
    r.delete(key)

在上述代码中,check_fallback 函数用于检查某个服务是否处于熔断状态,通过检查 Redis 中对应的键是否存在来判断。set_fallback 函数用于设置熔断状态,value 可以是预设的默认值,duration 是熔断持续的时间。clear_fallback 函数用于在熔断冷却期结束后,尝试恢复调用时清除熔断状态。

3. 阈值精准设定的重要性

阈值的精准设定对于限流熔断机制能否有效保障系统稳定运行至关重要。

3.1 限流阈值精准设定的意义

  • 避免系统过载:如果限流阈值设置过高,系统可能会接收过多的请求,导致资源耗尽,最终崩溃。例如,一个服务器的 CPU 和内存资源在处理每秒 1000 个请求时已经接近饱和,如果限流阈值设置为每秒 1500 个请求,那么当请求量达到 1500 时,服务器可能会因为无法承受而出现响应缓慢甚至宕机的情况。

  • 保证服务质量:如果限流阈值设置过低,会导致大量正常请求被限流,影响用户体验。比如,一个热门电商网站在促销活动期间,每秒实际可以处理 5000 个商品查询请求,但限流阈值误设置为每秒 1000 个请求,那么大量用户的查询请求将被拒绝,用户会认为网站响应不及时,降低对该网站的满意度。

3.2 熔断阈值精准设定的意义

  • 及时切断故障依赖:熔断阈值设置合理可以在下游服务出现故障时及时切断调用,防止故障扩散。例如,一个微服务系统中,某个服务 A 依赖服务 B 进行数据处理。如果服务 B 的错误率达到 50% 时,就应该及时熔断,否则服务 A 可能会因为不断调用失败的服务 B 而消耗大量自身资源,进而影响整个系统的稳定性。

  • 避免不必要的熔断:如果熔断阈值设置过低,可能会因为偶尔的网络波动或者短暂的服务抖动就触发熔断,导致服务不可用。比如,一个在线支付接口,由于网络瞬间波动,在 10 次请求中有 2 次失败,但实际该接口整体运行良好,如果熔断阈值设置为错误率 20% 就触发熔断,那么这个支付接口可能会被不必要地熔断,影响用户正常支付。

4. 影响限流阈值设定的因素

4.1 系统资源限制

  • CPU 资源:CPU 是处理请求的核心资源。如果 CPU 使用率过高,会导致请求处理速度变慢。例如,一个 Web 应用服务器,其 CPU 在处理请求时主要进行业务逻辑计算和数据处理。假设该服务器的 CPU 在 100% 使用率下可以每秒处理 2000 个简单请求。但在实际业务中,由于还需要处理数据库查询、文件读写等操作,CPU 实际在 80% 使用率下就可能出现性能瓶颈。那么在设置限流阈值时,就需要根据这个实际的性能瓶颈来计算。假设在 80% CPU 使用率下每秒能处理 1600 个请求,为了保证系统稳定,限流阈值可能需要设置在每秒 1200 - 1400 个请求之间,预留一定的缓冲空间。

  • 内存资源:内存用于存储请求处理过程中的数据、缓存等。如果内存不足,可能会导致数据频繁交换到磁盘,大大降低系统性能。比如,一个缓存服务器,其内存用于存储热门数据缓存。假设该服务器内存容量为 16GB,每个缓存对象平均占用 10KB 内存。理论上可以存储 1600000 个缓存对象。但考虑到操作系统和其他进程也会占用内存,以及缓存数据的动态更新和内存碎片等问题,实际可用内存可能只有 12GB,即能存储 1200000 个缓存对象。当请求量过大,导致缓存对象数量接近这个上限时,就需要限制请求,以避免内存溢出。因此,在设置限流阈值时,需要考虑内存资源的限制,根据实际可用内存和每个请求可能产生的内存占用情况来计算。

  • 网络带宽:网络带宽决定了系统接收和发送数据的速度。如果网络带宽不足,请求的数据无法及时传输,会导致请求堆积。例如,一个视频流媒体服务器,假设其网络带宽为 100Mbps,每个视频流平均需要 2Mbps 带宽。理论上可以同时支持 50 个视频流。但考虑到网络传输的开销和其他业务数据的传输,实际可能只能稳定支持 40 个视频流。那么在设置针对视频流请求的限流阈值时,就需要设置在每秒 40 个以下,以保证网络的稳定运行。

4.2 业务特性

  • 请求类型:不同类型的请求对系统资源的消耗不同。例如,在一个电商系统中,商品查询请求主要是读取数据库中的商品信息,相对来说对资源的消耗较小;而订单提交请求不仅要更新数据库中的订单表、库存表等多个表,还可能涉及到支付接口调用、物流信息预分配等复杂操作,对资源的消耗较大。因此,在设置限流阈值时,需要针对不同类型的请求分别设置。对于商品查询请求,可以设置相对较高的限流阈值,比如每秒 1000 个请求;而对于订单提交请求,可能需要设置较低的限流阈值,比如每秒 100 个请求。

  • 业务高峰期与低谷期:业务具有明显的高峰期和低谷期时,限流阈值也需要相应调整。以一个在线教育平台为例,在晚上 7 点 - 9 点是学生集中学习的高峰期,此时对课程视频播放、作业提交等请求量会大幅增加;而在凌晨 2 点 - 5 点,请求量会非常少。在高峰期,为了保证系统稳定运行,需要将限流阈值设置得相对较低,比如课程视频播放请求设置为每秒 500 个;而在低谷期,可以适当提高限流阈值,比如设置为每秒 1000 个,以充分利用系统资源。

  • 业务重要性:不同业务对系统的重要性不同。例如,在一个银行核心业务系统中,转账业务关乎用户资金安全,是非常重要的业务;而用户账户信息查询业务相对来说重要性稍低。在设置限流阈值时,对于转账业务,要保证其高可用性,限流阈值可以设置得较低,以确保即使在高并发情况下,转账业务也能稳定处理;而对于账户信息查询业务,可以设置相对较高的限流阈值。

5. 影响熔断阈值设定的因素

5.1 服务依赖特性

  • 响应时间:下游服务的响应时间是判断其是否正常的重要指标。如果响应时间过长,会导致调用方等待时间过长,影响整体系统性能。例如,一个电商系统调用第三方物流接口查询订单配送进度。正常情况下,该接口响应时间在 200ms 以内。但近期由于物流系统升级,部分时段响应时间飙升到 1000ms 以上。对于电商系统来说,这种长时间的等待会导致用户体验下降,并且可能阻塞其他业务流程。因此,当物流接口的平均响应时间超过 500ms 时,就可以考虑触发熔断。

  • 错误率:下游服务的错误率直接反映其运行状态。如果错误率过高,说明服务可能出现故障。比如,一个支付接口,正常情况下错误率应该在 1% 以下。但由于服务器硬件故障,错误率突然上升到 10%。对于依赖该支付接口的电商系统来说,大量支付失败会严重影响业务。因此,当支付接口的错误率超过 5% 时,就需要触发熔断,避免继续调用导致更多问题。

5.2 系统容忍度

  • 业务容忍度:不同业务对服务故障的容忍度不同。例如,在一个新闻资讯平台中,图片加载服务出现故障,部分图片无法显示,对用户获取新闻内容的影响相对较小,业务容忍度较高;而在一个在线游戏平台中,游戏服务器连接服务出现故障,导致玩家无法登录游戏,对业务影响极大,业务容忍度较低。对于新闻资讯平台,图片加载服务的熔断阈值可以设置得相对较高,比如错误率达到 20% 才触发熔断;而对于在线游戏平台,游戏服务器连接服务的熔断阈值需要设置得非常低,比如错误率达到 5% 就触发熔断。

  • 系统架构容忍度:系统架构的健壮性也影响熔断阈值的设定。如果系统采用了分布式架构,并且有完善的容错机制和备用方案,那么对某个服务故障的容忍度相对较高。例如,一个分布式电商系统,对于商品详情服务,有多个副本节点提供服务。当其中一个节点出现故障,错误率上升时,系统可以通过负载均衡将请求分配到其他正常节点。在这种情况下,该商品详情服务的熔断阈值可以设置得相对较高,比如错误率达到 30% 才触发熔断。而对于一些架构相对简单,没有太多备用方案的系统,熔断阈值需要设置得较低。

6. 阈值精准设定的方法

6.1 基于历史数据的分析

  • 收集历史数据:首先要收集系统在过去一段时间内的请求量、响应时间、错误率等相关数据。例如,对于一个 Web 应用,可以通过服务器日志记录每个请求的时间、请求类型、响应时间、是否成功等信息。对于下游服务,也可以通过监控工具记录其响应时间和错误率等数据。收集的数据时间跨度要足够长,以涵盖业务的高峰期和低谷期,以及各种可能的业务场景。

  • 数据分析:对收集到的历史数据进行分析。对于限流阈值的设定,可以分析请求量的分布情况。比如,绘制请求量随时间变化的图表,找出高峰期和低谷期的请求量峰值和平均值。然后根据系统资源限制和业务需求,确定合理的限流阈值。例如,如果分析发现某个接口在高峰期的平均请求量为每秒 800 个,而系统在保证稳定运行的情况下,每秒最多能处理 1000 个请求,为了预留一定的缓冲空间,可以将限流阈值设置为每秒 800 个请求。

对于熔断阈值的设定,可以分析下游服务的响应时间和错误率的分布。例如,计算下游服务在过去一周内的平均响应时间和错误率,以及响应时间和错误率的波动情况。如果发现某个服务的平均错误率为 3%,但在某些时段会突然上升到 10%,而业务容忍度为错误率 5%,那么可以将熔断阈值设置为错误率 5%。

6.2 实时监控与动态调整

  • 实时监控:建立实时监控系统,实时监测系统的请求量、响应时间、错误率等关键指标。可以使用 Prometheus、Grafana 等工具来实现实时监控。例如,通过 Prometheus 采集服务器的 CPU 使用率、内存使用率、请求量等指标数据,然后通过 Grafana 将这些数据以图表的形式展示出来,方便运维人员实时查看系统状态。

  • 动态调整:根据实时监控的数据,动态调整限流和熔断阈值。例如,当发现系统的 CPU 使用率持续上升接近 80%,而当前限流阈值设置下请求量仍然较高时,可以适当降低限流阈值,以减轻系统压力。对于熔断阈值,当发现下游服务的错误率在短时间内快速上升,但还未达到当前熔断阈值时,可以根据错误率上升的趋势,提前调整熔断阈值,以便更快地触发熔断,保护系统。

7. 代码示例优化与完整应用

7.1 限流代码优化

在之前简单限流代码的基础上,可以增加更多功能,比如支持分布式限流。

import redis
from redis.lock import Lock

r = redis.Redis(host='localhost', port=6379, db = 0)

def distributed_limit_request(key, limit, period):
    with Lock(r, key + ':lock'):
        current = r.incr(key)
        if current == 1:
            r.expire(key, period)
        if current > limit:
            return False
        return True

在这个优化后的代码中,使用了 Redis 锁 Lock 来保证在分布式环境下,对限流计数器的操作是原子性的。这样可以避免多个实例同时操作计数器导致的限流不准确问题。

7.2 熔断代码优化

优化熔断代码,使其能够更灵活地处理不同类型的下游服务。

def check_fallback(service_key):
    return r.get(service_key + ':fallback') is not None

def set_fallback(service_key, value, duration):
    r.setex(service_key + ':fallback', duration, value)

def clear_fallback(service_key):
    r.delete(service_key + ':fallback')

def call_downstream_service(service_key, actual_call_func):
    if check_fallback(service_key):
        fallback_value = r.get(service_key + ':fallback')
        return fallback_value.decode('utf-8') if fallback_value else 'Fallback value not set'
    try:
        result = actual_call_func()
        # 假设这里可以根据实际响应更新熔断状态
        # 比如如果响应时间过长或者错误,更新熔断状态
        return result
    except Exception as e:
        # 调用失败,更新熔断状态
        set_fallback(service_key, str(e), 60)
        return 'Service is currently unavailable'

在上述代码中,call_downstream_service 函数用于调用下游服务。首先检查是否处于熔断状态,如果是,则返回预设的熔断值;否则尝试实际调用下游服务,并根据调用结果更新熔断状态。

7.3 完整应用示例

以一个简单的电商系统为例,展示限流熔断机制的完整应用。

import redis
from redis.lock import Lock

r = redis.Redis(host='localhost', port=6379, db = 0)

def distributed_limit_request(key, limit, period):
    with Lock(r, key + ':lock'):
        current = r.incr(key)
        if current == 1:
            r.expire(key, period)
        if current > limit:
            return False
        return True

def check_fallback(service_key):
    return r.get(service_key + ':fallback') is not None

def set_fallback(service_key, value, duration):
    r.setex(service_key + ':fallback', duration, value)

def clear_fallback(service_key):
    r.delete(service_key + ':fallback')

def call_downstream_service(service_key, actual_call_func):
    if check_fallback(service_key):
        fallback_value = r.get(service_key + ':fallback')
        return fallback_value.decode('utf-8') if fallback_value else 'Fallback value not set'
    try:
        result = actual_call_func()
        # 假设这里可以根据实际响应更新熔断状态
        # 比如如果响应时间过长或者错误,更新熔断状态
        return result
    except Exception as e:
        # 调用失败,更新熔断状态
        set_fallback(service_key, str(e), 60)
        return 'Service is currently unavailable'

def get_product_info(product_id):
    # 模拟实际获取产品信息的函数
    return f'Product {product_id} information'

def get_product_info_wrapper(product_id):
    service_key = 'product_info_service'
    if not distributed_limit_request('product_info_request', 100, 60):
        return 'Request limit exceeded'
    return call_downstream_service(service_key, lambda: get_product_info(product_id))

在这个完整应用示例中,get_product_info_wrapper 函数用于获取产品信息。首先通过 distributed_limit_request 函数进行限流检查,如果请求未超过限流阈值,则通过 call_downstream_service 函数调用实际的获取产品信息函数 get_product_info,并且在调用过程中会根据情况处理熔断逻辑。

通过以上详细的阐述、分析和代码示例,我们对 Redis 限流熔断机制的阈值精准设定有了较为全面的理解和实践指导,在实际应用中,可以根据具体业务场景和系统特性,灵活运用这些方法和技术,保障系统的稳定运行。