/**
* Copyright 2024 Beijing Baolande Software Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Runtime Library Exception to the Apache 2.0 License:
*
* As an exception, if you use this Software to compile your source code and
* portions of this Software are embedded into the binary product as a result,
* you may redistribute such product without providing attribution as would
* otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
*/
package redis_sdk.client
/**
* 哨兵模式下的命令执行器
*/
public open class SentinelCommandExectuor <: CommandExecutor {
private static let logger: Logger = LoggerFactory.getLogger("redis-sdk")
private let sentinelMasterProvider: SentinelMasterProvider
// 最大重试次数
private let maxAttempts: Int64
// 最大重试时间
private let maxTotalRetriesDuration: Duration
public init(sentinelMasterProvider: SentinelMasterProvider) {
this.sentinelMasterProvider = sentinelMasterProvider
this.maxAttempts = 1
// 不需要使用,设置为一个较小的值
this.maxTotalRetriesDuration = Duration.second * 1
}
public init(sentinelMasterProvider: SentinelMasterProvider, maxAttempts: Int64, maxTotalRetriesDuration: Duration) {
this.sentinelMasterProvider = sentinelMasterProvider
this.maxAttempts = maxAttempts
this.maxTotalRetriesDuration = maxTotalRetriesDuration
}
public override func getRespVersion(): ?ProtocolVersion {
return sentinelMasterProvider.getRespVersion()
}
public open func getEndpoint(): ClientTcpEndpoint {
return sentinelMasterProvider.getMasterEndpoint()
}
public open func getEndpoint(command: RedisCommand): ClientTcpEndpoint {
return getEndpoint()
}
/**
* 支持重试的命令执行
*/
public open func executeCommand(command: RedisCommand): RedisMessage {
if (maxAttempts > 1) {
var attemptsLeft = maxAttempts
let deadline = DateTime.now().addSeconds(maxTotalRetriesDuration.toSeconds())
while (attemptsLeft > 0) {
try {
attemptsLeft--
let responseMessage = super.executeCommand(command)
return responseMessage
} catch (ex: Exception) {
if (attemptsLeft <= 0) {
if (let Some(redisEx) <- (ex as RedisException)) {
throw redisEx
} else {
throw RedisException(ex.message, ex)
}
}
if (logger.isDebugEnabled()) {
logger.warn(
"Failure to execute command ${command.getCommandType().name()}, will retry later",
ex
)
} else {
logger.warn(
"Failure to execute command ${command.getCommandType().name()}, will retry later, error msg: ${ex.message}"
)
}
handleConnectionProblem(ex, attemptsLeft, deadline)
}
}
throw RedisException("No more attempts left.")
} else {
return super.executeCommand(command)
}
}
/**
* 处理连接异常
*/
private func handleConnectionProblem(
ex: Exception,
attemptsLeft: Int64,
deadline: DateTime
): Unit {
if (!RecoverUtils.isTransportException(ex)) {
// 非通信异常,直接抛出异常,不再重试
if (let Some(redisEx) <- (ex as RedisException)) {
throw redisEx
} else {
throw RedisException(ex.message, ex)
}
}
let sleepTime = RecoverUtils.getBackoffSleepMillis(
attemptsLeft,
deadline
)
try {
if (sleepTime > 0) {
sleep(sleepTime * Duration.millisecond)
}
} catch (ex: Exception) {
throw RedisException(ex.message, ex)
}
}
protected open func internalClose(): Unit {
sentinelMasterProvider.close()
}
}