SpringBoot学习日记 Day6:解锁微服务与高效任务处理

发布于:2025-08-10 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、开篇:从单体到微服务的思维转变

刚开始接触微服务时,我总习惯把所有功能写在一个项目里。直到项目越来越臃肿,每次修改都要全量部署,才意识到微服务架构的价值。今天我们就来探索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哪种方式更适合?为什么?欢迎在评论区分享你的见解!

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


网站公告

今日签到

点亮在社区的每一天
去签到