一、开篇:从单体到微服务的思维转变
刚开始接触微服务时,我总习惯把所有功能写在一个项目里。直到项目越来越臃肿,每次修改都要全量部署,才意识到微服务架构的价值。今天我们就来探索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 {
@Override
public 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 {
@Autowired
private 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 {
@Bean
public 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);
}
@Async
public 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哪种方式更适合?为什么?欢迎在评论区分享你的见解!
如果觉得这篇学习日记有帮助,请点赞收藏支持~完整天气服务示例代码可通过私信获取。在实际开发中遇到异步或定时任务问题也欢迎留言讨论!