SpringBoot学习日记 Day6:解锁微服务与高效任务处理
一、开篇:从单体到微服务的思维转变
刚开始接触微服务时,我总习惯把所有功能写在一个项目里。直到项目越来越臃肿,每次修改都要全量部署,才意识到微服务架构的价值。今天我们就来探索SpringBoot在微服务场景下的强大能力!
二、多模块项目:微服务的雏形
1. 创建父工程(pom.xml关键配置)
<packaging>pom</packaging>
<modules><module>weather-service</module><module>user-service</module><module>common</module>
</modules><!-- 统一依赖管理 -->
<dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.7.3</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>
2. 子模块示例:common模块
common/
├── src/
│ ├── main/
│ │ ├── java/com/example/common/
│ │ │ ├── exception/ # 公共异常类
│ │ │ ├── utils/ # 工具类
│ │ │ └── config/ # 公共配置
│ ├── resources/
├── pom.xml
子模块pom.xml特点:
<parent><groupId>com.example</groupId><artifactId>parent-project</artifactId><version>1.0.0</version>
</parent><dependencies><!-- 模块间依赖 --><dependency><groupId>com.example</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency>
</dependencies>
三、REST客户端:服务间通信的桥梁
1. RestTemplate基础用法
@Service
public class WeatherService {private final RestTemplate restTemplate;// 推荐使用构造器注入public WeatherService(RestTemplateBuilder builder) {this.restTemplate = builder.rootUri("https://api.weather.com").setConnectTimeout(Duration.ofSeconds(3)).build();}public WeatherData getWeather(String city) {String url = "/v1/current?city={city}";try {return restTemplate.getForObject(url, WeatherData.class, city);} catch (RestClientException e) {throw new BusinessException("天气服务调用失败", e);}}
}
2. WebClient响应式调用
@Service
public class ReactiveWeatherService {private final WebClient webClient;public ReactiveWeatherService(WebClient.Builder builder) {this.webClient = builder.baseUrl("https://api.weather.com").build();}public Mono<WeatherData> getWeatherAsync(String city) {return webClient.get().uri("/v1/current?city={city}", city).retrieve().bodyToMono(WeatherData.class).timeout(Duration.ofSeconds(2)).onErrorResume(e -> Mono.error(new BusinessException("天气服务异常")));}
}
3. 重试机制增强健壮性
@Bean
public RestTemplate restTemplate() {return new RestTemplateBuilder().interceptors(new RetryableRestTemplateInterceptor()).build();
}// 自定义拦截器
public class RetryableRestTemplateInterceptor implements ClientHttpRequestInterceptor {@Overridepublic ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {int retryCount = 0;ClientHttpResponse response;while (retryCount < 3) {try {response = execution.execute(request, body);if (response.getStatusCode().is5xxServerError()) {throw new IOException("Server error");}return response;} catch (IOException e) {retryCount++;if (retryCount >= 3) {throw e;}Thread.sleep(1000 * retryCount);}}throw new IOException("Request failed after retries");}
}
四、定时任务:后台执行的瑞士军刀
1. 基础定时任务
@Slf4j
@Component
@EnableScheduling
public class WeatherDataSyncTask {// 每30分钟执行一次@Scheduled(fixedRate = 30 * 60 * 1000)public void syncWeatherData() {log.info("开始同步天气数据...");// 业务逻辑}// 每天凌晨1点执行@Scheduled(cron = "0 0 1 * * ?")public void clearOldData() {log.info("清理过期数据...");}
}
2. 动态定时任务(数据库配置触发时间)
@Service
public class DynamicTaskService {@Autowiredprivate ThreadPoolTaskScheduler taskScheduler;private ScheduledFuture<?> future;public void startTask(String taskId, Runnable task, String cron) {stopTask(taskId); // 先停止已有任务future = taskScheduler.schedule(task, new CronTrigger(cron));}public void stopTask(String taskId) {if (future != null) {future.cancel(true);}}
}// 配置线程池
@Configuration
public class TaskConfig {@Beanpublic ThreadPoolTaskScheduler taskScheduler() {ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();scheduler.setPoolSize(5);scheduler.setThreadNamePrefix("task-");return scheduler;}
}
五、异步处理:释放系统吞吐潜力
1. 快速启用异步
@SpringBootApplication
@EnableAsync
public class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}
}// 自定义线程池
@Configuration
public class AsyncConfig {@Bean(name = "asyncExecutor")public Executor asyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);executor.setThreadNamePrefix("Async-");executor.initialize();return executor;}
}
2. 异步方法实践
@Service
public class LogService {@Async("asyncExecutor")public CompletableFuture<Void> saveLogAsync(Log log) {// 模拟耗时操作Thread.sleep(1000);logRepository.save(log);return CompletableFuture.completedFuture(null);}@Asyncpublic void processInBackground() {// 无返回值的异步方法}
}// 调用示例
@RestController
public class LogController {@PostMapping("/logs")public Result<Void> addLog(@RequestBody Log log) {logService.saveLogAsync(log); // 异步执行return Result.success();}
}
六、实战项目:天气服务系统
1. 系统架构
weather-service (主模块)
├── 调用外部天气API
├── 定时缓存数据
├── 提供REST接口
│
common (公共模块)
├── 异常处理
├── 工具类
2. 核心代码片段
天气数据缓存:
@Cacheable(value = "weather", key = "#city")
public WeatherData getCachedWeather(String city) {return externalApiClient.getWeather(city);
}@Scheduled(fixedRate = 30 * 60 * 1000)
@CacheEvict(value = "weather", allEntries = true)
public void refreshCache() {log.info("清空天气缓存");
}
异步日志记录:
@Async
public void asyncAddAccessLog(HttpServletRequest request) {AccessLog log = new AccessLog();log.setPath(request.getRequestURI());log.setIp(request.getRemoteAddr());log.setCreateTime(LocalDateTime.now());logRepository.save(log);
}
七、避坑指南
1. 跨模块扫描问题:
- 主类添加@ComponentScan("com.example")
- 确保包结构有共同父包
2. RestTemplate单例问题:
- 推荐通过RestTemplateBuilder创建
- 为不同服务创建不同的RestTemplate实例
3. 定时任务阻塞:
- 默认使用单线程,长时间任务会影响其他任务
- 务必配置线程池
4. 异步失效场景:
- 同事务问题:自调用或private方法无效
- 异常处理:主线程无法捕获异步方法异常
八、明日计划
1. 学习SpringBoot Actuator监控
2. 集成Prometheus+Grafana
3. 实现健康检查与指标暴露
4. 探索SpringBoot Admin
思考题:在微服务架构下,如果天气服务和用户服务需要频繁通信,RestTemplate和WebClient哪种方式更适合?为什么?欢迎在评论区分享你的见解!
如果觉得这篇学习日记有帮助,请点赞收藏支持~完整天气服务示例代码可通过私信获取。在实际开发中遇到异步或定时任务问题也欢迎留言讨论!