dubbo集成hystrix實踐

涉及概念java

服務等級(service-level):spring

核心(core)api

重要(important)緩存

普通(normal)服務器

次要(secondary)併發

非必需(dispensable)app

服務隔離框架

消費者的每一個消費的服務之間互相獨立,互不影響,不會由於某個服務的故障或者不可用形成其餘服務的故障或者不可用。異步

隔離策略jvm

線程池隔離: 使用線程池做爲隔離的實現方式,每一個隔離單元擁有本身單獨的線程池,調用依賴服務時,申請一個新的線程執行真正的調用邏輯,線程池或者隊列滿了以後,拒絕服務。

信號量隔離: 使用信號量做爲隔離的實現方式,每一個隔離單元擁有配置了本身的信號量閾值,調用依賴服務時,在原請求線程中申請新的信號量,若是申請到,繼續在原線程中執行調用邏輯,信號量超過閾值以後,拒絕服務。

服務限流

按照服務隔離的原則,對每一個服務的流量進行限制,不會由於某個或某幾個服務的請求量過大而形成其餘服務的不可用

服務熔斷

當消費方依賴的某個服務不可用時,動態的隔絕對該服務的依賴。消費方再也不繼續請求該服務,嘗試使用降級邏輯。當服務恢復可用時,能當即感知並恢復對該服務的依賴。

服務降級

消費方依賴的某個服務不可用(異常或者超時),須要採起的補償性措施。

dubbo與hystrix比較

dubbo的限流,降級方案

消費端經過配置acitves限制消費端調用的併發量,在達到最大併發量以後等待一個timeout時間再重試。

服務端經過配置executes限制服務端接口的線程最大數量,達到最大數量以後直接拋出異常。

超時配置,當超時且超太重試次數以後,拋出異常。消費方實現本身的降級邏輯。

當沒有可用的服務提供者以後,消費者直接短路,消費方實現本身的短路邏輯。

經過註冊中心的URL實現服務運行時參數的動態配置。

限流或隔離的粒度是以接口方法爲粒度。

dubbo自帶的監控不夠強大,須要本身擴展或者使用第三方擴展。

hystrix的限流,降級方案

自定義限流位置。

提供超時時間配置,當超時或者拋出非BadRequestException以後,其餘任何錯誤,異常或者超時時,嘗試降級邏輯。

對一段時間內的錯誤,超時率進行統計,達到配置的閾值時自動短路,調用降級邏輯。

服務短路後提供自動恢復機制,快速恢復服務。

經過內置的archaius或者第三方配置框架實現服務運行時參數的動態配置。

隔離粒度能夠自定義,模塊,接口,方法粒度都支持。

提供了基於event-stream的擴展工具和官方的dashboard進行監控。但目前官方提供的even-stream是基於servlet的

兩種方案的優劣

dubbo同時提供消費端和服務端的限流。hystrix只提供消費端限流。

dubbo的消費端限流的信號量是以服務器爲粒度,而hystrix的消費端限流是以整個提供方集羣爲粒度(更合理)。

dubbo不提供服務容錯降級後的自動短路。hystrix支持自動短路和自動恢復。

dubbo管理平臺中的動態配置用通知的方式通知消費者,但存在不生效等一些bug。hystrix利用archaius的動態配置方案從本地或URL中輪詢拉取配置。

dubbo的限流實際上是基於信號量的,而hystrix同時支持信號量和線程池的限流。

hystrix與dubbo集成的方案

實現方式

在dubbo的消費端利用dubbo的filter對全部調用進行攔截擴展代碼以下: DubboHystrixFilter.java

public class DubboHystrixFilter implements Filter {

@Override

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

// 是否啓用hystrix

if (!hystrixIsOpen) {

return invoker.invoke(invocation);

}

String group = invoker.getUrl().getParameter(HystrixConstants.GROUP_KEY);

URL url = invoker.getUrl();

// 未配置groupKey的接口不進行限流

if (StringUtils.isBlank(group)) {

group = invoker.getUrl().getParameter(Constants.ID_KEY);

}

// int serviceLevel = invoker.getUrl().getParameter(HystrixConstants.SERVICE_LEVEL_KEY, ServiceLevelEnum.NORMAL.getLevel());

DubboHystrixCommand command = new DubboHystrixCommand(invoker, invocation, group);

return command.execute();

}

}

DubboHystrixCommand.java

public class DubboHystrixCommand extends HystrixCommand<Result>

{ // 統計必定時間內成功請求數

private static final int STATUSTIME = 20000; // 用於計算百分比的滾動窗口時間長度(毫秒)

private static final int ROLLINGTIME = 60000;

private Invoker<?> invoker;

private Invocation invocation;

public DubboHystrixCommand(Invoker<?> invoker, Invocation invocation, String group) {

// 使用dubbo配置的優先級 method > interface > application 同等級別 consumer > provider

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(group)) // 組名使用模塊名稱

// 服務等級爲NORMAL的隔離粒度爲模塊,其餘服務等級的隔離粒度爲接口

.andCommandKey(HystrixCommandKey.Factory.asKey(group))

.andCommandPropertiesDefaults(

HystrixCommandProperties.Setter()

.withMetricsRollingPercentileWindowInMilliseconds(ROLLINGTIME)

.withMetricsRollingStatisticalWindowInMilliseconds(STATUSTIME) // 使用信號量隔離的方式

.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

// 最大併發量,配置的優先級爲

.withExecutionIsolationSemaphoreMaxConcurrentRequests(invoker.getUrl().getParameter(

HystrixConstants.CONCURRENCY_KEY, HystrixConstants.DEFAULT_MAX_CONCURRENCY))

.withExecutionTimeoutEnabled(false) // 是否開啓熔斷功能

.withCircuitBreakerEnabled(true)

)); this.invoker = invoker; this.invocation = invocation;

}

@Override protected Result run() throws Exception

{ return invoker.invoke(invocation);

}

@Override protected Result getFallback() { if (executionResult.isResponseSemaphoreRejected()) {

Map<String, Object> map = new HashMap<String, Object>(); map.put("resultCode", DHAPCode.COM_FLOW_OVERRUN.getCode()); map.put("resultMsg", DHAPCode.COM_FLOW_OVERRUN.getMsg());

Result result = new RpcResult(map); return result;

}

Map<String, Object> map = new HashMap<String, Object>(); map.put("resultCode", DHAPCode.COM_SERVER_ERROR.getCode()); map.put("resultMsg", DHAPCode.COM_SERVER_ERROR.getMsg());

Result result = new RpcResult(map); return result;

}

}

配置

xmlns:hystrix="http://www.springframework.org/schema/p"<dubbo:consumer timeout="60000" check="false" filter="hystrixFilter,consumerLogFilter" hystrix:concurrency="40" />

<dubbo:reference id="getUserInfoByIdService" interface="com.cmiot.ums.api.user.GetUserInfoByIdService" hystrix:hgroup="ums" />

HystrixCommand和HystrixObservableCommand

HystrixObservableCommand只支持異步的調用方式,HystrixCommand同步異步都支持。

HystrixObservableCommand支持請求合併功能,HystrixCommand不支持。

隔離粒度

對於未配置hystrix:hgroup的消費者不進行限流和熔斷,對於配置了hystrix:hgroup的消費方,默認的最大併發量爲40,隔離粒度根據配置自定義。考慮引入服務等級的概念,對於重要的服務默認採用接口級別的隔離粒度,對於非重要框架,每一個模塊的每一個等級進行隔離,實現對每一個服務等級進行動態調整。當服務器資源不夠用時,可臨時限制或關閉非核心服務的功能。

動態配置

嘗試了使用單獨的配置文件去管理hystrix的配置,但因爲咱們須要使用dubbo的url中的參數對服務進行分組,所以若是用獨立的配置文件,配置會比較分散,不易於維護。所以仍然利用dubbo的配置,dubbo的配置也有不少方式,在消費端配置或者在服務端配置,用單一的註冊中心配置仍是分模塊的多註冊中心配置。最終咱們仍然決定用在消費端的單一註冊中心配置。理由以下:

服務治理上,服務消費方更清楚服務的使用場景,包括併發量,重要性等,如何降級,容錯等等。所以,配置在服務消費方比在提供方更合理。

爲了解決在服務消費方沒法對所使用的服務進行邏輯上的分組,方便分組的統一配置,曾考慮使用多註冊中心的方式,按照每一個模塊使用單獨的註冊中配置,可是須要每一個服務提供方都去修改註冊中心,改動較大,暫時不採用。

當隔離粒度爲模塊時,若是須要變動模塊的配置,目前不太方便,須要對消費方這個分組內的全部接口配置,而且可能因爲配置時的疏忽形成某個接口與其餘接口的配置不一樣。

hystrix-dashboard

因爲hystrix原生的event-stream是基於servlet容器的,應用平臺未使用基於servlet容器的方案,所以對event-stream進行了擴展,方便對接口運行情況進行實時監控統計。 注意hystrix-dashborad在監控過程當中的請求會被handle住,所以須要配置最大鏈接數

隔離策略

線程池策略比較信號量的優點是可以以非阻塞的方式進行調用,而且經過對單個接口的壓測顯示性能稍好於信號量。同時,線程池的方式支持緩衝隊列。

信號量比較線程池策略的優點是:相比較動態調整大小的開銷比較小,通過對單個接口的測試,對CPU的消耗比線程池小。

兩種方式混用

hystrix關鍵配置

組名(commandGroupKey)

用於統計報表,通知,儀表盤或服務歸屬之類的,使用應用平臺模塊名稱做爲commandGroupKey。

命令名(commandKey)

用於監控,熔斷器開關,緩存等做用,也是比較關鍵的,決定了隔離的粒度,缺省默認使用類名做爲key,即根據當前類名做爲隔離粒度,但因爲集成採用公用的dubbofilter的機制,全部的名稱都同樣,應用平臺根據服務等級,大於normal等級的使用接口做爲隔離粒度。其餘使用模塊級別的隔離粒度。

命令配置(commandProperties)

包含隔離策略配置,線程池大小,信號量大小,超時配置,熔斷功能配置,降級配置,監控配置等關鍵配置信息,每一個隔離的單元使用獨立的配置。

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(group)) // 組名使用模塊名稱

// 服務等級爲NORMAL的隔離粒度爲模塊,其餘服務等級的隔離粒度爲接口

.andCommandKey(HystrixCommandKey.Factory.asKey(group))

// 通用配置

.andCommandPropertiesDefaults( HystrixCommandProperties.Setter()

// 是否開啓熔斷

.withCircuitBreakerEnabled(true)

// 觸發熔斷的錯誤率

.withCircuitBreakerErrorThresholdPercentage(50)

// 強制關閉熔斷器

.withCircuitBreakerForceClosed(false)

// 強制打開熔斷器

.withCircuitBreakerForceOpen(false)

// 觸發熔斷器須要的請求量

.withCircuitBreakerRequestVolumeThreshold(20)

// 熔斷器從打開到半開的等待時間

.withCircuitBreakerSleepWindowInMilliseconds(5000)

// 使用信號量隔離的方式 默認:線程池

.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

// .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)

// 信號量閾值 默認:10

.withExecutionIsolationSemaphoreMaxConcurrentRequests(invoker.getUrl().getParameter(HystrixConstants.CONCURRENCY_KEY, HystrixConstants.DEFAULT_MAX_CONCURRENCY))

// // 是否開啓超時時間中斷拋出異常的功能

.withExecutionTimeoutEnabled(false)

// 超時後是否中斷線程

// .withExecutionIsolationThreadInterruptOnTimeout(true)

// // 超時時間 默認:1000// .withExecutionTimeoutInMilliseconds(invoker.getUrl().getParameter(Constants.TIMEOUT_KEY, HystrixConstants.DEFAULT_TIMEOUT_MILLSECOND))

// 是否開啓降級

.withFallbackEnabled(true)

// 信號量隔離時,容許請求降級的最大併發數

.withFallbackIsolationSemaphoreMaxConcurrentRequests(10)

// 計算錯誤率的間隔時間

.withMetricsHealthSnapshotIntervalInMilliseconds(500)

// 設置每一個bucket內執行的次數,若是超過這個次數,丟棄最先的,加入最新的

.withMetricsRollingPercentileBucketSize(100)

// 是否開啓監控統計功能,若是設置false,任何統計都返回-1

.withMetricsRollingPercentileEnabled(true)

// 用於計算百分比的滾動窗口內buckets的個數

.withMetricsRollingPercentileWindowBuckets(6)

// 用於計算百分比的滾動窗口時間長度

.withMetricsRollingPercentileWindowInMilliseconds(60000)

// 可統計的滾動窗口內的buckets數量,用於熔斷器和指標發佈

.withMetricsRollingStatisticalWindowBuckets(10)

// 可統計的滾動窗口的時間長度,這段時間內的執行數據用於熔斷器和指標發佈

.withMetricsRollingStatisticalWindowInMilliseconds(10000)

// 是否開啓緩存

.withRequestCacheEnabled(true)

// 是否開啓日誌

.withRequestLogEnabled(true)

)

// 線程池策略時的配置

// .andThreadPoolPropertiesDefaults(// HystrixThreadPoolProperties.Setter()

// .withCoreSize(10)

// .withKeepAliveTimeMinutes(5)

// .withMaxQueueSize(-1)

// .withMetricsRollingStatisticalWindowBuckets(10)

// .withMetricsRollingStatisticalWindowInMilliseconds(10)

// .withQueueSizeRejectionThreshold(20)

// )

);

springMVC與Hystrix

爲每一個url或者servlet實現單獨的HystrixCommand,來達到隔離,限流,熔斷,降級的目的

在servlet中的公共filter中實現

在調用內部或者外部服務時用aop的方式實現。

hystrix依賴

hystrix-core中只有三個依賴

archaius-core,用來實現動態配置,支持擴展第三方工具

rxjava,是hystrix最核心的一部分,是實現異步調用的核心,基於觀察者模式的擴展,支持基於jvm的語言,例如:scala,groovy等。

HdrHistogram,在hystrix運行時對相關的metrixs進行收集,支持擴展第三方工具

相關文章
相關標籤/搜索