Spring AOP實(shí)現(xiàn)斷路器方式
環(huán)境:Spring5.3.23
1. 概述
Spring Cloud體系中,斷路器有Hystrix,Resilience4j,Sentinel等組件,它們的核心功能是當(dāng)某個(gè)服務(wù)不可用時(shí),斷路器會(huì)屏蔽相關(guān)故障,返回一個(gè)用戶預(yù)設(shè)的fallback。
具體來(lái)說(shuō),斷路器有以下一些作用:
- 阻止故障的向上傳遞:對(duì)服務(wù)的健康狀況進(jìn)行監(jiān)控和防護(hù)。
- 對(duì)故障快速失敗并積極回復(fù):回退并優(yōu)雅降級(jí)。
- 提供三種容錯(cuò)方式來(lái)幫助達(dá)成目標(biāo):資源隔離,熔斷和降級(jí)。
總的來(lái)說(shuō),斷路器的核心作用是增強(qiáng)分布式系統(tǒng)的彈性,避免級(jí)聯(lián)故障,以提高系統(tǒng)的整體可用性。
2. 實(shí)現(xiàn)方案
我們將通過(guò)使用AOP和自定義注解,實(shí)現(xiàn)斷路器功能。根據(jù)自己的需要在關(guān)鍵的方法上添加注解,然后在運(yùn)行時(shí)通過(guò)AOP攔截這些注解,并執(zhí)行相應(yīng)的斷路器邏輯。
斷路器的主要作用是防止故障的擴(kuò)散,并保護(hù)系統(tǒng)的穩(wěn)定性。當(dāng)某個(gè)服務(wù)出現(xiàn)故障時(shí),斷路器可以快速中斷與該服務(wù)的連接,并返回一個(gè)預(yù)設(shè)的fallback響應(yīng),從而避免故障對(duì)整個(gè)系統(tǒng)的影響。
通過(guò)自定義注解和AOP的結(jié)合,我們可以實(shí)現(xiàn)以下功能:
在需要的接口上添加自定義注解,注解中可以包含與斷路器相關(guān)的配置信息,如:錯(cuò)誤次數(shù),時(shí)間窗口等。
通過(guò)AOP攔截這些注解,并在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建斷路器。
當(dāng)服務(wù)調(diào)用時(shí),斷路器會(huì)根據(jù)配置的邏輯判斷是否需要中斷連接或返回fallback響應(yīng)。
如果服務(wù)正常,斷路器將不會(huì)進(jìn)行任何操作;如果服務(wù)故障,斷路器將根據(jù)預(yù)設(shè)的邏輯進(jìn)行處理。
3. 代碼實(shí)現(xiàn)
3.1 自定義注解
AOP只會(huì)攔截該注解的方法或類。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface PackFuse {
/**降級(jí)方法*/
String fallback() default "" ;
/**失敗次數(shù)*/
int fails() default 5 ;
/**窗口時(shí)間:s*/
int windowSize() default 10 ;
}3.2 自定義斷路器的狀態(tài)
斷路器的狀態(tài)有以下幾種:
- Closed(關(guān)閉狀態(tài)):默認(rèn)情況下,斷路器處于關(guān)閉狀態(tài),允許遠(yuǎn)程服務(wù)調(diào)用正常進(jìn)行。
- Open(打開(kāi)狀態(tài)):當(dāng)遠(yuǎn)程服務(wù)調(diào)用失敗次數(shù)達(dá)到預(yù)設(shè)的閾值時(shí),斷路器會(huì)自動(dòng)打開(kāi),中斷與該服務(wù)的所有調(diào)用,并返回fallback響應(yīng)。
- Half-Open(半開(kāi)狀態(tài)):在一段時(shí)間后,斷路器會(huì)自動(dòng)從Open狀態(tài)轉(zhuǎn)換到Half-Open狀態(tài)。在Half-Open狀態(tài)下,斷路器會(huì)嘗試少量請(qǐng)求以測(cè)試服務(wù)是否已恢復(fù)。如果測(cè)試請(qǐng)求成功,斷路器將自動(dòng)關(guān)閉并恢復(fù)到Closed狀態(tài);否則,將保持Half-Open狀態(tài),如果超過(guò)指定的錯(cuò)誤次數(shù),則再次轉(zhuǎn)變?yōu)镺pen狀態(tài)。
狀態(tài)定義
public enum EnumState {
CLOSE, HALF_OPEN, OPEN ;
}每個(gè)斷路器都會(huì)自己的狀態(tài)
public class PackFuseState {
/**當(dāng)前狀態(tài)*/
private EnumState state = EnumState.CLOSE ;
/**失敗次數(shù)*/
private AtomicInteger failCount = new AtomicInteger(0) ;
/**最大失敗次數(shù)*/
private int maxFailCount = 5 ;
/**窗口大?。荒J(rèn)每10秒重置*/
private int windowTime = 10 ;
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1)) ;
private Object lock = new Object() ;
public PackFuseState(int maxFailCount, int windowTime) {
this.maxFailCount = maxFailCount ;
this.windowTime = windowTime ;
executor.execute(() -> {
while(true) {
if (state == EnumState.CLOSE) {
try {
TimeUnit.SECONDS.sleep(windowTime) ;
if (state == EnumState.CLOSE) {
failCount.set(0) ;
}
} catch (InterruptedException e) {
e.printStackTrace() ;
}
} else {
synchronized (lock) {
try {
lock.wait() ;
} catch (InterruptedException e) {
e.printStackTrace() ;
}
}
}
}
}) ;
}
public EnumState getState() {
return state;
}
public void setState(EnumState state) {
this.state = state;
}
public AtomicInteger getFailCount() {
return failCount;
}
public void setFailCount(AtomicInteger failCount) {
this.failCount = failCount;
}
public int getwindowTime() {
return windowTime;
}
public void setwindowTime(int windowTime) {
this.windowTime = windowTime;
}
public PackFuseState addFailCount() {
int count = this.failCount.incrementAndGet() ;
if (count >= maxFailCount) {
this.setState(EnumState.OPEN) ;
executor.execute(() -> {
try {
TimeUnit.SECONDS.sleep(windowTime) ;
setState(EnumState.HALF_OPEN) ;
failCount.set(0) ;
} catch (InterruptedException e) {
e.printStackTrace() ;
}
}) ;
}
return this ;
}
public PackFuseState closeState() {
this.setState(EnumState.CLOSE) ;
this.failCount.set(0) ;
return this ;
}
public Object getLock() {
return lock;
}
}3.3 切面定義
該切面攔截所有標(biāo)有@PackFuse注解的方法
@Aspect
@Component
public class PackFuseAspect {
private static final Map<String, PackFuseState> META_HOLDER_MAP = new ConcurrentHashMap<>() ;
private static final Map<String, Object> FALLBACK = new ConcurrentHashMap<>() ;
private static final String DEFAULT_RET_DATA = "服務(wù)不可用" ;
@Pointcut("@annotation(fuse)")
private void fuse(PackFuse fuse) {}
@Around("fuse(fuse)")
public Object packFuse(ProceedingJoinPoint pjp, PackFuse fuse) {
MethodSignature joinPointObject = (MethodSignature) pjp.getSignature() ;
Class<?> targetType = joinPointObject.getDeclaringType() ;
Method method = joinPointObject.getMethod() ;
String targetKey = getKey(targetType, method);
String fallback = fuse.fallback() ;
if (!FALLBACK.containsKey(targetKey)) {
if (StringUtils.hasLength(fallback)) {
try {
Method fallbackMethod = targetType.getDeclaredMethod(fallback) ;
FALLBACK.put(targetKey, fallbackMethod.invoke(pjp.getTarget())) ;
} catch (Exception e) {
e.printStackTrace() ;
}
} else {
FALLBACK.put(targetKey, DEFAULT_RET_DATA) ;
}
}
int fails = fuse.fails() ;
int windowSize = fuse.windowSize() ;
PackFuseState fuseState = null ;
try {
fuseState = META_HOLDER_MAP.computeIfAbsent(targetKey, key -> new PackFuseState(fails, windowSize)) ;
switch (fuseState.getState()) {
case CLOSE:
return pjp.proceed() ;
case HALF_OPEN:
Random rd = new Random() ;
int c = rd.nextInt(fails) ;
if (c >= (fails / 2)) {
Object ret = pjp.proceed() ;
fuseState.closeState() ;
synchronized (fuseState.getLock()) {
fuseState.getLock().notifyAll() ;
}
return ret ;
}
return FALLBACK.get(targetKey) ;
case OPEN:
return FALLBACK.get(targetKey) ;
}
} catch (Throwable e) {
fuseState.addFailCount() ;
}
return FALLBACK.get(targetKey) ;
}
private String getKey(Class<?> targetType, Method method) {
StringBuilder builder = new StringBuilder();
builder.append(targetType.getSimpleName());
builder.append('#').append(method.getName()).append('(');
if (method.getParameterTypes().length > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.append(')').toString().replaceAll("[^a-zA-Z0-9]", "") ;
}
}以上就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的斷路器功能。
通過(guò)使用AOP+自定義注解的方式成功地實(shí)現(xiàn)了斷路器功能。這種方法給予了我們很大的靈活性和擴(kuò)展性,可以輕松地對(duì)特定的服務(wù)進(jìn)行故障隔離,避免故障擴(kuò)散,保護(hù)整個(gè)系統(tǒng)的穩(wěn)定性。同時(shí),通過(guò)自定義注解,我們能夠清晰地定義斷路器的配置和邏輯,使代碼更易于閱讀和維護(hù)。
這里只是一個(gè)非常簡(jiǎn)單的小例子給大家一個(gè)實(shí)現(xiàn)的思路,大家可以根據(jù)自己的想法或者結(jié)合Hystrix的實(shí)現(xiàn)來(lái)豐富功能。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java設(shè)計(jì)模式之動(dòng)態(tài)代理
今天小編就為大家分享一篇關(guān)于Java設(shè)計(jì)模式之動(dòng)態(tài)代理,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
Springboot創(chuàng)建時(shí)常用的依賴詳解
本文介紹了Spring?Boot項(xiàng)目中常用依賴的配置及作用,涵蓋了父依賴、Web應(yīng)用、測(cè)試、數(shù)據(jù)庫(kù)、MyBatis、連接池、JSON處理、Lombok、AOP、校驗(yàn)、監(jiān)控、工具包、打包配置、多配置文件以及熱部署等2025-03-03
JSP服務(wù)器端和前端出現(xiàn)亂碼問(wèn)題解決方案
這篇文章主要介紹了JSP服務(wù)器端和前端出現(xiàn)亂碼問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Spring?Boot深入學(xué)習(xí)數(shù)據(jù)訪問(wèn)之Spring?Data?JPA與Hibernate的應(yīng)用
Spring?Data?JPA是Spring?Data的子項(xiàng)目,在使用Spring?Data?JPA之前,先了解一下Hibernate,因?yàn)镾pring?Data?JPA是由Hibernate默認(rèn)實(shí)現(xiàn)的2022-10-10
java性能優(yōu)化之編譯器版本與平臺(tái)對(duì)應(yīng)關(guān)系
這篇文章主要介紹了java性能優(yōu)化--編譯器版本與平臺(tái)對(duì)應(yīng)關(guān)系,本章節(jié)更加具體化的學(xué)習(xí)編譯器還有哪些可以優(yōu)化的方便,讓你的應(yīng)用展現(xiàn)出更好的性能,需要的朋友可以參考下2022-06-06
Spring Data JPA踩坑記錄(@id @GeneratedValue)
這篇文章主要介紹了Spring Data JPA踩坑記錄(@id @GeneratedValue),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07

