我使用 Spring 配置了我的 MongoDB 副本集,并且我正在尝试测试自动故障转移。 我知道如果主节点宕机了,需要几秒钟才能选出一个新的主节点,所以在那个时间段内,所有的写入都会失败。
我有一个测试应用程序,它每 1 秒写入一次数据库,当我关闭主数据库时,我得到一个 java.io.IOException(因为没有要写入的主数据库)。如果我重新启动我的应用程序,新主节点的写入操作不会出现问题。
我认为 MongoDB Java 驱动程序可以使用重试来处理这些情况(我错了吗?),但我无法配置 Spring 来做到这一点,所以我需要一些帮助。 :)
我的配置是这样的:
<mongo:mongo id="mongo" replica-set="host1:27017,host2:27017,host3:27017">
<mongo:options
connections-per-host="8"
threads-allowed-to-block-for-connection-multiplier="4"
connect-timeout="1000"
max-wait-time="1500"
auto-connect-retry="true"
socket-keep-alive="true"
socket-timeout="1500"
slave-ok="true"
write-number="1"
write-timeout="0"
write-fsync="true"/>
</mongo:mongo>
<mongo:repositories base-package="my.repositories" />
<mongo:db-factory dbname="my_db" mongo-ref="mongo" />
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
谢谢!
最佳答案
这里是 spring aop/spring retry custom RetryPolicy 的初始尝试,用于在各种情况下进行通用重试。 这很脆弱(因为它使用了可能会更改的异常消息等)。我建议进行稳健的测试,并且肯定会在更改 MongoDB 和/或 java 驱动程序版本时重复。
首先,使用的maven依赖:
<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.11.3</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.0.3.RELEASE</version>
</dependency>
</dependencies>
二、自定义org.springframework.retry.RetryPolicy
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.SimpleRetryPolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
public class CustomMongoDBRetryPolicy extends SimpleRetryPolicy {
private static final Logger logger = Logger.getLogger(CustomMongoDBRetryPolicy.class.getName());
public CustomMongoDBRetryPolicy(int maxAttempts) {
super(maxAttempts, createRetryableExceptions(), true);
}
private static Map<Class<? extends Throwable>, Boolean> createRetryableExceptions() {
HashMap<Class<? extends Throwable>, Boolean> classBooleanHashMap = new HashMap<Class<? extends Throwable>, Boolean>();
classBooleanHashMap.put(org.springframework.dao.DataAccessResourceFailureException.class, true);
classBooleanHashMap.put(org.springframework.data.mongodb.UncategorizedMongoDbException.class, true);
classBooleanHashMap.put(com.mongodb.MongoException.class, true);
classBooleanHashMap.put(java.net.ConnectException.class, true);
return classBooleanHashMap;
}
@Override
public boolean canRetry(RetryContext context) {
boolean retry = super.canRetry(context);
if (retry) {
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
Throwable lastThrowable = context.getLastThrowable();
if (lastThrowable != null) {
String message = lastThrowable.getMessage();
Throwable cause = lastThrowable.getCause();
if (message != null) {
if (message.startsWith("No replica set members available in")) {
logger.info("Retrying because no replica set members available. "+message);
return true;
}
if (message.startsWith("not talking to master and retries used up")) {
logger.info("Retrying because no master. "+message);
return true;
}
if (message.startsWith("can't find a master")) {
logger.info("Retrying because no master. "+message);
return true;
}
if (message.matches("Read operation to server [^\\s]* failed on database .*")) {
logger.info("Retrying because read operation failed. "+message);
return true;
}
}
if (cause != null) {
String causeMessage = cause.getMessage();
if (causeMessage != null) {
if (causeMessage.startsWith("Connection refused")) {
logger.info("Retrying because connection not available. "+causeMessage+"("+message+")");
return true;
}
}
}
logger.info("Not retrying. "+message+" "+lastThrowable.getClass().getName());
return false;
}
}
return retry;
}
}
最后,使用 Spring AOP 绑定(bind)到 Dao
<aop:config proxy-target-class="false">
<aop:pointcut id="retry"
expression="execution(* IMyDao.count(..))" />
<aop:pointcut id="retry2"
expression="execution(* IMyDao.insert(..))" />
<aop:advisor pointcut-ref="retry"
advice-ref="retryAdvice" order="-1"/>
<aop:advisor pointcut-ref="retry2"
advice-ref="retryAdvice" order="-1"/>
</aop:config>
下面结合org.springframework.retry.backoff.ExponentialBackOffPolicy来延迟重试,org.springframework.retry.policy.TimeoutRetryPolicy来限制重试时间和CustomMongoDBRetryPolicy,它重试似乎可以重试的内容...
<bean id="retryAdvice"
class="org.springframework.retry.interceptor.RetryOperationsInterceptor">
<property name="retryOperations">
<bean class="org.springframework.retry.support.RetryTemplate">
<property name="retryPolicy">
<bean class="org.springframework.retry.policy.CompositeRetryPolicy">
<property name="optimistic" value="false"/>
<property name="policies">
<set>
<bean class="org.springframework.retry.policy.TimeoutRetryPolicy">
<property name="timeout" value="20000"/>
</bean>
<bean class="CustomMongoDBRetryPolicy">
<constructor-arg value="100"/>
</bean>
</set>
</property>
</bean>
</property>
<property name="listeners">
<set>
<bean class="MyRetryListener"/>
</set>
</property>
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="500"/>
<property name="maxInterval" value="8000"/>
<property name="multiplier" value="1.5"/>
</bean>
</property>
</bean>
</property>
</bean>
我已经在各种情况下对此进行了测试,它似乎处理得非常好。但它是否适用于特定的应用程序,需要根据具体情况来回答。
- 初始副本集启动 - 在服务器监听之前定期自动重新连接句柄,该句柄在主选举之前处理 - 对应用程序来说都是不可见的(除非有很长的延迟)
- 杀死主程序 - 正在进行的写入操作对应用程序失败,后续重试
- 降级主节点,关闭主节点 - 就像杀死主节点。
- 完整的副本集重启(如果足够快)
希望对你有帮助
关于spring - MongoDB - 如何在 Spring 的主要连任期间处理写入失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20312704/