弹性 AOP:@Retry / @CircuitBreaker / @Timed
外部 HTTP 调用、数据库操作等场景天生不稳定。ACE Framework 提供三个弹性切面宏,无需修改业务逻辑即可叠加重试、熔断和计时能力。
@Retry 自动重试
@Retry[n] 在方法抛出异常时自动重试,最多重试 n 次(不含首次调用)。若所有尝试均失败,最后一次异常原样抛出给调用方。
package demo.service
import ace.framework.*
@Service
class PaymentClient {
// 最多重试 3 次,共尝试 4 次
@Retry[3]
func charge(orderId: String, amount: Float64): Bool {
// 调用支付网关,可能因网络抖动失败
return paymentGateway.charge(orderId, amount)
}
}重试行为
| 调用次数 | 结果 | 框架行为 |
|---|---|---|
| 第 1 次(原始调用) | 异常 | 等待后重试 |
| 第 2–4 次(重试 1–3) | 异常 | 继续重试 / 最终抛出 |
| 任意次成功 | 正常返回 | 立即返回,停止重试 |
重试间隔
当前版本采用立即重试(无退避延迟)。若需指数退避,请在方法体内手动 Thread.sleep,或等待后续 @Retry[n, backoff: "exponential"] 支持。
@CircuitBreaker 熔断器
@CircuitBreaker[n] 在连续 n 次失败后触发熔断。熔断期间直接抛出 503 错误,不执行方法体,从而保护下游服务和本机资源。经过冷却期后自动进入半开状态,尝试恢复。
@Service
class InventoryClient {
// 连续 5 次失败后熔断
@CircuitBreaker[5]
func queryStock(skuId: String): Int64 {
return inventoryApi.query(skuId)
}
}熔断器状态机
正常调用
│
▼
[CLOSED] ──连续失败 n 次──► [OPEN] ──冷却期到──► [HALF_OPEN]
▲ │
└──────────── 调用成功 ─────────────────────────────┘
│
调用失败 → 重回 [OPEN]| 状态 | 行为 |
|---|---|
CLOSED(正常) | 正常执行方法,统计失败次数 |
OPEN(熔断) | 直接抛出 503,不调用方法体 |
HALF_OPEN(半开) | 放行一次试探调用,成功则关闭,失败则重新熔断 |
@Timed 精确计时
@Timed 记录方法的调用次数、平均耗时和最大耗时,通过 aceTimedStats("方法名") 读取统计数据。
@Service
class SearchService {
@Timed
func search(keyword: String): Array<String> {
return searchEngine.query(keyword)
}
}// 读取统计数据:(count, avgMs, maxMs)
let (count, avgMs, maxMs) = aceTimedStats("search")
println("调用次数: ${count}, 平均耗时: ${avgMs}ms, 最大耗时: ${maxMs}ms")aceTimedStats API
| 参数 | 说明 |
|---|---|
"methodName" | 方法名(字符串字面量),与被 @Timed 标注的方法名一致 |
返回值类型为 (Int64, Float64, Float64),分别对应 (调用次数, 平均耗时ms, 最大耗时ms)。
组合使用:重试 + 熔断 + 计时
三个宏可叠加,执行顺序为 @Timed → @CircuitBreaker → @Retry → 方法体(由外到内)。
package demo.service
import ace.framework.*
@Service
class ExternalHttpService {
@Timed
@CircuitBreaker[5] // 连续 5 次失败后熔断
@Retry[2] // 每次请求失败最多重试 2 次
func callUpstream(endpoint: String): String {
let resp = httpClient.get(endpoint)
if (resp.statusCode >= 500) {
throw Exception("上游服务错误: ${resp.statusCode}")
}
return resp.body
}
}// Controller 中使用
@Controller["/api"]
class ProxyController {
@Inject
var svc!: ExternalHttpService
@Get["/proxy"]
func proxy(): String {
try {
return svc.callUpstream("https://upstream.example.com/data")
} catch (e: CircuitBreakerOpenException) {
// 熔断期间的降级响应
return """{"code":503,"msg":"服务暂时不可用,请稍后重试"}"""
}
}
}组合执行逻辑说明
@Timed最外层,无论成功失败都记录耗时。@CircuitBreaker若处于 OPEN 状态,直接抛出CircuitBreakerOpenException,@Retry层不会重试熔断异常(避免无效重试加剧雪崩)。@Retry只重试方法体本身抛出的业务异常。
注意事项
@Retry 与幂等性
重试意味着方法可能被多次执行。务必确保被 @Retry 标注的方法是幂等的(如 GET 查询、带幂等键的写操作),避免重复扣款、重复入库等副作用。
@CircuitBreaker 不跨实例共享
熔断器状态存储在单进程内存中,多实例部署时各节点独立计数。若需集群级熔断,请在业务层结合分布式计数器(Redis)实现,或使用服务网格(Envoy/Istio)的熔断能力。
监控集成
aceTimedStats 数据可在 /actuator/metrics 端点聚合输出(需引入 ace-actuator 模块)。生产环境建议将 @Timed 数据推送到 Prometheus,配合 Grafana 可视化。