ホームページ >Java >&#&チュートリアル >Spring Boot + gRPC を使用してマイクロサービスを構築およびデプロイする方法
現時点では、Java テクノロジー スタックの場合、マイクロサービスを構築するのに最適な選択肢は Spring Boot であり、Spring Boot は実装事例が多いマイクロサービス フレームワーク Spring Cloud とともに使用されるのが一般的です。
Spring Cloud は完璧に見えますが、実際に開発してみると、Spring Cloud には次のような深刻な問題があることが簡単にわかります。
サービス ガバナンス関連のロジックは、次のような SDK に存在します。 Spring Cloud Netflix として機能し、ビジネス コードと密接に結合されています。
SDK はビジネス コードに対して侵入しすぎています。SDK がアップグレードされて下位互換性がなくなると、たとえビジネスがロジックは変わりません。何も変わっていません。
さまざまなコンポーネントが眩しく、品質にもばらつきがあり、学習コストが高すぎ、コンポーネント間でコードを完全に再利用することは困難です。ガバナンス ロジックを実装するためだけに SDK を使用するのは、あまり良い選択ではありません。
は Java テクノロジー スタックにバインドされており、他の言語も接続できますが、サービス ガバナンスに関連するロジックは手動で実装する必要があり、の原則に準拠していません。マイクロサービスは「複数の言語で開発可能」です。
Spring Cloud は単なる開発フレームワークであり、マイクロサービスに必要なサービスのスケジューリングやリソース割り当てなどの機能は実装されておらず、これらの要件は Kubernetes などのプラットフォームの助けを借りて満たす必要があります。 Spring Cloud と Kubernetes には重複する機能があり、機能が競合するため、2 つの間のスムーズなコラボレーションが困難になります。
Spring Cloud に代わるものはありますか?持っている!イスティオです。
Istio は、ガバナンス ロジックをビジネス コードから完全に分離し、独立したプロセス (サイドカー) を実装します。デプロイメント中、Sidecar とビジネス コードは同じ Pod 内に共存しますが、ビジネス コードは Sidecar の存在をまったく認識しません。これにより、ビジネス コードへのガバナンス ロジックの侵入がゼロになります。実際、コードが侵入しないだけでなく、実行時に 2 つの間に結合がありません。これにより、サービス ガバナンスの問題を気にすることなく、異なる言語やテクノロジー スタックを使用して異なるマイクロサービスを開発できるようになり、非常にエレガントなソリューションであると言えます。
つまり、「なぜ Istio を使用するのか」という疑問は簡単に解決されます。Istio は、ビジネス ロジックとサービス ガバナンス ロジックの結合や、言語間をうまく実現できないなど、従来のマイクロサービスの問題点を解決してくれるからです。 . そしてとても使いやすいです。 Kubernetes をマスターすれば、Istio の使用方法を学ぶのは難しくありません。
マイクロサービス アーキテクチャでは、サービス間の通信は比較的大きな問題であり、通常は RPC または RESTful API を使用して実装されます。
Spring Boot では RestTemplate を使用してリモート サービスを呼び出すことができますが、この方法は直感的ではなく、コードが複雑です。言語間の通信も大きな問題です。Dubbo などの一般的な Java RPC フレームワークよりも gRPC の方が便利です。軽量で使いやすく、コードの可読性が高く、Istio や Kubernetes とうまく統合できます。Protobuf と HTTP2 のサポートにより、パフォーマンスもかなり優れているため、今回は Spring Boot の問題を解決するために gRPC を選択しました。マイクロプロセッサの問題 サービス間通信の問題また、gRPC にはサービス ディスカバリやロード バランシングなどの機能はありませんが、Istio はこの点で非常に強力であり、両者は完全な補完関係を形成しています。
さまざまな grpc-spring-boot-starter が Spring Boot と Istio の統合に未知の副作用をもたらす可能性があることを考慮して、今回は grpc-spring-boot-starter を使用しませんでした。 Spring Boot は直接手書きでした。 gRPC と Spring Boot を統合するためにサードパーティのフレームワークを使用したくない場合は、私の簡単な実装方法を参照してください。
まず Spring Initializr を使用して親プロジェクト spring-boot-istio を確立し、gRPC の依存関係を導入します。 pom ファイルは次のとおりです:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <modules> <module>spring-boot-istio-api</module> <module>spring-boot-istio-server</module> <module>spring-boot-istio-client</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> </parent> <groupId>site.wendev</groupId> <artifactId>spring-boot-istio</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-istio</name> <description>Demo project for Spring Boot With Istio.</description> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-all</artifactId> <version>1.28.1</version> </dependency> </dependencies> </dependencyManagement> </project>
次に、パブリック依存関係モジュール spring-boot-istio-api を作成します。pom ファイルは次のとおりで、主に gRPC の依存関係がいくつかあります:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-boot-istio</artifactId> <groupId>site.wendev</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-istio-api</artifactId> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-all</artifactId> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.2</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.11.3:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.28.1:exe:${os.detected.classifier}</pluginArtifact> <protocExecutable>/Users/jiangwen/tools/protoc-3.11.3/bin/protoc</protocExecutable> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
src/main/proto フォルダーに移動し、このフォルダーに hello.proto を作成し、次のようにサービス間のインターフェイスを定義します。
syntax = "proto3"; option java_package = "site.wendev.spring.boot.istio.api"; option java_outer_classname = "HelloWorldService"; package helloworld; service HelloWorld { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; }
これは非常に簡単で、名前を送信し、その名前を含むメッセージを返すだけです。
次に、サーバー コードとクライアント コードを生成し、それらを java フォルダーに置きます。この部分については、gRPC の公式ドキュメントを参照してください。
API モジュールが利用可能になると、サービス プロバイダー (サーバー) とサービス コンシューマー (クライアント) を開発できます。ここでは、gRPC と Spring Boot を統合する方法に焦点を当てます。
1) サーバー側
ビジネス コードは非常に単純です:
/** * 服务端业务逻辑实现 * * @author 江文 * @date 2020/4/12 2:49 下午 */ @Slf4j @Component public class HelloServiceImpl extends HelloWorldGrpc.HelloWorldImplBase { @Override public void sayHello(HelloWorldService.HelloRequest request, StreamObserver<HelloWorldService.HelloResponse> responseObserver) { // 根据请求对象建立响应对象,返回响应信息 HelloWorldService.HelloResponse response = HelloWorldService.HelloResponse .newBuilder() .setMessage(String.format("Hello, %s. This message comes from gRPC.", request.getName())) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); log.info("Client Message Received:[{}]", request.getName()); } }
ビジネス コードに加えて、gRPC も開始する必要があります。アプリケーションがサーバーを起動すると同時に。まず、サーバー側で起動、シャットダウン、その他のロジックを記述します:
/** * gRPC Server的配置——启动、关闭等 * 需要使用<code>@Component</code>注解注册为一个Spring Bean * * @author 江文 * @date 2020/4/12 2:56 下午 */ @Slf4j @Componentpublic class GrpcServerConfiguration { @Autowired HelloServiceImpl service; /** 注入配置文件中的端口信息 */ @Value("${grpc.server-port}") private int port; private Server server; public void start() throws IOException { // 构建服务端 log.info("Starting gRPC on port {}.", port); server = ServerBuilder.forPort(port).addService(service).build().start(); log.info("gRPC server started, listening on {}.", port); // 添加服务端关闭的逻辑 Runtime.getRuntime().addShutdownHook(new Thread(() -> { log.info("Shutting down gRPC server."); GrpcServerConfiguration.this.stop(); log.info("gRPC server shut down successfully."); })); } private void stop() { if (server != null) { // 关闭服务端 server.shutdown(); } } public void block() throws InterruptedException { if (server != null) { // 服务端启动后直到应用关闭都处于阻塞状态,方便接收请求 server.awaitTermination(); } } }
gRPC の起動、停止、その他のロジックを定義した後、CommandLineRunner を使用してそれを Spring Boot の起動に追加できます:
/** * 加入gRPC Server的启动、停止等逻辑到Spring Boot的生命周期中 * * @author 江文 * @date 2020/4/12 3:10 下午 */ @Component public class GrpcCommandLineRunner implements CommandLineRunner { @Autowired GrpcServerConfiguration configuration; @Override public void run(String... args) throws Exception { configuration.start(); configuration.block(); } }
インスタンスを取得して対応する操作を実行する必要があるため、gRPC ロジックを Spring Bean として登録します。
このように、Spring Boot を起動すると、CommandLineRunner の存在により、gRPC サーバーも同時に起動することができます。
2) クライアント
ビジネス コードも非常にシンプルです:
/** * 客户端业务逻辑实现 * * @author 江文 * @date 2020/4/12 3:26 下午 */ @RestController @Slf4j public class HelloController { @Autowired GrpcClientConfiguration configuration; @GetMapping("/hello") public String hello(@RequestParam(name = "name", defaultValue = "JiangWen", required = false) String name) { // 构建一个请求 HelloWorldService.HelloRequest request = HelloWorldService.HelloRequest .newBuilder() .setName(name) .build(); // 使用stub发送请求至服务端 HelloWorldService.HelloResponse response = configuration.getStub().sayHello(request); log.info("Server response received: [{}]", response.getMessage()); return response.getMessage(); } }
在启动客户端时,我们需要打开gRPC的客户端,并获取到channel和stub以进行RPC通信,来看看gRPC客户端的实现逻辑:
/** * gRPC Client的配置——启动、建立channel、获取stub、关闭等 * 需要注册为Spring Bean * * @author 江文 * @date 2020/4/12 3:27 下午 */ @Slf4j @Component public class GrpcClientConfiguration { /** gRPC Server的地址 */ @Value("${server-host}") private String host; /** gRPC Server的端口 */ @Value("${server-port}") private int port; private ManagedChannel channel; private HelloWorldGrpc.HelloWorldBlockingStub stub; public void start() { // 开启channel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); // 通过channel获取到服务端的stub stub = HelloWorldGrpc.newBlockingStub(channel); log.info("gRPC client started, server address: {}:{}", host, port); } public void shutdown() throws InterruptedException { // 调用shutdown方法后等待1秒关闭channel channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); log.info("gRPC client shut down successfully."); } public HelloWorldGrpc.HelloWorldBlockingStub getStub() { return this.stub; } }
比服务端要简单一些。
最后,仍然需要一个CommandLineRunner把这些启动逻辑加入到Spring Boot的启动过程中:
/** * 加入gRPC Client的启动、停止等逻辑到Spring Boot生命周期中 * * @author 江文 * @date 2020/4/12 3:36 下午 */ @Component @Slf4j public class GrpcClientCommandLineRunner implements CommandLineRunner { @Autowired GrpcClientConfiguration configuration; @Override public void run(String... args) { // 开启gRPC客户端 configuration.start(); // 添加客户端关闭的逻辑 Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { configuration.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); } })); } }
业务代码跑通之后,就可以制作Docker镜像,准备部署到Istio中去了。
在开始编写Dockerfile之前,先改动一下客户端的配置文件:
server: port: 19090 spring: application: name: spring-boot-istio-clientserver-host: ${server-host}server-port: ${server-port}
接下来编写Dockerfile:
1) 服务端:
FROM openjdk:8u121-jdk RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo 'Asia/Shanghai' >/etc/timezone ADD /target/spring-boot-istio-server-0.0.1-SNAPSHOT.jar /ENV SERVER_PORT="18080" ENTRYPOINT java -jar /spring-boot-istio-server-0.0.1-SNAPSHOT.jar
主要是规定服务端应用的端口为18080,并且在容器启动时让服务端也一起启动。
2) 客户端:
FROM openjdk:8u121-jdk RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo 'Asia/Shanghai' >/etc/timezoneADD /target/spring-boot-istio-client-0.0.1-SNAPSHOT.jar /ENV GRPC_SERVER_HOST="spring-boot-istio-server"ENV GRPC_SERVER_PORT="18888"ENTRYPOINT java -jar /spring-boot-istio-client-0.0.1-SNAPSHOT.jar \ --server-host=$GRPC_SERVER_HOST \ --server-port=$GRPC_SERVER_PORT
可以看到这里添加了启动参数,配合前面的配置,当这个镜像部署到Kubernetes集群时,就可以在Kubernetes的配合之下通过服务名找到服务端了。
同时,服务端和客户端的pom文件中添加:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> <version>1.4.13</version> <dependencies> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1</version> </dependency> </dependencies> <executions> <execution> <id>default</id> <goals> <goal>build</goal> <goal>push</goal> </goals> </execution> </executions> <configuration> <repository>wendev-docker.pkg.coding.net/develop/docker/${project.artifactId}</repository> <tag>${project.version}</tag> <buildArgs> <JAR_FILE>${project.build.finalName}.jar</JAR_FILE> </buildArgs> </configuration> </plugin> </plugins> </build>
这样执行mvn clean package时就可以同时把docker镜像构建出来了。
有了镜像之后,就可以写部署文件了:
1) 服务端:
apiVersion: v1 kind: Servicemetadata: name: spring-boot-istio-server spec: type: ClusterIP ports: - name: http port: 18080 targetPort: 18080 - name: grpc port: 18888 targetPort: 18888 selector: app: spring-boot-istio-server ---apiVersion: apps/v1 kind: Deploymentmetadata: name: spring-boot-istio-server spec: replicas: 1 selector: matchLabels: app: spring-boot-istio-server template: metadata: labels: app: spring-boot-istio-server spec: containers: - name: spring-boot-istio-server image: wendev-docker.pkg.coding.net/develop/docker/spring-boot-istio-server:0.0.1-SNAPSHOT imagePullPolicy: Always tty: true ports: - name: http protocol: TCP containerPort: 18080 - name: grpc protocol: TCP containerPort: 18888
主要是暴露服务端的端口:18080和gRPC Server的端口18888,以便可以从Pod外部访问服务端。
2) 客户端:
apiVersion: v1 kind: Servicemetadata: name: spring-boot-istio-client spec: type: ClusterIP ports: - name: http port: 19090 targetPort: 19090 selector: app: spring-boot-istio-client ---apiVersion: apps/v1 kind: Deploymentmetadata: name: spring-boot-istio-client spec: replicas: 1 selector: matchLabels: app: spring-boot-istio-client template: metadata: labels: app: spring-boot-istio-client spec: containers: - name: spring-boot-istio-client image: wendev-docker.pkg.coding.net/develop/docker/spring-boot-istio-client:0.0.1-SNAPSHOT imagePullPolicy: Always tty: true ports: - name: http protocol: TCP containerPort: 19090
主要是暴露客户端的端口19090,以便访问客户端并调用服务端。
如果想先试试把它们部署到k8s可不可以正常访问,可以这样配置Ingress:
apiVersion: networking.k8s.io/v1beta1 kind: Ingressmetadata: name: nginx-web annotations: kubernetes.io/ingress.class: "nginx" nginx.ingress.kubernetes.io/use-reges: "true" nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" nginx.ingress.kubernetes.io/proxy-send-timeout: "600" nginx.ingress.kubernetes.io/proxy-read-timeout: "600" nginx.ingress.kubernetes.io/proxy-body-size: "10m" nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: dev.wendev.site http: paths: - path: / backend: serviceName: spring-boot-istio-client servicePort: 19090
Istio的网关配置文件与k8s不大一样:
apiVersion: networking.istio.io/v1alpha3 kind: Gatewaymetadata: name: spring-boot-istio-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - "*" ---apiVersion: networking.istio.io/v1alpha3 kind: Virtual Servicemetadata: name: spring-boot-istio spec: hosts: - "*" gateways: - spring-boot-istio-gateway http: - match: - uri: exact: /hello route: - destination: host: spring-boot-istio-client port: number: 19090
主要就是暴露/hello这个路径,并且指定对应的服务和端口。
首先搭建k8s集群并且安装istio。我使用的k8s版本是1.16.0,Istio版本是最新的1.6.0-alpha.1,使用istioctl命令安装Istio。建议跑通官方的bookinfo示例之后再来部署本项目。
注:以下命令都是在开启了自动注入Sidecar的前提下运行的
我是在虚拟机中运行的k8s,所以istio-ingressgateway没有外部ip:
$ kubectl get svc istio-ingressgateway -n istio-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEistio-ingressgateway NodePort 10.97.158.232 <none> 15020:30388/TCP,80:31690/TCP,443:31493/TCP,15029:32182/TCP,15030:31724/TCP,15031:30887/TCP,15032:30369/TCP,31400:31122/TCP,15443:31545/TCP 26h
所以,需要设置IP和端口,以NodePort的方式访问gateway:
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}') export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}') export INGRESS_HOST=127.0.0.1export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT
这样就可以了。
接下来部署服务:
$ kubectl apply -f spring-boot-istio-server.yml $ kubectl apply -f spring-boot-istio-client.yml $ kubectl apply -f istio-gateway.yml
必须要等到两个pod全部变为Running而且Ready变为2/2才算部署完成。
接下来就可以通过
curl -s http://${GATEWAY_URL}/hello
访问到服务了。如果成功返回了Hello, JiangWen. This message comes from gRPC.的结果,没有出错则说明部署完成。
以上がSpring Boot + gRPC を使用してマイクロサービスを構築およびデプロイする方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。