nginx重试机制导致系统雪崩

本文讲述了在上线项目中由于一个GET请求导致数据库宕机的问题。问题源于Nginx的请求故障转移机制,当GET请求超时时,nginx会将请求转发至其他节点,造成请求重复执行,数据库压力剧增。优化措施包括缩短请求响应时间或改用POST请求。了解到nginx仅在GET请求中启用故障转移,而在POST请求中不转发,这为避免类似问题提供了思路。

         最近在上线项目的过程中遇到由于一个请求大量查询数据库,导致生产系统宕机。后期在把开发、dba、运维都拉在一起开会讨论时,按dba提供的数据库配置 性能后都无法相信这个请求会导致数据库宕机。后面重新复盘,在执行一次这个请求后仔细排查执行过程,才发现是由于nginx自带的请求故障转移导致了这次的宕机。

    我们系统是采用nginx作为负载均衡工具,负责转发请求到后端的应用节点。nginx有一个故障请求重试的机制。在后端节点请求超时时会把该请求自动转发到下一个节点,如果下一个节点返回结果则将该结果返回给用户,相关参数是proxy_next_upstream。但这次出现问题的请求需要大量查询数据库,性能无法优化,超过了nginx设定的超时时间。这种情况下雪崩开始了,nginx发现这个请求超时没有返回后将该请求转发到了其他的节点,相当于这个大请求执行了多次,这样更加加剧了数据库的压力,数据库宕机了,系统也无法响应了。

  优化措施:优化该请求,另起线程执行耗时任务,缩短该请求的响应时间。

   后面在网上看到这篇文章,Nginx upstream 失效转移机制研究,了解到nginx在get类型请求才会触发故障转移机制,在post类型则不会转发。而出问题的那个请求是get请求,如果是post发送该请求则不会导致上述问题。

    结论:在nginx做负载均衡下,get类型的耗时的请求容易导致系统雪崩。需要优化请求,减少请求的响应时间或者改成post类型提交该请求。

<think>我们讨论的是缓存击穿解决方案中如何限制请求的重试次数。在分布式互斥锁方案中,当线程未能获取到锁时,通常会等待一段时间后重试。为了避免重试无限制进行,我们需要限制重试次数。 参考用户提供的引用,特别是引用[1][2][3]中关于缓存击穿的定义和基本解决方案,以及引用[4]中提到的使用锁或队列的方式。 在分布式锁方案中,我们可以通过以下方式限制重试次数: 1. 设置最大重试次数 2. 设置重试等待时间(避免立即重试导致CPU竞争激烈) 3. 当重试次数达到上限后,可以采取降级策略(例如返回默认值、错误提示等) 修改之前的代码示例,加入重试次数限制: ```java public Object getDataWithRetryLimit(String key, int maxRetries) { Object value = redis.get(key); if (value == null) { int retryCount = 0; while (retryCount < maxRetries) { if (lockRedis.set(key, "lock", "PX", 100, "NX")) { // 尝试获取锁 try { value = getDataFromDb(key); // 查数据库 redis.set(key, value, "PX", 30000); // 更新缓存 break; // 成功获取数据,跳出循环 } finally { lockRedis.del(key); // 释放锁 } } else { // 未获取到锁,等待一段时间后重试 try { Thread.sleep(50); // 等待50ms } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; // 中断则退出 } retryCount++; // 增加重试次数 } } // 如果重试次数达到上限仍未获取到数据,则执行降级策略 if (retryCount >= maxRetries) { return getFallbackData(key); // 降级方法 } } return value; } ``` 上述代码中,我们引入了: - `maxRetries`:最大重试次数 - `retryCount`:当前重试次数 - 循环条件:当重试次数小于最大重试次数时,继续尝试 - 达到最大重试次数后,调用降级方法`getFallbackData` 注意:为了避免无限循环,必须设置最大重试次数。同时,每次重试之间需要等待一段时间(如50ms),避免频繁重试给系统带来压力。 另外,降级策略可以根据业务场景设计,例如: 1. 返回一个默认值(比如空对象、静态提示信息等) 2. 抛出特定的异常 3. 使用备份缓存(如之前提到的双层缓存策略中的备用缓存) 结合引用[4]中提到的“设置过期标志更新缓存”的思想,我们还可以在降级策略中返回一个接近过期的缓存值(如果存在的话),但这需要额外的设计。 总结:限制重试次数的关键在于在循环条件中加入重试次数的判断,并在达到上限时执行降级策略,避免无限制重试导致线程阻塞或资源浪费。 相关问题: 1. 如何设置合理的重试次数和等待时间? 2. 降级策略有哪些常见的设计模式? 3. 在分布式锁方案中,锁等待超时应该如何处理?</think>### 缓存击穿解决方案中的重试次数控制 在缓存击穿解决方案中,限制重试次数是防止系统雪崩的关键环节。以下是具体实现方法(基于分布式锁方案): #### 核心实现方案 ```java public Object getDataWithRetryLimit(String key) { final int MAX_RETRIES = 3; // 最大重试次数 int retryCount = 0; Object value = null; while (value == null && retryCount <= MAX_RETRIES) { value = redis.get(key); if (value != null) return value; // 尝试获取分布式锁 if (lockRedis.set(key, "lock", "PX", 100, "NX")) { try { value = getDataFromDb(key); // 查数据库 redis.set(key, value, "PX", 30000); // 更新缓存 } finally { lockRedis.del(key); // 释放锁 } } else { // 未获取锁时的重试逻辑 try { Thread.sleep(50 + (20 * retryCount)); // 退避策略:50ms, 70ms, 90ms... } catch (InterruptedException e) { Thread.currentThread().interrupt(); } retryCount++; } } // 重试次数耗尽后的降级处理 return (value != null) ? value : getFallbackData(key); } ``` #### 关键技术点 1. **指数退避策略**(关键优化) - 每次重试间隔增加:`50ms → 70ms → 90ms...` - 公式:$ \text{waitTime} = 50 + (20 \times \text{retryCount}) $ - 作用:避免集群同时重试造成惊群效应[^4] 2. **重试次数限制** ```java final int MAX_RETRIES = 3; // 可配置参数 while (retryCount <= MAX_RETRIES) { ... } ``` 3. **降级处理机制** ```java private Object getFallbackData(String key) { // 可选方案: // 1. 返回静态默认值 // 2. 返回本地缓存副本 // 3. 抛出业务友好异常 return DEFAULT_VALUE; } ``` 4. **熔断器模式**(进阶方案) ```java CircuitBreaker breaker = new CircuitBreaker( 3, // 失败阈值 5000 // 熔断时间(ms) ); if (breaker.allowRequest()) { // 执行数据库查询 } else { return getFallbackData(key); } ``` #### 配置建议 | 参数 | 推荐值 | 说明 | |--------------------|----------|-----------------------------| | 最大重试次数(MAX_RETRIES) | 2-5 | 过高会导致延迟累积 | | 基础等待时间 | 30-100ms | 根据网络延迟调整 | | 退避系数 | 20-50ms | 控制重试间隔增长幅度 | | 熔断时间 | 5-10s | 触发熔断后的恢复时间 | > **最佳实践**: > 1. 结合**监控系统**动态调整参数:当数据库负载 > 70% 时自动降低 MAX_RETRIES[^4] > 2. 采用**自适应退避算法**:$ \text{waitTime} = \text{base} \times 2^{\text{retryCount}} $ > 3. 在 Nginx 层添加**请求排队**机制,避免重试请求压垮应用层
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值