当前位置: 首页 > news >正文

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哪种方式更适合?为什么?欢迎在评论区分享你的见解!

如果觉得这篇学习日记有帮助,请点赞收藏支持~完整天气服务示例代码可通过私信获取。在实际开发中遇到异步或定时任务问题也欢迎留言讨论!

http://www.lryc.cn/news/614927.html

相关文章:

  • 39.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--调整发布脚本
  • 24SpringCloud黑马商城微服务整合Seata重启服务报错的解决办法
  • Web3: DeFi借贷的安全基石, 了解喂价与清算机制的原理与重要性
  • 递归---记忆化搜索
  • 八、Linux Shell 脚本:变量与字符串
  • ESP32之wifi_HTTP
  • 商品、股指及ETF期权五档盘口Tick级与分钟级历史行情数据多维解析
  • 网盘短剧资源转存项目源码 支持垮克 带后台 附教程
  • 深入解析 Apache APISIX 在微服务网关中的性能优化实践指南
  • LeetCode 面试经典 150_数组/字符串_分发糖果(15_135_C++_困难)(贪心算法)
  • Swift 实战:秒算两个数组的交集(LeetCode 349)
  • 海康威视摄像头实时推流到阿里云公网服务器(Windows + FFmpeg + nginx-rtmp)
  • 基于开源AI大模型、AI智能名片与S2B2C商城小程序的零售智能化升级路径研究
  • Selenium使用超全指南
  • Linux运维新手的修炼手扎之第27天
  • 【无标题】AI 赋能日常效率:实用案例与操作心得分享
  • vulhub-Beelzebub靶机
  • 【LeetCode 热题 100】(五)普通数组
  • 版本控制的详细说明介绍(已有github账号版)
  • 【数学归纳法】证明数列极限
  • 模拟人脑处理文本——从分句到分词,从段落到时间线叙事
  • 小米开源大模型 MiDashengLM-7B:不仅是“听懂”,更能“理解”声音
  • 力扣前200题字符串总结
  • Effective C++ 条款31: 将文件间的编译依存关系降至最低
  • Matlab系列(004) 一 Matlab分析正态分布(高斯分布)
  • DBSCAN聚类算法实战全解析
  • 制作 VSCode 插件
  • React Native jpush-react-native极光推送 iOS生产环境接收不到推送
  • 计算机网络:如何将/22的CIDR地址块划分为4个子网
  • 华数杯C题:可调控生物节律的LED光源研究——数学建模与Python实战