异常和重试机制
异常
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
默认机制
- 默认符合条件的请求会重试,不符合条件的请求不会重试,重试的次数没有限制,重试的时间间隔为5秒,自定义重试的代码示例如下:
- 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="<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());