專案中一些服務需要監聽其他微服務的啟動資訊,需要監聽到啟動後主動向其發請求拉取一些配置等。
可是eureka-client
並未提供監聽其他服務啟動的事件,eureka-server
倒是提供了事件,
可以在自己的eureka-server
中監聽服務啟動,監聽後傳送服務啟動資訊到kafka這些訊息佇列,服務監聽kafka訊息,
這種方式要依賴訊息佇列: 原始碼;
或者改造eureka-client
, 由於需要改的程式碼不多,修改原始碼重新打成依賴自己維護不方便,這裡通過javassist直接修改jar裡的位元組碼實現。
使用javassist修改位元組碼
- 檢視eureka裡原始碼,有個com.netflix.discovery.shared.Application類,addInstance方法在服務上線或更新,removeInstance方法在服務下線時呼叫,因此修改這倆方法,實現監聽服務上、下線。
- 由於spring-boot自己實現的類載入機制,以spring-boot的jar形式執行javassist會掃描不到包,要通過insertClassPath新增掃描路徑。
- 通過setBody修改方法體,分別新增me.flyleft.eureka.client.event.EurekaEventHandler.getInstance().eurekaAddInstance($1);和me.flyleft.eureka.client.event.EurekaEventHandler.getInstance().eurekaRemoveInstance($1);
- 通過toClass覆蓋原有類後,通過類載入器重新載入。
public void init() {
try {
ClassPool classPool = new ClassPool(true);
//新增com.netflix.discovery包的掃描路徑
ClassClassPath classPath = new ClassClassPath(Applications.class);
classPool.insertClassPath(classPath);
//獲取要修改Application類
CtClass ctClass = classPool.get(APPLICATION_PATH);
//獲取addInstance方法
CtMethod addInstanceMethod = ctClass.getDeclaredMethod("addInstance");
//修改addInstance方法
addInstanceMethod.setBody("{instancesMap.put($1.getId(), $1);"
+ "synchronized (instances) {me.flyleft.eureka.client.event.EurekaEventHandler.getInstance().eurekaAddInstance($1);" +
"instances.remove($1);instances.add($1);isDirty = true;}}");
//獲取removeInstance方法
CtMethod removeInstanceMethod = ctClass.getDeclaredMethod("removeInstance");
//修改removeInstance方法
removeInstanceMethod.setBody("{me.flyleft.eureka.client.event.EurekaEventHandler.getInstance().eurekaRemoveInstance($1);this.removeInstance($1, true);}");
//覆蓋原有的Application類
ctClass.toClass();
//使用類載入器重新載入Application類
classPool.getClassLoader().loadClass(APPLICATION_PATH);
Class.forName(APPLICATION_PATH);
} catch (Exception e) {
throw new EurekaEventException(e);
}
}
複製程式碼
- 放入main函式,在spring boot啟動前執行或者使用spring boot的事件,在spring bean初始化之前執行。(確保在eureka第一次執行之前執行即可)
@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {
public static void main(String[] args) {
//先執行修改位元組碼程式碼
EurekaEventHandler.getInstance().init();
new SpringApplicationBuilder(EurekaClientApplication.class).web(true).run(args);
}
}
複製程式碼
使用JDK中Observable和Observer實現觀察者,訂閱者模式
- 傳送事件使用java.util.Observable的setChanged和notifyObservers
public class EurekaEventObservable extends Observable {
public void sendEvent(EurekaEventPayload payload) {
setChanged();
notifyObservers(payload);
}
}
複製程式碼
- 接收事件使用使用java.util.Observer的update
public abstract class AbstractEurekaEventObserver implements Observer, EurekaEventService {
@Override
public void update(Observable o, Object arg) {
if (arg instanceof EurekaEventPayload) {
EurekaEventPayload payload = (EurekaEventPayload) arg;
if (InstanceInfo.InstanceStatus.UP.name().equals(payload.getStatus())) {
LOGGER.info("Receive UP event, payload: {}", payload);
} else {
LOGGER.info("Receive DOWN event, payload: {}", payload);
}
putPayloadInCache(payload);
consumerEventWithAutoRetry(payload);
}
}
}
複製程式碼
使用RxJava實現自動重試。
接收到服務啟動去執行一些操作,如果執行失敗有異常則自動重試指定次數,每個一段事件重試一次,執行成功則不再執行
private void consumerEventWithAutoRetry(final EurekaEventPayload payload) {
rx.Observable.just(payload)
.map(t -> {
// 此處為接收到服務啟動去執行的一些操作
consumerEvent(payload);
return payload;
}).retryWhen(x -> x.zipWith(rx.Observable.range(1, retryTime),
(t, retryCount) -> {
//異常處理
if (retryCount >= retryTime) {
if (t instanceof RemoteAccessException || t instanceof RestClientException) {
LOGGER.warn("error.eurekaEventObserver.fetchError, payload {}", payload, t);
} else {
LOGGER.warn("error.eurekaEventObserver.consumerError, payload {}", payload, t);
}
}
return retryCount;
}).flatMap(y -> rx.Observable.timer(retryInterval, TimeUnit.SECONDS)))
.subscribeOn(Schedulers.io())
.subscribe((EurekaEventPayload payload1) -> {
});
}
複製程式碼
新增手動重試失敗介面
自動重試失敗,可以手動重試,新增手動重試介面
@RestController
@RequestMapping(value = "/v1/eureka/events")
public class EurekaEventEndpoint {
private EurekaEventService eurekaEventService;
public EurekaEventEndpoint(EurekaEventService eurekaEventService) {
this.eurekaEventService = eurekaEventService;
}
@Permission(permissionLogin = true)
@ApiOperation(value = "獲取未消費的事件列表")
@GetMapping
public List<EurekaEventPayload> list(@RequestParam(value = "service", required = false) String service) {
return eurekaEventService.unfinishedEvents(service);
}
@Permission(permissionLogin = true)
@ApiOperation(value = "手動重試未消費成功的事件")
@PostMapping("retry")
public List<EurekaEventPayload> retry(@RequestParam(value = "id", required = false) String id,
@RequestParam(value = "service", required = false) String service) {
return eurekaEventService.retryEvents(id, service);
}
}
複製程式碼