为什么要自定义重试?
执行过程中会有如下异常
- 读超时异常
- 写入超时异常
- 节点不可用异常
- 请求中止异常
- 响应错误异常
异常具体报错
org.springframework.data.cassandra.CassandraUncategorizedException: Query; CQL [com.datastax.oss.driver.internal.core.cql.DefaultSimpleStatement@e3ca556c]; null; nested exception is com.datastax.oss.driver.api.core.connection.HeartbeatException
Cassandra timeout during SIMPLE write query at consistency LOCAL_ONE (1 replica were required but only 0 acknowledged the write); nested exception is com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException: Cassandra timeout during SIMPLE write query at consistency LOCAL_ONE (1 replica were required but only 0 acknowledged the write)
自定义重试策略
package cn.yizhoucp.octopus.config.configuration.custom.retry;
import com.datastax.oss.driver.api.core.ConsistencyLevel;
import com.datastax.oss.driver.api.core.connection.ClosedConnectionException;
import com.datastax.oss.driver.api.core.connection.HeartbeatException;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.retry.RetryDecision;
import com.datastax.oss.driver.api.core.retry.RetryPolicy;
import com.datastax.oss.driver.api.core.servererrors.CoordinatorException;
import com.datastax.oss.driver.api.core.servererrors.ReadFailureException;
import com.datastax.oss.driver.api.core.servererrors.WriteFailureException;
import com.datastax.oss.driver.api.core.servererrors.WriteType;
import com.datastax.oss.driver.api.core.session.Request;
import edu.umd.cs.findbugs.annotations.NonNull;
import lombok.extern.slf4j.Slf4j;
/**
* 自定义重试策略
*
* @author tingfeng
*/
@Slf4j
public class DatastaxCustomRetry implements RetryPolicy {
/**
* 读重试次数
*/
private final int readAttempts = 3;
/**
* 写重试次数
*/
private final int writeAttempts = 3;
/**
* 不可用重试次数
*/
private final int unavailableAttempts = 2;
private final DriverContext driverContext;
private final String cl;
/**
* 必须显示声明包含这两个参数的构造器
*
* @param driverContext 驱动上下文
* @param cl 一致性级别
*/
public DatastaxCustomRetry(DriverContext driverContext, String cl) {
this.driverContext = driverContext;
this.cl = cl;
// 在这里进行其他初始化操作,如果需要的话
log.info("DatastaxCustomRetry-init readAttempts : {} writeAttempts : {} unavailableAttempts : {} cl : {}"
, readAttempts, writeAttempts, unavailableAttempts, cl);
}
/**
* 读超时重试
*
* @param request 传递的请求对象,其中包含了触发读取超时的请求信息。
* @param cl 一致性级别,表示读操作的一致性要求。
* @param blockFor 所需的响应数量,即需要的副本数量
* @param received 实际收到的响应数量
* @param dataPresent 表示是否成功检索到数据
* @param retryCount 当前的重试次数
* @return
*/
@Override
public RetryDecision onReadTimeout(@NonNull Request request, @NonNull ConsistencyLevel cl
, int blockFor, int received, boolean dataPresent, int retryCount) {
/**
* 如果当前的重试次数小于指定的最大重试次数(readAttempts),并且已经收到的响应数量大于或等于所需的响应数量(blockFor)
* ,并且没有成功检索到数据(!dataPresent),则决定进行相同节点重试,否则选择不进行重试,将异常传播给调用方
*/
RetryDecision decision = (retryCount < readAttempts && received >= blockFor && !dataPresent)
? RetryDecision.RETRY_SAME
: RetryDecision.RETHROW;
if (decision == RetryDecision.RETRY_SAME) {
log.info("casRetry-读取超时在相同节点上重试 一致性: {},所需响应: {},收到的响应: {},是否检索到数据: {},重试次数: {}"
, cl, blockFor, received, false, retryCount);
}
return decision;
}
/**
* 写入超时时的重试策略。
*
* @param request 请求对象,包含触发写入超时的请求信息。
* @param cl 一致性级别,表示写入操作的一致性要求。
* @param writeType 写入类型,表示发生写入超时的写入操作类型。
* @param blockFor 所需的响应数量,即需要的副本数量。
* @param received 实际收到的响应数量。
* @param retryCount 当前的重试次数。
* @return RetryDecision 决策,指示是否进行重试。
*/
@Override
public RetryDecision onWriteTimeout(@NonNull Request request, @NonNull ConsistencyLevel cl
, @NonNull WriteType writeType, int blockFor, int received, int retryCount) {
RetryDecision decision = (retryCount < writeAttempts)
? RetryDecision.RETRY_SAME
: RetryDecision.RETHROW;
if (decision == RetryDecision.RETRY_SAME) {
log.info("casRetry-写超时在相同节点上重试 一致性: {},所需响应: {},收到的响应: {},是否检索到数据: {},重试次数: {}"
, cl, blockFor, received, false, retryCount);
}
return decision;
}
/**
* 处理在节点不可用情况下的重试策略。
*
* @param request 请求对象,包含触发不可用情况的请求信息。
* @param cl 一致性级别,表示操作的一致性要求。
* @param required 所需的副本数量。
* @param alive 实际可用的副本数量。
* @param retryCount 当前的重试次数。
* @return RetryDecision 决策,指示是否进行重试。
*/
@Override
public RetryDecision onUnavailable(@NonNull Request request, @NonNull ConsistencyLevel cl
, int required, int alive, int retryCount) {
RetryDecision decision =
(retryCount < unavailableAttempts) ? RetryDecision.RETRY_NEXT : RetryDecision.RETHROW;
if (decision == RetryDecision.RETRY_NEXT) {
log.info("casRetry-节点不可用重试 一致性: {},所需的副本数量: {},实际可用的副本数量: {},重试次数: {}"
, cl, required, alive, retryCount);
}
return decision;
}
/**
* 处理在请求中止时的重试策略。
*
* @param request 请求对象,包含触发请求中止的请求信息。
* @param error 异常对象,表示引发请求中止的错误。
* @param retryCount 当前的重试次数。
* @return RetryDecision 决策,指示是否进行重试。
*/
@Override
public RetryDecision onRequestAborted(@NonNull Request request, @NonNull Throwable error, int retryCount) {
RetryDecision decision =
((error instanceof ClosedConnectionException || error instanceof HeartbeatException) && (retryCount < unavailableAttempts))
? RetryDecision.RETRY_NEXT
: RetryDecision.RETHROW;
if (decision == RetryDecision.RETRY_NEXT) {
log.info("casRetry-请求中止时的重试策略 重试次数: {}", retryCount);
}
return decision;
}
/**
* 处理在错误响应时的重试策略。
*
* @param request 请求对象,包含触发错误响应的请求信息。
* @param error CoordinatorException,表示引发错误响应的异常。
* @param retryCount 当前的重试次数。
* @return RetryDecision 决策,指示是否进行重试。
*/
@Override
public RetryDecision onErrorResponse(@NonNull Request request, @NonNull CoordinatorException error, int retryCount) {
RetryDecision decision =
((error instanceof ReadFailureException || error instanceof WriteFailureException) && (retryCount < unavailableAttempts))
? RetryDecision.RETRY_NEXT
: RetryDecision.RETHROW;
if (decision == RetryDecision.RETRY_NEXT) {
log.info("casRetry-错误响应时的重试策略 重试次数: {}", retryCount);
}
return decision;
}
@Override
public void close() {
// Nothing to do
}
}
驱动器加载自定义重试类
package cn.yizhoucp.octopus.config.configuration;
import cn.yizhoucp.octopus.config.configuration.custom.retry.DatastaxCustomRetry;
import com.datastax.oss.driver.api.core.CqlSessionBuilder;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.config.CqlSessionFactoryBean;
import org.springframework.data.cassandra.config.SessionBuilderConfigurer;
import java.time.Duration;
@Slf4j
@Configuration
public class CassandraConfig extends AbstractCassandraConfiguration {
//空间名称
@Value("${spring.data.cassandra.keyspace-name}")
String keyspaceName;
//节点IP(连接的集群节点IP)
@Value("${spring.data.cassandra.contact-points}")
String contactPoints;
@Value("${spring.data.cassandra.username}")
String username;
@Value("${spring.data.cassandra.password}")
String password;
@Value("${spring.data.cassandra.session-name}")
String sessionName;
@Value("${spring.data.cassandra.pool-size}")
Integer poolSize;
@Override
public String getKeyspaceName() {
return keyspaceName;
}
@Override
public String getContactPoints() {
return contactPoints;
}
@Override
public String getSessionName() {
return sessionName;
}
@Override
public String getLocalDataCenter() {
return "datacenter1";
}
@Bean
@Override
public CqlSessionFactoryBean cassandraSession() {
CqlSessionFactoryBean cqlSessionFactoryBean = super.cassandraSession();
cqlSessionFactoryBean.setPassword(password);
cqlSessionFactoryBean.setUsername(username);
return cqlSessionFactoryBean;
}
/**
* PT2S 异常优化 & 配置文件修改 request.timeout 不生效
*
* @return
*/
@Override
protected SessionBuilderConfigurer getSessionBuilderConfigurer() {
log.info("getSessionBuilderConfigurer-start poolSize : {}", poolSize);
return new SessionBuilderConfigurer() {
@Override
public CqlSessionBuilder configure(CqlSessionBuilder cqlSessionBuilder) {
CqlSessionBuilder cqlSessionBuilder1 = cqlSessionBuilder
.withConfigLoader(DriverConfigLoader.programmaticBuilder()
.withDuration(DefaultDriverOption.REQUEST_TIMEOUT, Duration.ofMillis(6000))
.withDuration(DefaultDriverOption.HEARTBEAT_TIMEOUT, Duration.ofMillis(3000))
.withDuration(DefaultDriverOption.HEARTBEAT_INTERVAL, Duration.ofSeconds(10))
.withDuration(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, Duration.ofSeconds(3))
.withInt(DefaultDriverOption.CONNECTION_POOL_LOCAL_SIZE, poolSize)
.withInt(DefaultDriverOption.CONNECTION_POOL_REMOTE_SIZE, poolSize)
// 添加自定义重试策略
.withClass(DefaultDriverOption.RETRY_POLICY_CLASS, DatastaxCustomRetry.class)
.build());
return cqlSessionBuilder1;
}
};
}
}