システム概要
最近、ECサイトのマイクロサービス化プロジェクトに取り組んでおり、Spring Cloud Alibaba技術スタックを活用しています。このシステムは「双11」のような大規模セールにおける高負荷シナリオに対応する必要があるため、アーキテクチャ設計においてサービスガバナンス、分散トランザクション、リミッティングとフォールバックといった重要なポイントを特に考慮しました。本記事では、実際の開発経験から得た知見とベストプラクティスを共有します。
システムアーキテクチャ設計
ECシステムは4つの主要なマイクロサービスに分割されています:
- 商品サービス:商品情報のCRUD、在庫管理などを担当
- 注文サービス:注文作成、照会、状態変更などを処理
- ユーザーサービス:ユーザー情報、認証などを管理
- 支払いサービス:サードパーティ決済プラットフォーム連携、決済フロー処理
これらのサービスはすべてNacosにサービスとして登録され、Feignを介してサービス間の呼び出しを実現しています。アーキテクチャには以下の要素も導入されています:
- Sentinelによるリミッティングとフォールバック、特に秒殺シナリオ
- Seataによる分散トランザクション処理、データ一貫性の保証
- RocketMQによる非同期メッセージ処理、システムスループットの向上
主要機能の実装
1. サービス登録と発見
すべてのサービスはNacosを介してサービス登録と発見を実現しています。設定は簡単で、application.ymlにNacosアドレスを追加し、@EnableDiscoveryClientアノテーションを追加するだけです。Nacosのインターフェースは直感的で、各サービスの正常性を明確に確認できます。
商品サービスの設定例:
spring:
application:
name: product-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: dev
group: DEFAULT_GROUP
@SpringBootApplication
@EnableDiscoveryClient
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
2. 秒殺シナリオのリミッティング設計
秒殺はシステムの耐圧能力を最も試すシナリオです。Sentinelを使用して実現しています:
- 商品サービスでQPSリミッティングルールを設定(例:毎秒最大1000リクエスト)
- ホットパラメータ(例:人気商品ID)に対して個別のリミッティングしきい値を設定
- フォールバックルールを設定し、システム負荷が高い場合に自動的に非コア機能をフォールバック
Sentinelコンソールではリアルタイムにトラフィックを監視し、ルールの調整も簡単です。
Sentinel設定例:
@Configuration
public class SentinelConfig {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public FilterRegistrationBean sentinelFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean<>();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
registration.setName("sentinelFilter");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
@SentinelResource(value = "getProductById",
blockHandler = "handleBlock",
fallback = "handleFallback")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}
public ResponseEntity<Product> handleBlock(Long id, BlockException ex) {
// リミット超過時の処理
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(null);
}
public ResponseEntity<Product> handleFallback(Long id, Throwable ex) {
// フォールバック処理
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null);
}
}
3. 分散トランザクション処理
注文プロセスでは複数サービスのデータ変更が関与するため、SeataのATモードを使用しています:
- 注文サービスが注文を作成(状態:「支払い待ち」)
- 商品サービスが在庫を減少
- 支払いサービスが支払いレコードを作成
いずれのステップでも失敗した場合、Seataはすべての操作を自動的にロールバックします。設定主にはSeataサーバーアドレスを設定し、トランザクションが必要なメソッドに@GlobalTransactionalアノテーションを追加します。
Seata設定例:
seata:
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
client:
rm:
async-commit-buffer-limit: 10000
tm:
commit-retry-times: 5
rollback-retry-times: 5
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductService productService;
@Autowired
private PaymentService paymentService;
@GlobalTransactional
public Order createOrder(OrderDTO orderDTO) {
// 注文作成
Order order = new Order();
// 注文情報の設定...
order = orderRepository.save(order);
// 在庫減少
productService.decreaseStock(orderDTO.getProductId(), orderDTO.getQuantity());
// 支払いレコード作成
paymentService.createPaymentRecord(order.getId(), order.getTotalAmount());
return order;
}
}
4. 非同期メッセージ処理
システムスループットを向上させるため、一部の非リアルタイム操作をRocketMQを介して非同期処理します:
- 注文成功後、メッセージを送信して在庫システムに通知
- 支払い成功後、メッセージを送信して注文状態を更新
- ユーザー登録後、歓迎メッセージを送信
これにより、メインプロセスがこれらの二次的な操作によってブロックされることがなくなり、システムの応答速度が向上します。
RocketMQ設定例:
spring:
cloud:
stream:
binders:
defaultRocket:
type: rocket
environment:
name-server: 127.0.0.1:9876
bindings:
orderCreated:
destination: order-topic
binder: defaultRocket
group: order-group
paymentCompleted:
destination: payment-topic
binder: defaultRocket
group: payment-group
@EnableBinding(OrderEventProcessor.class)
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductService productService;
@StreamListener(OrderEventProcessor.INPUT)
public void handleOrderCreated(OrderEvent event) {
// 注文作成処理
Order order = convertToOrder(event);
orderRepository.save(order);
// 非同期で在庫減少メッセージを送信
productService.sendDecreaseStockMessage(event.getProductId(), event.getQuantity());
}
}
public interface OrderEventProcessor {
String INPUT = "orderCreated";
@Input(INPUT)
SubscribableChannel orderCreated();
}
問題点と最適化
実際の開発で遭遇した典型的な問題:
- Nacosサービス発現の遅延問題:ハートビート間隔とヘルスチェックパラメータを調整して解決
- Sentinelルールが有効にならない:依存関係のバージョン競合が原因で、バージョンを統一後正常に動作
- Seataのパフォーマンス問題:undo_logテーブルのインデックスを最適化し、パフォーマンスが大幅に向上
「双11」大規模セールに向けて、以下の最適化も実施しました:
- サービスインスタンス数を増やし、水平スケーリングを適切に行う
- 核心インターフェースに合理的なサーキットブレーカー戦略を設定
- 事前に負荷テストを実施し、JVMパラメータを調整
- フォールバック計画を準備し、システムの安定性を確保
まとめ
この実践を通じて、Spring Cloud Alibaba技術スタックの強力さを深く実感しています:
- Nacosによりサービスガバナンスが簡素化
- Sentinelが包括的なトラフィック制御手段を提供
- Seataが分散トランザクションの課題を解決
- RocketMQがシステムの非同期処理能力を向上
これらのコンポーネントを組み合わせて使用することで、高負荷ECシナリオを効果的にサポートできます。同様のプロジェクトに取り組んでいる場合、Spring Cloud Alibaba技術スタックの使用を強くお勧めします。