異常和重試機制
異常
ClientException
ClientException代表明確的客戶端錯誤,比如以下錯誤碼:
錯誤碼 | 描述 |
---|---|
SDK.SDK_INVALID_PARAMETER | 使用了非法的參數值,請求尚未發送到服務端之前,被檢測到錯誤 |
SDK.SDK_ENDPOINT_RESOLVING_ERROR | 找不到合適的Endpoint,無法把請求發送到服務端 |
SDK.SDK_INVALID_REQUEST | 使用了框架無法處理的Request對象 |
ServerException
在HTTP API的使用過程中,對於status_code != 200的請求結果,服務端會提供明確的error_code作為錯誤碼。這些失敗或者錯誤的請求,SDK會通過ServerException來表示。
ServerException的錯誤碼和原因,請參考HTTP API的服務端文檔。
重試機制
不同的業務場景,需要的重試策略很可能不一致,SDK預置了重試策略,也允許自定義擴展和設置。
HTTP API
默認機制
- 默認不重試
- 設置了重試次數後,符合條件的請求會重試,不符合條件的請求不會重試,開啟重試的代碼示例如下:
- Python
- Java
from webullsdkcore.client import ApiClient
# 設置最大重試次數為3
client = ApiClient(app_key="<your_app_key>", app_secret="<your_app_secret>", region_id="<region_id>", auto_retry=True, max_retry_num=3)
HttpApiConfig apiConfig = HttpApiConfig.builder()
.appKey("<your_app_key>")
.appSecret("<your_app_secret>")
.regionId("<region_id>")
// 設置最大重試次數為3
.autoRetry(true)
.maxRetryNum(3)
.build();
TradeApiService apiService = new TradeHttpApiService(apiConfig);
默認重試的條件判斷說明:
- 超過最大重試次數,不重試
- 不是GET請求,不重試
- 客戶端IOError會重試,非特定的ClientException不重試
- 服務端的特定錯誤碼(如下)重試,非特定錯誤碼不重試
- TOO_MANY_REQUESTS
- SERVICE_NOT_AVAILABLE
- GATEWAY_TIMEOUT
- INTERNAL_ERROR
- 其他錯誤和異常,不重試
行情訂閱
默認機制
- 默認符合條件的請求會重試,不符合條件的請求不會重試,重試的次數沒有限制,重試的時間間隔為10秒,自定義重試的代碼示例如下:
- Python
- Java
from webullsdkmdata.quotes.subscribe.default_client import DefaultQuotesClient
from webullsdkcore.retry.retry_policy import NO_RETRY_POLICY
# 設置不重試
quotes_client = DefaultQuotesClient("<your_app_key>", "<your_app_secret>", "<region_id>", retry_policy=NO_RETRY_POLICY)
QuotesSubsClient client = QuotesSubsClient.builder()
.appKey("<your_app_key>")
.appSecret("<your_app_secret>")
.regionId("<region_id>")
// 設置不重試
.reconnectBy(RetryPolicy.never())
.build();
默認重試的條件判斷說明:
- 客戶端IOError會重試,非特定的ClientException不重試
- mqtt協議的特定錯誤碼(如下)重試,非特定錯誤碼不重試
- 3 (Server Unavailable)
- 5 (Authorization error)
- 其他錯誤和異常,重試
交易事件檢閱/行情API
默認機制
- 默認符合條件的請求會重試,不符合條件的請求不會重試,重試的次數沒有限制,重試的時間間隔為10秒,自定義重試的代碼示例如下:
- Python
- Java
from webullsdktradeeventscore.events_client import EventsClient
from webullsdkcore.retry.retry_policy import NO_RETRY_POLICY
# 設置不重試
client = EventsClient("<your_app_key>", "<your_app_secret>", "<region_id>", retry_policy=NO_RETRY_POLICY)
EventClient client = EventClient.builder()
.appKey("<your_app_key>")
.appSecret("<your_app_secret>")
.regionId("<region_id>")
// 設置不重試
.reconnectBy(RetryPolicy.never())
.build();
默認重試的條件判斷說明:
- gRPC協議的特定錯誤碼(如下)重試,非特定錯誤碼不重試
- 2 (UNKNOWN)
- 13 (INTERNAL)
- 14 (UNAVAILABLE)
- 其他錯誤和異常,不重試
自定義重試策略
自定義重試策略,主要通過擴展RetryPolicy
來實現
RetryPolicy
通過RetryCondition
實現是否重試的條件判斷, 通過BackoffStrategy
實現重試時間間隔的退避策略
- Python
- Java
以下代碼演示了RetryOnRcCodeCondition
的實現:
# 根據MQTT協議的錯誤碼,實現的RetryCondition
class RetryOnRcCodeCondition(RetryCondition):
DEFAULT_RETRYABLE_RC_CODE_LIST = [
3,
5,
]
def __init__(self, retryable_rc_code_list=None):
if retryable_rc_code_list:
self.retryable_rc_code_list = retryable_rc_code_list
else:
self.retryable_rc_code_list = self.DEFAULT_RETRYABLE_RC_CODE_LIST
def should_retry(self, retry_policy_context):
if retry_policy_context.rc_code in self.retryable_rc_code_list:
return RetryCondition.RETRY
else:
return RetryCondition.NO_RETRY
以下代碼演示了ExponentialBackoffStrategy
的實現:
# 一個簡單的指數退避策略
class ExponentialBackoffStrategy(BackoffStrategy):
MAX_RETRY_LIMIT = 30
def __init__(self, base_delay_in_milliseconds, max_delay_in_milliseconds):
self.base_delay_in_milliseconds = base_delay_in_milliseconds
self.max_delay_in_milliseconds = max_delay_in_milliseconds
def compute_delay_before_next_retry(self, retry_policy_context):
retries = min(self.MAX_RETRY_LIMIT, retry_policy_context.retries_attempted)
delay = min(self.max_delay_in_milliseconds, self.base_delay_in_milliseconds << retries)
return delay
以下代碼演示了一個簡單的RetryPolicy
實現:
class MyRetryPolicy(RetryPolicy):
def __init__(self):
RetryPolicy.__init__(self, RetryOnRcCodeCondition(), ExponentialBackoffStrategy())
以下代碼演示了QuotesSubsRetryCondition
的實現:
// 根據MQTT協議,實現的行情數據流RetryCondition
import com.webull.openapi.quotes.subsribe.lifecycle.QuotesSubsFailedContext;
import com.webull.openapi.quotes.subsribe.message.ConnAck;
import com.webull.openapi.retry.RetryContext;
import com.webull.openapi.retry.condition.RetryCondition;
import com.webull.openapi.utils.ExceptionUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class QuotesSubsRetryCondition implements RetryCondition {
private QuotesSubsRetryCondition() {
}
private static class InstanceHolder {
private static final QuotesSubsRetryCondition instance = new QuotesSubsRetryCondition();
}
public static QuotesSubsRetryCondition getInstance() {
return QuotesSubsRetryCondition.InstanceHolder.instance;
}
@Override
public boolean shouldRetry(RetryContext context) {
if (!(context instanceof QuotesSubsFailedContext)) {
throw new IllegalArgumentException("Retry context[" + context.getClass().getName() + "] is inappropriate to mqtt retry condition!");
}
QuotesSubsFailedContext quotesCtx = (QuotesSubsFailedContext) context;
if (quotesCtx.userDisconnect()) {
return false;
}
if (quotesCtx.getConnAck().isPresent()) {
int connAck = quotesCtx.getConnAck().get();
if (connAck != ConnAck.SERVER_UNAVAILABLE && connAck != ConnAck.NOT_AUTHORIZED) {
return false;
}
}
Throwable rootCause = ExceptionUtils.getRootCause(quotesCtx.getCause());
return !quotesCtx.clientDisconnect() || rootCause instanceof IOException || rootCause instanceof TimeoutException;
}
}
以下代碼演示了ExponentialBackoffStrategy
的實現:
// 一個簡單的指數退避策略
import com.webull.openapi.retry.RetryContext;
import java.util.concurrent.TimeUnit;
public class ExponentialBackoffStrategy implements BackoffStrategy {
private static final int MAX_RETRY_LIMIT = 30;
private final long initialDelayNanos;
private final long maxDelayNanos;
public ExponentialBackoffStrategy() {
this(1, 120, TimeUnit.SECONDS);
}
public ExponentialBackoffStrategy(long initialDelay, long maxDelay, TimeUnit timeUnit) {
this.initialDelayNanos = timeUnit.toNanos(initialDelay);
this.maxDelayNanos = timeUnit.toNanos(maxDelay);
}
@Override
public long nextRetryDelay(RetryContext context, TimeUnit timeUnit) {
int retries = Math.min(ExponentialBackoffStrategy.MAX_RETRY_LIMIT, context.getRetriesAttempted());
long delay = Math.min(this.maxDelayNanos, this.initialDelayNanos << retries);
if (delay < this.initialDelayNanos) {
delay = this.maxDelayNanos;
}
return timeUnit.convert(delay, TimeUnit.NANOSECONDS);
}
}
以下代碼演示了一個簡單的RetryPolicy
實現:
RetryPolicy retryPolicy = new RetryPolicy(QuotesSubsRetryCondition.getInstance(), new ExponentialBackoffStrategy());