Maison  >  Article  >  Java  >  Comment utiliser Spring Boot+gRPC pour créer et déployer des microservices

Comment utiliser Spring Boot+gRPC pour créer et déployer des microservices

WBOY
WBOYavant
2023-05-22 20:13:591317parcourir

1. Pourquoi utiliser Istio ?

Actuellement, pour la pile technologique Java, le meilleur choix pour créer des microservices est Spring Boot, et Spring Boot est généralement utilisé avec le framework de microservices Spring Cloud, qui a de nombreux cas d'implémentation.

Spring Cloud semble parfait, mais après le développement réel, il est facile de constater que Spring Cloud présente les problèmes sérieux suivants :

  • La logique liée à la gouvernance des services existe dans les SDK tels que Spring Cloud Netflix, qui est liée à l'entreprise. le code est étroitement couplé.

  • Le SDK est trop intrusif dans le code métier. Lorsque le SDK est mis à niveau et ne peut pas être rétrocompatible, le code métier doit être modifié pour s'adapter à la mise à niveau du SDK - même si la logique métier ne change pas du tout.

  • Les différents composants sont éblouissants, et la qualité est également inégale. Le coût d'apprentissage est trop élevé et il est difficile de réutiliser complètement le code entre les composants. Apprendre le SDK uniquement pour implémenter la logique de gouvernance n'est pas un bon choix.

  • est lié à la pile technologique Java. Bien que d'autres langages puissent être connectés, la logique liée à la gouvernance des services doit être implémentée manuellement, ce qui n'est pas conforme au principe des microservices « pouvant être développés dans plusieurs langages ».

  • Spring Cloud n'est qu'un framework de développement et n'implémente pas la planification des services, l'allocation des ressources et d'autres fonctions nécessaires aux microservices. Ces exigences doivent être remplies à l'aide de plateformes telles que Kubernetes. Spring Cloud et Kubernetes ont des fonctionnalités qui se chevauchent, et des fonctionnalités contradictoires créent des difficultés pour une collaboration fluide entre les deux.

Existe-t-il des alternatives à Spring Cloud ? avoir! C'est Istio.

Istio sépare complètement la logique de gouvernance du code métier et met en œuvre un processus indépendant (Sidecar). Lors du déploiement, Sidecar et le code métier coexistent dans le même Pod, mais le code métier ignore totalement l'existence de Sidecar. Cela permet d'obtenir une intrusion nulle de la logique de gouvernance dans le code métier : en fait, non seulement le code n'est pas intrusif, mais il n'y a aucun couplage entre les deux au moment de l'exécution. Cela permet de développer différents microservices en utilisant différents langages et piles technologiques sans avoir à se soucier des problèmes de gouvernance des services. On peut dire que c'est une solution très élégante.

Ainsi, la question « Pourquoi utiliser Istio » est facilement résolue - car Istio résout les problèmes des microservices traditionnels tels que le couplage de la logique métier et de la logique de gouvernance des services, et l'incapacité de bien implémenter plusieurs langages, et c'est très facile à utiliser. Apprendre à utiliser Istio n’est pas difficile une fois que vous maîtrisez Kubernetes.

1.1. Pourquoi utiliser gRPC comme framework de communication ?

Dans l'architecture des microservices, la communication entre les services est un problème relativement important et est généralement implémentée à l'aide de l'API RPC ou RESTful.

Spring Boot peut utiliser RestTemplate pour appeler des services distants, mais cette méthode n'est pas intuitive et le code est compliqué. La communication multilingue est également un gros problème ; gRPC est plus léger que les frameworks Java RPC courants tels que Dubbo et est facile à utiliser. utilisation. Il est également très pratique à utiliser, le code est très lisible et il peut être bien intégré à Istio et Kubernetes. Avec le support de Protobuf et HTTP2, les performances ne sont pas mauvaises, donc cette fois, gRPC a été choisi pour résoudre le problème. problème de communication entre les microservices Spring Boot. De plus, bien que gRPC ne dispose pas de fonctionnalités de découverte de services, d'équilibrage de charge et autres, Istio est très puissant à cet égard, et les deux forment une relation parfaitement complémentaire.

Considérant que divers grpc-spring-boot-starters peuvent avoir des effets secondaires inconnus sur l'intégration de Spring Boot et d'Istio, je n'ai utilisé aucun grpc-spring-boot-starter cette fois, mais je l'ai écrit directement à la main Intégration de gRPC et Spring Boot. Si vous ne souhaitez pas utiliser de framework tiers pour intégrer gRPC et Spring Boot, vous pouvez vous référer à ma méthode d'implémentation simple.

1.2. Écrivez du code commercial

Utilisez d'abord Spring Initializr pour établir le projet parent spring-boot-istio et introduire les dépendances gRPC. Le fichier pom est le suivant :

<?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>

Créez ensuite le module de dépendances public spring-boot-istio-api. Le fichier pom est le suivant, principalement certaines dépendances de 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>

Créez le dossier src/main/proto. créez hello sous ce dossier .proto définit l'interface entre les services comme suit :

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;
}

C'est très simple, il suffit d'envoyer un nom et de renvoyer un message avec le nom.

Générez ensuite les codes serveur et client et placez-les dans le dossier java. Pour cette partie, veuillez vous référer à la documentation officielle de gRPC.

Une fois le module API disponible, les fournisseurs de services (serveurs) et les consommateurs de services (clients) peuvent être développés. Ici, nous nous concentrons sur la façon d'intégrer gRPC et Spring Boot.

1) Le code métier

côté serveur est très simple :

/**
 * 服务端业务逻辑实现
 * 
 * @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());
    }
}

En plus du code métier, nous devons également démarrer le serveur gRPC en même temps que l'application démarre. Tout d'abord, écrivez la logique de démarrage, d'arrêt et autre côté serveur :

/**
 * 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();
        }
    }
}

Après avoir défini la logique de démarrage, d'arrêt et autre de gRPC, vous pouvez utiliser CommandLineRunner pour l'ajouter au démarrage de 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();
    }
}

Nous allons nous inscrire la logique de gRPC Il devient un Spring Bean car vous devez obtenir son instance et effectuer les opérations correspondantes.

De cette façon, au démarrage de Spring Boot, grâce à l'existence de CommandLineRunner, le serveur gRPC peut être démarré ensemble.

2) Le code entreprise du client

est également très simple :

/**
 * 客户端业务逻辑实现
 *
 * @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();
            }
        }));
    }
}

1.3、 编写Dockerfile

业务代码跑通之后,就可以制作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 &#39;Asia/Shanghai&#39; >/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 &#39;Asia/Shanghai&#39; >/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镜像构建出来了。

2. 编写部署文件

有了镜像之后,就可以写部署文件了:

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这个路径,并且指定对应的服务和端口。

3. 部署应用到Istio

首先搭建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=&#39;{.spec.ports[?(@.name=="http2")].nodePort}&#39;)
export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=&#39;{.spec.ports[?(@.name=="https")].nodePort}&#39;)
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.的结果,没有出错则说明部署完成。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer