meerkat 是用於服務監控以及服務降級基礎組件,主要爲了解決調用外部接口的時候進行成功率,響應時間,QPS指標的監控,同時在成功率降低到預設的閾值如下的時候自動切斷外部接口的調用,外部接口成功率恢復後自動恢復請求。本文將對使用方式以及進階特性進行介紹。java
項目主頁: https://github.com/ChanningBJ...git
在咱們的Java服務中,常常會調用外部的一些接口進行數據的獲取操做,當這些外部接口的成功率比較低的時候會直接影響到服務自己的成功率,所以咱們添加了對外部接口調用的成功率和響應時間監控,這樣能夠在形成大量用戶影響以前預先發現並解決問題。同時,對於接口中的非關鍵數據,咱們採起了更具成功率判斷進行觸發熔斷的方式,當成功率降低到預約的閥值如下的時候自動中止對這個外部接口的訪問以便保證關鍵數據可以正常提供,當成功率恢復之後自動恢復請求。github
<dependency> <groupId>com.github.channingbj</groupId> <artifactId>meerkat</artifactId> <version>1.2</version> </dependency>
假設咱們的服務中須要從HTTP接口查詢一個節目的播放次數,爲了防止這個HTTP接口大量超時影響咱們自身服務的質量,能夠定義一個查詢Command:apache
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { this.videoID = videoID; } protected Optional<Long> run() { Long result = 0l; // 調用HTTP接口獲取視頻的播放次數信息 // 若是調用失敗,返回 null 或者拋出異常,會將此次操做記錄爲失敗 // 若是ID非法,返回 Optional.absent(),會將此次操做記錄爲成功 return Optional.fromNullable(result); } }
執行查詢:segmentfault
//獲取視頻ID爲123的視頻的播放次數 GetPlayCountCommand command = new GetPlayCountCommand(123l); Long result = command.execute(); // 執行查詢操做,若是執行失敗或者處於熔斷狀態,返回 null
在服務初始化的時候須要對監控上報進行設置。下面的例子中開啓了監控數據向日志文件的打印服務器
MeterCenter.INSTANCE .enableReporter(new EnablingLogReporter("org.apache.log4j.RollingFileAppender")) .init();
統計結果會以熔斷命令類名爲進行分組。例如前面咱們定義的 GetPlayCountCommand 類,package name 是 com.test,那麼在日誌中的輸出將會是這個樣子:app
type=GAUGE, name=com.test.GetPlayCountCommand.normal-rate, value=0.0 type=GAUGE, name=com.test.GetPlayCountCommand.success-rate, value=61.0 type=TIMER, name=com.test.GetPlayCountCommand.time, count=25866500, min=0.0, max=0.001, mean=3.963926781047921E-5, stddev=1.951102156677818E-4, median=0.0, p75=0.0, p95=0.0, p98=0.001, p99=0.001, p999=0.001, mean_rate=649806.0831335272, m1=1665370.7316699813, m5=2315813.300713087, m15=2446572.324069477, rate_unit=events/second, duration_unit=milliseconds
監控項 | 含義 |
---|---|
[classname].success-rate | 成功率 |
[classname].time.m1 | QPS |
[classname].time.mean | 平均響應時間 |
[classname].normal-rate | 過去1分鐘內處於正常訪問(非熔斷)的時間比例 |
若是不想使用熔斷功能,只是想監控Java方法調用的耗時和成功率,能夠直接使用 OperationMeter 進行實現,只須要在函數調用的先後添加開始和結束的調用便可:maven
//建立一個操做的計數器 OperationMeter meter = MeterCenter.INSTANCE.getOrCreateMeter(OperationMeterTest.class, OperationMeter.class); //模擬成功率60% for(int k=0; k<100; k++){ Timer.Context context = meter.startOperation(); if(k%10<6){ meter.endOperation(context, OperationMeter.Result.SUCCESS); } else { meter.endOperation(context, OperationMeter.Result.FAILURE); } }
# 開啓熔斷並配置閥值和持續時間ide
首先建立一個接口,繼承自FusingConfig,用於指定配置文件的加載路徑,同時還能夠設定配置文件的刷新時間,具體定義方法請參照 owner 文檔函數
@Config.Sources("classpath:app_config.properties") @Config.HotReload( value = 1, unit = java.util.concurrent.TimeUnit.MINUTES, type = Config.HotReloadType.ASYNC) public interface APPFusingConfig extends FusingConfig { }
建立查詢Command的時候在構造函數中傳入
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { super( APPFusingConfig.class); //設定配置文件 this.videoID = videoID; } protected Optional<Long> run() { Long result = 0l; // 調用HTTP接口獲取視頻的播放次數信息 // 若是調用失敗,返回 null 或者拋出異常,會將此次操做記錄爲失敗 // 若是ID非法,返回 Optional.absent(),會將此次操做記錄爲成功 return Optional.fromNullable(result); } }
配置文件內容以下:
監控項 | 含義 | 默認值 |
---|---|---|
fusing.[CommandClassName].mode | 熔斷模式: FORCE_NORMAL-關閉熔斷功能; AUTO_FUSING-自動進入熔斷模式; FORCE_NORMAL-強制進行熔斷 |
FORCE_NORMAL |
fusing.[CommandClassName].duration | 觸發一次熔斷之後持續的時間,支持ms,sec,min 單位。例如 10sec | 50sec |
fusing.[CommandClassName].success_rate_threshold | 觸發熔斷的成功率閥值,下降到這個成功率如下將觸發熔斷,例如0.9表示成功率90% | 0.9 |
配置文件中的 CommandClassName 是每一個操做類的名稱,能夠爲每一個操做單獨設置上述參數。同時,這個配置文件支持動態加載,樂意經過修改fusing.[CommandClassName].mode 手工觸發或者關閉熔斷。
咱們的服務中使用的是Metric+Graphite+Gafana進行監控數據的採集存儲和展示,下面將介紹如何配置監控數據上報Grafana,關於Graphite+Grafana的配置,能夠參考文章:使用graphite和grafana進行應用程序監控
首先定義一個接口,繼承自GraphiteReporterConfig,經過這個接口定義配置文件的加載路徑。配置文件路徑的定義方法請參照 owner 文檔, 下面是一個例子:
@Config.Sources("classpath:config.properties") public interface MyConfig extends GraphiteReporterConfig { }
配置文件中須要定義下列內容:
配置項 | 含義 |
---|---|
meter.reporter.enabled.hosts | 開啓監控上報的服務器列表 |
meter.reporter.perfix | 上報使用的前綴 |
meter.reporter.carbon.host | grafana(carbon-cache) 的 IP 地址,用於存儲監控數據 |
meter.reporter.carbon.port | grafana(carbon-cache) 的端口 |
下面這個例子是在192.168.0.0.1和192.168.0.0.2兩臺服務器上開啓監控數據上報,上報監控指標的前綴是project_name.dc:
meter.reporter.enabled.hosts = 192.168.0.0.1,192.168.0.0.2 meter.reporter.perfix = project_name.dc meter.reporter.carbon.host = hostname.graphite
因爲相同機房的不一樣服務器對外部接口的訪問狀況通常比較相似,因此僅選取部分機器上報,也是爲了節省資源。僅選擇部分機器上報不影響熔斷效果。
在服務初始化的時候須要對監控上報進行設置。下面的例子中開啓了監控數據向日志文件的打印,同時經過MyConfig指定的配置文件加載Graphite配置信息。
MeterCenter.INSTANCE .enableReporter(new EnablingLogReporter("org.apache.log4j.RollingFileAppender")) .enableReporter(new EnablingGraphiteReporter(MyConfig.class)) //監控數據上報Grafana .init();
統計結果會以熔斷命令類名爲進行分組。例如前面咱們定義的 GetPlayCountCommand 類,package name 是 com.test,那麼在日誌中的輸出將會是這個樣子:
type=GAUGE, name=com.test.GetPlayCountCommand.normal-rate, value=0.0 type=GAUGE, name=com.test.GetPlayCountCommand.success-rate, value=61.0 type=TIMER, name=com.test.GetPlayCountCommand.time, count=25866500, min=0.0, max=0.001, mean=3.963926781047921E-5, stddev=1.951102156677818E-4, median=0.0, p75=0.0, p95=0.0, p98=0.001, p99=0.001, p999=0.001, mean_rate=649806.0831335272, m1=1665370.7316699813, m5=2315813.300713087, m15=2446572.324069477, rate_unit=events/second, duration_unit=milliseconds
監控項 | 含義 |
---|---|
[classname].success-rate | 成功率 |
[classname].time.m1 | QPS |
[classname].time.mean | 平均響應時間 |
[classname].normal-rate | 過去1分鐘內處於正常訪問(非熔斷)的時間比例 |
在Grafanna中能夠看到下面的監控圖:
meerkat使用Metrics進行監控數據的統計,所以可使用Metrics支持的全部reporter進行上報。添加一種上報的時候,只須要實現 EnablingReporter 並在 MeterCenter 初始化以前進行調用便可。下面是log reporter的實現,能夠做爲參考
public class EnablingLogReporter implements EnablingReporter { private String loggername; public EnablingLogReporter(String loggername) { this.loggername = loggername; } @Override public void invoke(MetricRegistry metricRegistry, long period, TimeUnit timeUnit) { Slf4jReporter.forRegistry(metricRegistry) .outputTo(LoggerFactory.getLogger(loggername)) .convertRatesTo(java.util.concurrent.TimeUnit.SECONDS) .convertDurationsTo(java.util.concurrent.TimeUnit.MILLISECONDS) .build().start(period, timeUnit); } }
MeterCenter 初始化的時候開啓reporter
MeterCenter.INSTANCE .enableReporter(new EnablingLogReporter("org.apache.log4j.RollingFileAppender")) .init();
多實例監控主要是爲了解決一個被監控操做的實現類須要根據輸入參數的不一樣分別進行監控和熔斷的狀況,經過定義實例的名稱進行實現。例如獲取視頻播放次數的例子,獲取視頻播放次數的接口對於不一樣的視頻類型而言請求邏輯是同樣的,因此使用同一個類進行實現;可是對於不一樣的視頻類型,接口實現的複雜程度不一樣致使成功率不一樣,當用戶上傳的視頻的播次接口大量失敗的時候咱們不但願同時熔斷電影電視劇這類視頻的播放次數獲取,這時就須要使用多實例這種特性進行監控和熔斷。
下面是一個單實例的實現:
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { super( APPFusingConfig.class); this.videoID = videoID; } protected Optional<Long> run() { Long result = 0l; // 調用HTTP接口獲取視頻的播放次數信息 // 若是調用失敗,返回 null 或者拋出異常,會將此次操做記錄爲失敗 // 若是ID非法,返回 Optional.absent(),會將此次操做記錄爲成功 return Optional.fromNullable(result); } }
假設業務上咱們能夠根據視頻ID判斷視頻類型,能夠在類初始化的時候根據類型建立多種監控實例,添加了多實例支持的實現以下:
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { super( getVideoType(videoID), APPFusingConfig.class); this.videoID = videoID; } private static String getVideoType(Long videoID){ return "PGC"; //根據videoID進行判斷,返回 "PGC" 或者 "UGC" 這兩個類別 } protected Optional<Long> run() { Long result = 0l; // 調用HTTP接口獲取視頻的播放次數信息 // 若是調用失敗,返回 null 或者拋出異常,會將此次操做記錄爲失敗 // 若是ID非法,返回 Optional.absent(),會將此次操做記錄爲成功 return Optional.fromNullable(result); }
因爲每一個實例獨享一個監控指標,日誌中的監控個結果是這個樣子:
type=GAUGE, name=com.test.GetPlayCountCommand.PGC.normal-rate, value=100.0 type=GAUGE, name=com.test.GetPlayCountCommand.PGC.success-rate, value=100.0 type=GAUGE, name=com.test.GetPlayCountCommand.UGC.normal-rate, value=100.0 type=GAUGE, name=com.test.GetPlayCountCommand.UGC.success-rate, value=60.0 type=TIMER, name=com.test.GetPlayCountCommand.PGC.time, count=100, min=0.0, max=0.509, mean=0.00635, stddev=0.05052135687013958, median=0.001, p75=0.002, p95=0.002, p98=0.003, p99=0.003, p999=0.509, mean_rate=1.6680162586215173, m1=8.691964170141569, m5=16.929634497812284, m15=18.919189378135307, rate_unit=events/second, duration_unit=milliseconds type=TIMER, name=com.test.GetPlayCountCommand.UGC.time, count=100, min=0.0, max=0.027, mean=0.00132, stddev=0.0026939933184772376, median=0.001, p75=0.001, p95=0.002, p98=0.005, p99=0.006, p999=0.027, mean_rate=1.6715904477699361, m1=8.691964170141569, m5=16.929634497812284, m15=18.919189378135307, rate_unit=events/second, duration_unit=milliseconds
相應的,對熔斷閥值以及持續時間的配置也須要明確指出實例的名字:
fusing.GetPlayCountCommand.UGC.mode = AUTO_FUSING fusing.GetPlayCountCommand.UGC.duration = 50sec fusing.GetPlayCountCommand.UGC.success_rate_threshold = 0.9 fusing.GetPlayCountCommand.PGC.mode = AUTO_FUSING fusing.GetPlayCountCommand.PGC.duration = 50sec fusing.GetPlayCountCommand.PGC.success_rate_threshold = 0.9