撮合效能來自「讀最優價與寫回」的快與穩。我用 Redis 建立簡單、直覺的三層模型:價位集合、訂單詳情、用戶id索引。這個設計支援高速的搓合以及快速查詢用戶訂單,也便於之後替換或升級。
價位集合(ZSET)
Key = orderbook:buy(score=price)
Key = orderbook:sell(score=price)
value= orderId
我用分數做為價格排序,之後由業務層依照買賣單需求取最低或最高。
訂單詳情(Value / JSON)
Key = {orderId},value= OrderCreatedEvent 的 JSON
使用者索引(SET)
Key={userId},value=orders → 該使用者所有 orderId(查詢時先拿 id 再批次讀詳情,提高查詢效率)
這裡的關鍵是「三個維度一起更新」:價位集合、詳情、使用者索引。
// RedisOrderBookService.addOrder(...)
public void addOrder(OrderCreatedEvent e) throws JsonProcessingException {
String key = e.getOrderType().equalsIgnoreCase("BUY") ? BUY_ORDERBOOK_KEY :
SELL_ORDERBOOK_KEY;
redisTemplate.opsForZSet().add(key, e.getOrderId().toString(), e.getPrice());
redisTemplate.opsForValue().set("order:" + e.getOrderId(),
objectMapper.writeValueAsString(e));
redisTemplate.opsForSet().add("user:" + e.getUserId() + ":orders",
e.getOrderId().toString());
}
設計理由
移除也要同步清三個維度,確保不遺留殘骸。
// RedisOrderBookService.removeOrder(...)
public void removeOrder(OrderCreatedEvent e) {
String key = e.getOrderType().equalsIgnoreCase("BUY") ? BUY_ORDERBOOK_KEY :
SELL_ORDERBOOK_KEY;
redisTemplate.opsForZSet().remove(key, e.getOrderId().toString());
redisTemplate.delete("order:" + e.getOrderId());
redisTemplate.opsForSet().remove("user:" + e.getUserId() + ":orders",
e.getOrderId().toString());
}
我提供以人為中心的查詢,這在管理個人掛單列表時很好用。
// RedisOrderBookService.getOrderByUserId(...)
public List<OrderCreatedEvent> getOrderByUserId(UUID userId) {
Set<String> ids = redisTemplate.opsForSet().members("user:" + userId + ":orders");
if (ids == null || ids.isEmpty()) return List.of();
return ids.stream()
.map(id -> redisTemplate.opsForValue().get("order:" + id))
.filter(Objects::nonNull)
.map(json -> objectMapper.readValue(json, OrderCreatedEvent.class))
.collect(Collectors.toList());
}
我在這裡刻意保留彈性