一、開篇:從單體到微服務的思維轉變
剛開始接觸微服務時,我總習慣把所有功能寫在一個項目里。直到項目越來越臃腫,每次修改都要全量部署,才意識到微服務架構的價值。今天我們就來探索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哪種方式更適合?為什么?歡迎在評論區分享你的見解!
如果覺得這篇學習日記有幫助,請點贊收藏支持~完整天氣服務示例代碼可通過私信獲取。在實際開發中遇到異步或定時任務問題也歡迎留言討論!