マイクロサービス
マイクロサービスは、単一責任に特化した多数の小規模プロジェクトを基盤として、複雑な大規模アプリケーションを構成するソフトウェアアーキテクチャスタイルです。
サービス分割戦略
いつ分割すべきか?
- スタートアッププロジェクト:まず単一アーキテクチャを採用し、迅速な開発と試行錯誤を重ねます。規模が拡大するにつれて、段階的に分割します。
- 大規模確定プロジェクト:資金が豊富で目標が明確な場合は、マイクロサービスアーキテクチャを直接選択し、後の分割作業を避けます。
どのように分割するか?
分割目標から見ると、以下の原則を満たす必要があります:
- 高凝集性:各マイクロサービスの責務はできるだけ単一で、包含するビジネス間の関連性が高く、完全性が高い必要があります。
- 低結合性:各マイクロサービスの機能は相対的に独立しており、他のマイクロサービスへの依存をできるだけ減らす必要があります。
分割方法から見ると、通常以下の2つの方式が含まれます:
- 縦分割:ビジネスモジュールに基づいて分割します
- 横分割:共通サービスを抽出し、再利用性を高めます
eコマースプラットフォーム例
eコマースプラットフォームを例に、元々すべてのビジネスが単一プロジェクト内にあったものを、以下の5つのマイクロサービスに分割できます:
- ユーザーサービス
- 商品サービス
- 注文サービス
- ショッピングカートサービス
- 支払いサービス
以下は分割後のプロジェクト構造です。ここでは負荷分散をテストしたため、2つの商品マイクロサービスインスタンスを起動しています。
登録センター
マイクロサービス間のリモート呼び出しプロセスでは、2つの役割が含まれます:
- サービスプロバイダ:他のマイクロサービスがアクセスするインターフェースを提供します(例:
product-service) - サービスコンシューマ:他のマイクロサービスが提供するインターフェースを呼び出します(例:
cart-service)
大規模マイクロサービスプロジェクトでは、サービスプロバイダの数が非常に多くなるため、これらのサービスを管理するために登録センターの概念が導入されます。
ここではNacos(Alibaba社製、現在SpringCloudAlibabaに統合されています)を採用しています。
データベースのインポート
まず、nacosが必要とするsqlファイルをデータベース(ここではmysqlを例に)にインポートする必要があります。
データベースの設定
次に、/root/nacos/custom.envに設定ファイルを作成します。
ここでは実際にnacosのデータベースを設定しています。
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=192.168.88.188
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3306
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=123
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
Nacosコンテナの作成
dockerを使用してnacosコンテナを作成します。
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
起動完了後、nacosログインページにアクセスできます:http://IP:8848/nacos/、アカウントとパスワードは両方ともnacosです。
サービス登録
application.ymlにnacos設定を追加します。
spring:
application:
name: product-service # サービス名
cloud:
nacos:
server-addr: 192.168.88.188:8848 # nacosアドレス
コンソールにアクセスすると、サービス登録が成功したことが確認でき、サービスインスタンス情報を見ることができます。
サービス発見
pom.xmlに依存関係を導入し、application.ymlにnacos設定を追加します。
<!--nacos サービス登録と発見-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
その後、サービス呼び出し側は負荷分散アルゴリズムを利用して、複数のインスタンスから1つを選んで購読サービスにアクセスできます。
一般的な負荷分散アルゴリズムには以下のようなものがあります:
- ランダム
- ラウンドロビン
- IPのハッシュ
- 最近最少アクセス
- など...
ここでは最もシンプルなランダム負荷分散を選択できます。
OpenFeignリモート呼び出し
依存関係の導入
OpenFeignはリモート呼び出しをローカルメソッド呼び出しのように簡単にします。
まずOpenFeignの依存関係と負荷分散の依存関係を導入します。
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--負荷分散器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
また、接続プールの依存関係としてokhttpを選択します。
<!--OK http 依存関係 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
application.ymlで接続プールを有効にします。
feign:
okhttp:
enabled: true # OKHttp機能を有効化
コンポーネントの有効化
次に、プロジェクト起動クラスにアノテーションを追加して、OpenFeign機能を有効化します:
@EnableFeignClients(basePackages = "com.example.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
@MapperScan("com.example.hmall.pay.mapper")
@SpringBootApplication
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
使用方法
一般的に、重複したClientインターフェースの記述を避けるために、コードを抽出する2つの方法が採られます(図参照):
- アプローチ1:マイクロサービス外の共通
moduleに抽出 - アプローチ2:各マイクロサービスが独自の
moduleを抽出
アプローチ1の抽出はよりシンプルで、プロジェクト構造も比較的明確ですが、欠点はプロジェクト全体の結合度が高くなることです。
アプローチ2の抽出は比較的複雑で、プロジェクト構造もより複雑ですが、サービス間の結合度が低くなります。
ここでは2番目の方法を採用しているため、他のサービスは依存関係を導入することでClientを使用できます。
<dependency>
<groupId>com.example.hmall</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
続いてClientの記述方法を見てみましょう。
@FeignClient("product-service")
public interface ProductClient {
@GetMapping("/products")
List<ProductDTO> queryProductsByIds(@RequestParam("ids") Collection<Long> ids);
@PutMapping("/inventory/reduce")
void reduceInventory(@RequestBody List<OrderDetailDTO> items);
}
対応するserviceでは、Clientを通じてリモート呼び出しを行います。
// private final IProductService productService;
private final ProductClient productClient;
// List<ProductDTO> products = productService.queryProductsByIds(productIds);
List<ProductDTO> products = productClient.queryProductsByIds(productIds);
ログレベルの設定
開始クラスのアノテーション@EnableFeignClientsでグローバルに有効化する必要があり、ローカルで有効化したい場合は特定のClientで設定することもできます。
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}