霸王餐接口故障演练:ChaosMesh注入延迟与Java线程池拒绝策略
场景下,上游服务是否具备容错能力。使用 ChaosMesh 在 Kubernetes 集群中注入网络延迟,并结合自定义 Java 线程池拒绝策略,确保系统不雪崩。“吃喝不愁”App的霸王餐核销接口依赖下游优惠券服务,需验证在。本文著作权归吃喝不愁app开发者团队,转载请注明出处!),则 JVM 内存会持续增长直至 OOM。的所有请求增加 2 秒固定延迟。
·
霸王餐接口故障演练:ChaosMesh注入延迟与Java线程池拒绝策略
演练目标与环境
“吃喝不愁”App的霸王餐核销接口依赖下游优惠券服务,需验证在网络延迟突增场景下,上游服务是否具备容错能力。使用 ChaosMesh 在 Kubernetes 集群中注入网络延迟,并结合自定义 Java 线程池拒绝策略,确保系统不雪崩。
部署架构:
order-service(Spring Boot):调用coupon-service;- 两者均运行在 K8s,命名空间
eatfree-prod; - ChaosMesh 已安装并授权。
ChaosMesh 网络延迟注入
创建 network-delay.yaml:
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: delay-coupon-service
namespace: eatfree-prod
spec:
action: delay
mode: one
selector:
namespaces:
- eatfree-prod
labelSelectors:
app: order-service
duration: "60s"
delay:
latency: "2s"
correlation: "100"
jitter: "0ms"
target:
selector:
namespaces:
- eatfree-prod
labelSelectors:
app: coupon-service
mode: all
执行注入:
kubectl apply -f network-delay.yaml
效果:order-service 到 coupon-service 的所有请求增加 2 秒固定延迟。
Java 异步调用与线程池配置
order-service 使用 CompletableFuture 异步调用下游,避免主线程阻塞:
package juwatech.cn.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.*;
@Service
public class CouponAsyncClient {
private final ExecutorService couponExecutor;
public CouponAsyncClient() {
this.couponExecutor = new ThreadPoolExecutor(
10, // corePoolSize
30, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new LinkedBlockingQueue<>(50), // bounded queue
new ThreadFactory() {
private int counter = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "coupon-client-" + counter++);
}
},
new CouponRejectedExecutionHandler() // 自定义拒绝策略
);
}
public CompletableFuture<CouponResult> verifyCoupon(String code) {
return CompletableFuture.supplyAsync(() -> {
// 模拟 HTTP 调用,实际使用 Feign 或 WebClient
try {
Thread.sleep(2500); // 模拟超时(>2s)
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new CouponResult("VALID");
}, couponExecutor);
}
static class CouponRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录拒绝事件,返回兜底结果
System.err.println("Coupon task rejected. Queue size: " + executor.getQueue().size());
throw new CouponRejectedException("Service busy, please retry later");
}
}
}
关键设计:
- 有界队列(
LinkedBlockingQueue(50))防止内存溢出; - 自定义拒绝策略:抛出业务异常,触发降级逻辑。
Controller 层降级处理
package juwatech.cn.controller;
import juwatech.cn.service.CouponAsyncClient;
import juwatech.cn.service.CouponRejectedException;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@RestController
public class OrderController {
private final CouponAsyncClient couponClient;
public OrderController(CouponAsyncClient couponClient) {
this.couponClient = couponClient;
}
@PostMapping("/orders/claim")
public ResponseEntity<?> claimFreeMeal(@RequestParam String couponCode) {
CompletableFuture<CouponResult> future = couponClient.verifyCoupon(couponCode);
try {
// 设置 3 秒超时,略大于 ChaosMesh 注入的 2 秒
CouponResult result = future.get(3000, TimeUnit.MILLISECONDS);
if ("VALID".equals(result.getStatus())) {
return ResponseEntity.ok("Claim success");
}
} catch (TimeoutException e) {
return ResponseEntity.status(504).body("Coupon service timeout");
} catch (ExecutionException e) {
if (e.getCause() instanceof CouponRejectedException) {
return ResponseEntity.status(503).body("System busy, try again later");
}
return ResponseEntity.status(500).body("Internal error");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ResponseEntity.status(500).body("Interrupted");
}
return ResponseEntity.badRequest().body("Invalid coupon");
}
}
故障演练结果分析
注入 2 秒延迟后:
- 前 10~20 个请求因线程池空闲,排队等待,最终超时返回
504; - 持续高并发下,队列迅速填满(50),新任务触发拒绝策略,返回
503; - 主线程未阻塞,其他非相关接口(如用户信息查询)仍正常响应;
- 无 Full GC,堆内存稳定。
若使用无界队列(如 newCachedThreadPool),则 JVM 内存会持续增长直至 OOM。
优化建议
- 结合 Hystrix 或 Resilience4j 实现熔断;
- 监控指标暴露:
executor.getActiveCount(),executor.getQueue().size(); - 拒绝策略可改为返回缓存数据或默认值,而非直接报错。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
更多推荐



所有评论(0)