Spring Boot 2.3.2项目Mongo Socket异常自动重试配置与本地测试
针对你遇到的Spring Boot MongoDB操作重试问题,结合你使用的2.3.2.RELEASE版本,我给你梳理下解决方案和测试方法:
一、先搞清楚默认的重试逻辑
首先要明确两个关键点:
- MongoDB驱动的
retryWrites=true参数:它只针对已建立连接后出现的特定可重试写入操作(比如幂等的插入、更新、删除),而且只处理驱动层面定义的少数异常,像你测试时用的「无效URI/连接被拒绝」这种连接初始化失败的情况,它是不处理的。 - Spring Data MongoDB默认不自动重试所有Mongo相关异常:你遇到的
MongoSocketWriteException、MongoSocketReadException、MongoSocketOpenException这些,Spring Data本身没有内置的全局重试机制,需要手动配置。
二、启用Spring层面的自动重试
要覆盖这些异常的重试,需要用到Spring的spring-retry模块,结合AOP拦截Mongo操作的异常,具体步骤如下:
1. 添加依赖
在你的Gradle构建文件中加入两个依赖:
implementation 'org.springframework.retry:spring-retry' implementation 'org.springframework:spring-aspects'
2. 开启重试功能
在Spring Boot主类上添加@EnableRetry注解,开启重试支持:
@SpringBootApplication @EnableRetry public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }
3. 配置重试规则
你可以选择两种方式配置:
方式一:针对单个Repository方法
在你的MongoRepository接口的方法上,添加@Retryable注解,指定要重试的异常、次数和间隔:
@Repository public interface YourEntityRepository extends MongoRepository<YourEntity, String> { @Retryable( value = {MongoReadException.class, MongoWriteException.class, MongoSocketWriteException.class, MongoSocketReadException.class, MongoSocketOpenException.class, MongoTimeoutException.class}, maxAttempts = 3, // 最多重试3次 backoff = @Backoff(delay = 1000, multiplier = 2) // 第一次延迟1秒,之后每次间隔翻倍 ) @Override <S extends YourEntity> S save(S entity); }
方式二:全局配置(针对所有Mongo操作)
如果想对所有MongoTemplate的操作统一重试,可以自定义RetryTemplate并注入到MongoTemplate中:
@Configuration public class MongoRetryConfig { @Bean public RetryTemplate retryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); // 重试策略:最多3次 SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(3); // 指定需要重试的异常 Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>(); retryableExceptions.put(MongoReadException.class, true); retryableExceptions.put(MongoWriteException.class, true); retryableExceptions.put(MongoSocketWriteException.class, true); retryableExceptions.put(MongoSocketReadException.class, true); retryableExceptions.put(MongoSocketOpenException.class, true); retryableExceptions.put(MongoTimeoutException.class, true); retryPolicy.setRetryableExceptions(retryableExceptions); // 退避策略:每次间隔1秒 FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); backOffPolicy.setBackOffPeriod(1000); retryTemplate.setRetryPolicy(retryPolicy); retryTemplate.setBackOffPolicy(backOffPolicy); return retryTemplate; } @Bean public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter, RetryTemplate retryTemplate) { MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter); mongoTemplate.setRetryTemplate(retryTemplate); return mongoTemplate; } }
三、本地测试重试行为的正确姿势
你之前用无效URI测试的方式不对,因为这种属于连接根本没建立,驱动和Spring Retry都不会处理。正确的测试应该模拟已建立连接后出现的Socket异常:
方法1:用Docker模拟MongoDB中断
- 启动一个MongoDB 4.0.5容器:
docker run -d -p 27017:27017 --name test-mongo mongo:4.0.5
- 配置应用连接这个本地MongoDB:
spring.data.mongodb.uri=mongodb://localhost:27017/testDB?retryWrites=true
- 启动应用,调用save方法的同时,临时停止容器:
docker stop test-mongo
- 观察应用日志,应该能看到重试记录,之后再启动容器:
docker start test-mongo
- 查看第三次重试是否成功写入数据。
方法2:单元测试Mock异常
用Mockito模拟MongoTemplate抛出指定异常,验证重试次数:
@SpringBootTest @EnableRetry public class RetryTest { @Autowired private YourService yourService; @MockBean private MongoTemplate mongoTemplate; @Test public void testSaveRetry() { YourEntity entity = new YourEntity(); // 模拟前两次抛异常,第三次成功 doThrow(MongoSocketWriteException.class) .doThrow(MongoSocketWriteException.class) .doNothing() .when(mongoTemplate).save(entity); yourService.saveEntity(entity); // 验证save方法被调用了3次 verify(mongoTemplate, times(3)).save(entity); } }
四、补充说明
- MongoDB驱动的
retryWrites=true和Spring Retry是互补的:前者负责驱动层的细粒度写入重试,后者负责业务层的全局异常重试,建议同时开启。 - 重试时要确保操作是幂等的:比如用
insertOne时如果重试可能会插入重复数据,建议用updateOne配合upsert=true这类幂等操作。
内容的提问来源于stack exchange,提问作者Sravani Ch
相关产品推荐
相关产品推荐

