EasyQuant 后端采用 Spring Boot 3.4 + Java 21 构建,这个技术栈的选择不是偶然,而是基于量化平台的特殊需求:高并发、低延迟、强一致性、以及快速迭代能力。
结论先行:Spring Boot 3.4 + Java 21 的组合为量化平台提供了现代化的基础设施,虚拟线程(Virtual Threads)、记录类型(Records)、模式匹配(Pattern Matching)等新特性,让代码更简洁、性能更优、可维护性更强。
一、为什么选择 Spring Boot 3.4 + Java 21?
技术栈演进路径
| 版本 | 发布时间 | 关键特性 | 适用场景 |
|---|---|---|---|
| Java 8 | 2014 | Lambda、Stream | 传统企业应用 |
| Java 11 | 2018 | HTTP Client、Var | 云原生应用 |
| Java 17 | 2021 | Records、Sealed Classes | 现代化应用 |
| Java 21 | 2023 | 虚拟线程、模式匹配 | 高并发应用 |
Java 21 在量化场景下的核心优势
- 虚拟线程(Virtual Threads) :轻量级线程,解决高并发场景下的线程调度问题
- 记录类型(Records) :不可变数据载体,简化 DTO 和实体类定义
- 模式匹配(Pattern Matching) :简化类型判断和转换逻辑
- 字符串模板(String Templates) :简化字符串拼接和格式化
- 序列化集合(Sequenced Collections) :统一的集合访问接口
Spring Boot 3.4 的关键改进
- 原生镜像支持:GraalVM Native Image,启动速度提升 10-100 倍
- 观测性增强:Micrometer Tracing、OpenTelemetry 原生支持
- 性能优化:启动时间、内存占用、响应速度全面提升
- 安全增强:Spring Security 6.2,OAuth 2.1、JWT 支持更完善
二、虚拟线程在量化平台中的应用
1)传统线程 vs 虚拟线程
scss
// 传统线程池模型(Java 8-17)
@Configuration
public class ThreadPoolConfig {
@Bean("marketDataExecutor")
public Executor marketDataExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(50);
executor.setMaxPoolSize(200);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("market-data-");
executor.initialize();
return executor;
}
}
// 使用传统线程池处理行情数据
@Service
public class MarketDataProcessor {
@Async("marketDataExecutor")
public void processTick(Tick tick) {
// 处理单个 tick
analyzeTick(tick);
updateIndicator(tick);
checkSignal(tick);
}
}
kotlin
// 虚拟线程模型(Java 21)
@Configuration
public class VirtualThreadConfig {
@Bean("virtualThreadExecutor")
public Executor virtualThreadExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
// 使用虚拟线程处理行情数据
@Service
public class MarketDataProcessor {
@Async("virtualThreadExecutor")
public void processTick(Tick tick) {
// 处理单个 tick
analyzeTick(tick);
updateIndicator(tick);
checkSignal(tick);
}
}
2)性能对比
| 指标 | 传统线程池 | 虚拟线程 | 提升 |
|---|---|---|---|
| 并发处理能力 | 200 线程 | 10,000+ 线程 | 50x+ |
| 内存占用 | 1MB/线程 | 几KB/线程 | 100x+ |
| 上下文切换 | 昂贵 | 极低 | 10x+ |
| 启动延迟 | 毫秒级 | 微秒级 | 100x+ |
3)实际应用场景
typescript
// 场景1:批量处理多个标的的行情数据
@Service
public class MultiSymbolProcessor {
@Async("virtualThreadExecutor")
public void processSymbols(List<String> symbols) {
// 为每个标的创建一个虚拟线程
List<CompletableFuture<Void>> futures = symbols.stream()
.map(symbol -> CompletableFuture.runAsync(
() -> processSymbol(symbol),
Executors.newVirtualThreadPerTaskExecutor()
))
.toList();
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
private void processSymbol(String symbol) {
// 获取最新 K 线
List<Bar> bars = barService.getLatestBars(symbol, 100);
// 计算指标
Indicator indicator = indicatorService.calculate(bars);
// 评估信号
Signal signal = strategyService.evaluate(indicator);
// 执行交易
if (signal != null) {
executionService.execute(signal);
}
}
}
// 场景2:WebSocket 连接管理
@Component
public class WebSocketManager {
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
public void broadcastToAll(String message) {
// 为每个连接创建虚拟线程
sessions.values().parallelStream().forEach(session -> {
try {
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
log.error("发送消息失败", e);
}
});
}
}
三、记录类型(Records)简化数据模型
1)传统类 vs 记录类型
less
// 传统类定义(Java 8-17)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Tick {
private Long id;
private String symbol;
private String exchange;
private Instant timestamp;
private BigDecimal price;
private Long volume;
private String side;
}
// 记录类型定义(Java 21)
public record Tick(
Long id,
String symbol,
String String exchange,
Instant timestamp,
BigDecimal price,
Long volume,
String side
) {}
2)在量化平台中的应用
typescript
// 1. 策略信号定义
public record StrategySignal(
Long strategyId,
String symbol,
SignalType type,
BigDecimal price,
Long volume,
Instant timestamp,
Map<String, Object> metadata
) {}
// 2. 风控检查结果
public record RiskCheckResult(
boolean passed,
BlockingReasonCode reason,
String message,
Map<String, Object> context
) {}
// 3. 回测结果摘要
public record BacktestSummary(
String strategyName,
String symbol,
Instant startTime,
Instant endTime,
BigDecimal totalReturn,
BigDecimal maxDrawdown,
int totalTrades,
BigDecimal sharpeRatio
) {}
// 4. 使用记录类型
@Service
public class StrategyExecutionService {
public RiskCheckResult checkRisk(StrategySignal signal) {
// 风控检查逻辑
if (signal.volume() > 10000) {
return new RiskCheckResult(
false,
BlockingReasonCode.VOLUME_TOO_LARGE,
"单笔交易量超过限制",
Map.of("volume", signal.volume(), "limit", 10000)
);
}
return new RiskCheckResult(true, null, "通过", Map.of());
}
}
3)记录类型 + 模式匹配
csharp
// 使用模式匹配处理不同类型的信号
public void processSignal(Object signal) {
switch (signal) {
case StrategySignal s when s.type() == SignalType.BUY ->
executeBuy(s);
case StrategySignal s when s.type() == SignalType.SELL ->
executeSell(s);
case RiskCheckResult r when !r.passed() ->
log.warn("风控拒绝: {}", r.message());
default ->
log.warn("未知信号类型: {}", signal);
}
}
四、字符串模板简化日志和 SQL 构建
1)传统字符串拼接 vs 字符串模板
typescript
// 传统字符串拼接(Java 8-17)
String message = String.format(
"策略 %d 在 %s 触发 %s 信号,价格 %.2f,数量 %d",
strategyId, symbol, signalType, price, volume
);
log.info("执行订单: {}", message);
// 字符串模板(Java 21)
String message = STR."策略 {strategyId} 在 {symbol} 触发 {signalType} 信号,价格 {price},数量 {volume}";
log.info("执行订单: {}", message);
2)在量化平台中的应用
python
// 1. 构建动态 SQL 查询
@Service
public class BarQueryService {
public String buildQuery(String symbol, Instant startTime, Instant endTime) {
return STR."""
SELECT * FROM md_bars_1m
WHERE symbol = '{symbol}'
AND timestamp >= '{startTime}'
AND timestamp <= '{endTime}'
ORDER BY timestamp DESC
LIMIT 100
""";
}
}
// 2. 构建日志消息
@Component
public class SignalLogger {
public void logSignal(StrategySignal signal) {
log.info(STR."""
策略信号:
- 策略ID: {signal.strategyId()}
- 标的: {signal.symbol()}
- 类型: {signal.type()}
- 价格: {signal.price()}
- 数量: {signal.volume()}
- 时间: {signal.timestamp()}
""");
}
}
// 3. 构建错误消息
@Service
public class ErrorHandler {
public String buildErrorMessage(RiskCheckResult result) {
return STR."""
风控检查失败:
- 原因码: {result.reason()}
- 消息: {result.message()}
- 上下文: {result.context()}
""";
}
}
五、序列化集合简化数据处理
1)传统集合操作 vs 序列化集合
ini
// 传统集合操作(Java 8-17)
List<Bar> bars = getBars();
Bar first = bars.get(0);
Bar last = bars.get(bars.size() - 1);
List<Bar> reversed = new ArrayList<>(bars);
Collections.reverse(reversed);
// 序列化集合(Java 21)
List<Bar> bars = getBars();
Bar first = bars.getFirst();
Bar last = bars.getLast();
List<Bar> reversed = bars.reversed();
2)在量化平台中的应用
scss
// 1. 获取最新 K 线
@Service
public class BarService {
public Bar getLatestBar(String symbol) {
List<Bar> bars = barRepository.findLatestBars(symbol, 100);
return bars.getFirst(); // Java 21: getFirst()
}
}
// 2. 计算技术指标
@Service
public class IndicatorService {
public BigDecimal calculateMA(List<Bar> bars, int period) {
if (bars.size() < period) {
return BigDecimal.ZERO;
}
// 获取最近的 period 个 K 线
List<Bar> recentBars = bars.reversed()
.stream()
.limit(period)
.toList();
// 计算平均值
return recentBars.stream()
.map(Bar::closePrice)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(period), 2, RoundingMode.HALF_UP);
}
}
// 3. 检测趋势变化
@Service
public class TrendDetector {
public boolean detectTrendChange(List<Bar> bars) {
if (bars.size() < 3) {
return false;
}
Bar first = bars.getFirst();
Bar last = bars.getLast();
// 简单趋势判断
return last.closePrice().compareTo(first.closePrice()) > 0;
}
}
六、Spring Boot 3.4 的观测性增强
1)Micrometer Tracing 集成
scss
// 启用分布式追踪
@Configuration
public class TracingConfig {
@Bean
public ObservationRegistry observationRegistry() {
return ObservationRegistry.create();
}
}
// 在服务中使用追踪
@Service
public class StrategyExecutionService {
private final ObservationRegistry observationRegistry;
public void executeStrategy(StrategySignal signal) {
Observation.createNotStarted("strategy.execution", observationRegistry)
.contextualName("execute_strategy")
.lowCardinalityKeyValue("strategy.id", signal.strategyId().toString())
.lowCardinalityKeyValue("symbol", signal.symbol())
.observe(() -> {
// 策略执行逻辑
checkRisk(signal);
placeOrder(signal);
updatePosition(signal);
});
}
}
2)自定义指标
java
// 定义自定义指标
@Component
public class StrategyMetrics {
private final Counter signalCounter;
private final Timer executionTimer;
private final Gauge activeStrategies;
public StrategyMetrics(MeterRegistry registry) {
this.signalCounter = Counter.builder("strategy.signals")
.description("策略信号计数")
.tag("type", "all")
.register(registry);
this.executionTimer = Timer.builder("strategy.execution.time")
.description("策略执行时间")
.register(registry);
this.activeStrategies = Gauge.builder("strategy.active.count",
strategyRepository,
repo -> repo.countActiveStrategies())
.description("活跃策略数量")
.register(registry);
}
public void recordSignal(SignalType type) {
signalCounter.increment();
}
public void recordExecutionTime(long duration) {
executionTimer.record(duration, TimeUnit.MILLISECONDS);
}
}
七、性能优化实战
1)虚拟线程优化高并发场景
scss
// 场景:同时处理多个策略的信号
@Service
public class MultiStrategyProcessor {
@Async("virtualThreadExecutor")
public void processStrategies(List<Strategy> strategies) {
strategies.parallelStream().forEach(strategy -> {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 为每个策略创建多个子任务
Supplier<List<Bar>> barsTask = scope.fork(() ->
barService.getLatestBars(strategy.symbol(), 100)
);
Supplier<Indicator> indicatorTask = scope.fork(() ->
indicatorService.calculate(barsTask.get())
);
Supplier<Signal> signalTask = scope.fork(() ->
strategyService.evaluate(indicatorTask.get())
);
// 等待所有任务完成
scope.join();
scope.throwIfFailed();
// 处理信号
Signal signal = signalTask.get();
if (signal != null) {
executionService.execute(signal);
}
} catch (Exception e) {
log.error("策略执行失败: {}", strategy.id(), e);
}
});
}
}
2)缓存优化
less
// 使用 Spring Cache + Caffeine
@Service
@CacheConfig(cacheNames = "bars")
public class BarService {
@Cacheable(key = "#symbol + ':' + #limit")
public List<Bar> getLatestBars(String symbol, int limit) {
return barRepository.findLatestBars(symbol, limit);
}
@CacheEvict(key = "#symbol + ':' + #limit")
public void evictBars(String symbol, int limit) {
// 清除缓存
}
}
// 配置缓存
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats());
return cacheManager;
}
}
八、最佳实践总结
1)虚拟线程使用原则
- 适合场景:IO 密集型任务(数据库查询、HTTP 请求、文件操作)
- 不适合场景:CPU 密集型任务(复杂计算、图像处理)
- 注意事项:避免在虚拟线程中使用 synchronized,使用 ReentrantLock
2)记录类型使用原则
- 适合场景:不可变数据载体(DTO、实体、返回值)
- 不适合场景:需要可变状态的对象
- 注意事项:记录类型不能继承,但可以实现接口
3)字符串模板使用原则
- 适合场景:日志消息、SQL 查询、错误消息
- 不适合场景:复杂的字符串处理逻辑
- 注意事项:注意 SQL 注入风险,使用参数化查询
结语:现代化技术栈是量化平台的基石
Spring Boot 3.4 + Java 21 为量化平台提供了现代化的基础设施,虚拟线程、记录类型、模式匹配等新特性,让代码更简洁、性能更优、可维护性更强。
关键优势总结:
- 虚拟线程:高并发场景下的性能提升 50x+
- 记录类型:简化数据模型定义,减少样板代码
- 模式匹配:简化类型判断和转换逻辑
- 字符串模板:简化字符串拼接和格式化
- 观测性增强:更好的监控和追踪能力
对于正在构建或优化量化平台的团队,Spring Boot 3.4 + Java 21 是一个值得深入考虑的技术栈。