springcloudBus 2.x 事件追蹤

松子無淚發表於2019-06-16

  最近由於工作需要,需要根據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裡面的一樣,否則會報一些莫名的錯誤,具體原因沒有細究,反正我是改了名字就可以。

相關文章