最近由於工作需要,需要根據springcloudconfigserver搭建自己的配置管理服務,關於怎么搭建,怎么刷新等通過搜索網上資料很快完成,但是為了使我們的配置服務更完美,開始研究如果有多個conifig client實例的時候,其中一個實例進行配置刷新的時候,怎么保證或者追蹤其他實例是否成功刷新,一開始網上搜索大致都說通過調用刷新端點(actuator/bus-refresh)后,調用端點(actuator/trace)就可以得到事件刷新紀錄,大致得到的json響應長這樣:
{
"timestamp": 1481098786017,
"info": {
"signal": "spring.cloud.bus.ack",
"event": "RefreshRemoteApplicationEvent",
"id": "66d172e0-e770-4349-baf7-0210af62ea8d",
"origin": "microservice-foo:8081",
"destination": "**"
}
},{
"timestamp": 1481098779073,
"info": {
"signal": "spring.cloud.bus.sent",
"type": "RefreshRemoteApplicationEvent",
"id": "66d172e0-e770-4349-baf7-0210af62ea8d",
"origin": "microservice-config-server:8080",
"destination": "**:**"
}
上述json報文表明RefreshRemoteApplicationEvent已從 microservice-config-server:8080發送,廣播到所有服務,並被microservice-foo:8081收到和確認。
但是當我啟動我的configclient的時候查看控制台日志,並沒有看到actuator暴露/actuator/trace接口。取而代之的是一個/actuator/httptrace接口,我嘗試訪問這個接口但是結果不是我想要的上述樣子,幾經查閱最終確認 actuator/trace是springbootactuator1.x版本才有的一個接口,不知什么原因在springboot2.x版本后官方取消了這個接口,以下代碼段是springcloudbus 2.x和1.x的tracelistener源碼
2.x
/*** Eclipse Class Decompiler plugin, copyright (c) 2016 Chen Chao (cnfree2000@hotmail.com) ***/
package org.springframework.cloud.bus.event;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.trace.http.HttpTraceRepository;
import org.springframework.cloud.bus.event.AckRemoteApplicationEvent;
import org.springframework.cloud.bus.event.SentApplicationEvent;
import org.springframework.context.event.EventListener;
public class TraceListener {
private static Log log = LogFactory.getLog(TraceListener.class);
private HttpTraceRepository repository;
public TraceListener(HttpTraceRepository repository) {
this.repository = repository;
}
@EventListener
public void onAck(AckRemoteApplicationEvent event) {
this.getReceivedTrace(event);
}
@EventListener
public void onSend(SentApplicationEvent event) {
this.getSentTrace(event);
}
protected Map<String, Object> getSentTrace(SentApplicationEvent event) {
LinkedHashMap map = new LinkedHashMap();
map.put("signal", "spring.cloud.bus.sent");
map.put("type", event.getType().getSimpleName());
map.put("id", event.getId());
map.put("origin", event.getOriginService());
map.put("destination", event.getDestinationService());
if (log.isDebugEnabled()) {
log.debug(map);
}
return map;
}
protected Map<String, Object> getReceivedTrace(AckRemoteApplicationEvent event) {
LinkedHashMap map = new LinkedHashMap();
map.put("signal", "spring.cloud.bus.ack");
map.put("event", event.getEvent().getSimpleName());
map.put("id", event.getAckId());
map.put("origin", event.getOriginService());
map.put("destination", event.getAckDestinationService());
if (log.isDebugEnabled()) {
log.debug(map);
}
return map;
}
}
1.x
package org.springframework.cloud.bus.event;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.context.event.EventListener;
public class TraceListener
{
private static Log log = LogFactory.getLog(TraceListener.class);
private TraceRepository repository;
public TraceListener(TraceRepository repository)
{
this.repository = repository;
}
@EventListener
public void onAck(AckRemoteApplicationEvent event) {
this.repository.add(getReceivedTrace(event));
}
@EventListener
public void onSend(SentApplicationEvent event) {
this.repository.add(getSentTrace(event));
}
protected Map<String, Object> getSentTrace(SentApplicationEvent event) {
Map map = new LinkedHashMap();
map.put("signal", "spring.cloud.bus.sent");
map.put("type", event.getType().getSimpleName());
map.put("id", event.getId());
map.put("origin", event.getOriginService());
map.put("destination", event.getDestinationService());
if (log.isDebugEnabled()) {
log.debug(map);
}
return map;
}
protected Map<String, Object> getReceivedTrace(AckRemoteApplicationEvent event) {
Map map = new LinkedHashMap();
map.put("signal", "spring.cloud.bus.ack");
map.put("event", event.getEvent().getSimpleName());
map.put("id", event.getAckId());
map.put("origin", event.getOriginService());
map.put("destination", event.getAckDestinationService());
if (log.isDebugEnabled()) {
log.debug(map);
}
return map;
}
}
可以觀察到的是其實兩個版本都有生成我們想要的追蹤記錄,只是在2.x版本並沒有把保存最終記錄的map集合添加到repository中,關鍵是即使add,2.x的代碼也不會成功,因為HttpTraceRepository並沒有一個map引用的屬性,不知道這是不是springcloud的一個bug。
其他探究過程不再贅述了,下面說下在springcloud2.x里面怎么實現事件追蹤(參考springcloud1.x版本中源碼)
1. 開啟bus相關屬性
spring:
application:
name: config-client1
cloud:
config:
label: master
profile: dev
uri: http://localhost:8888
bus: enabled: true trace: enabled: true ack: enabled: true
2. 建立自己的TraceRepository和Trace
package com.kevin.config.client; import java.util.List; import java.util.Map; public interface CustomeTraceRepository { List<CustomTrace> findAll(); void add(Map<String, Object> arg0); }
package com.kevin.config.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class CustomTraceRepositoryImpl implements CustomeTraceRepository {
private int capacity = 100;
private boolean reverse = true;
private final List<CustomTrace> traces = new LinkedList();
public void setReverse(boolean reverse) {
List arg1 = this.traces;
synchronized (this.traces) {
this.reverse = reverse;
}
}
public void setCapacity(int capacity) {
List arg1 = this.traces;
synchronized (this.traces) {
this.capacity = capacity;
}
}
public List<CustomTrace> findAll() {
List arg0 = this.traces;
System.out.println(this.traces.size());
synchronized (this.traces) {
return Collections.unmodifiableList(new ArrayList(this.traces));
}
}
public void add(Map<String, Object> map) {
CustomTrace trace = new CustomTrace(new Date(), map);
List arg2 = this.traces;
synchronized (this.traces) {
while (this.traces.size() >= this.capacity) {
this.traces.remove(this.reverse ? this.capacity - 1 : 0);
}
if (this.reverse) {
this.traces.add(0, trace);
} else {
this.traces.add(trace);
}
}
}
}
package com.kevin.config.client;
import java.util.Date;
import java.util.Map;
import org.springframework.util.Assert;
public final class CustomTrace {
private final Date timestamp;
private final Map<String, Object> info;
public CustomTrace(Date timestamp, Map<String, Object> info) {
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(info, "Info must not be null");
this.timestamp = timestamp;
this.info = info;
}
public Date getTimestamp() {
return this.timestamp;
}
public Map<String, Object> getInfo() {
return this.info;
}
}
3. 建立自己的監聽器,監聽SentApplicationEvent和AckRemoteApplicationEvent
package com.kevin.config.client; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.bus.event.AckRemoteApplicationEvent; import org.springframework.cloud.bus.event.SentApplicationEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @Component public class TraceListener { private static Log log = LogFactory.getLog(TraceListener.class); private CustomeTraceRepository repository; public TraceListener(CustomeTraceRepository repository) { this.repository = repository; } @EventListener public void onAck(AckRemoteApplicationEvent event) { this.getReceivedTrace(event); } @EventListener public void onSend(SentApplicationEvent event) { this.getSentTrace(event); } protected Map<String, Object> getSentTrace(SentApplicationEvent event) { LinkedHashMap map = new LinkedHashMap(); map.put("signal", "spring.cloud.bus.sent"); map.put("type", event.getType().getSimpleName()); map.put("id", event.getId()); map.put("origin", event.getOriginService()); map.put("destination", event.getDestinationService()); if (log.isDebugEnabled()) { log.debug(map); } this.repository.add(map); return map; } protected Map<String, Object> getReceivedTrace(AckRemoteApplicationEvent event) { LinkedHashMap map = new LinkedHashMap(); map.put("signal", "spring.cloud.bus.ack"); map.put("event", event.getEvent().getSimpleName()); map.put("id", event.getAckId()); map.put("origin", event.getOriginService()); map.put("destination", event.getAckDestinationService()); if (log.isDebugEnabled()) { log.debug(map); } this.repository.add(map); return map; } }
4. 讓spring管理建立的repository
@Bean
public CustomTraceRepositoryImpl customeTraceRepository() {
return new CustomTraceRepositoryImpl();
}
5 .最后暴露一個接口從repository獲取數據
@RequestMapping(value = "/trace") public List<CustomTrace> trace(HttpServletRequest request) throws IOException { return customeTraceRepository().findAll(); }
至此,就可以通過這個接口獲取事件追蹤記錄,本例子中repository默認存儲100條,如果超出就會覆蓋首尾的記錄,如果有其他特殊需求可以自行實現。
注意:在建立自己的repository和trace的時候類的名字最好不要和spring里面的一樣,否則會報一些莫名的錯誤,具體原因沒有細究,反正我是改了名字就可以。
