怎麼使用Spring Boot+gRPC建置微服務並部署
1. 為什麼要用Istio?
目前,對於Java技術堆疊來說,建構微服務的最佳選擇是Spring Boot而Spring Boot一般搭配目前落地案例很多的微服務框架Spring Cloud來使用。
Spring Cloud看似很完美,但是在實際上手開發後,很容易就會發現Spring Cloud存在以下比較嚴重的問題:
服務治理相關的邏輯存在於Spring Cloud Netflix等SDK中,與業務程式碼緊密耦合。
SDK對業務代碼侵入太大,SDK發生升級且無法向下兼容時,業務代碼必須做出改變以適配SDK的升級——即使業務邏輯並沒有發生任何變化。
各種元件令人眼花撩亂,品質也參差不齊,學習成本太高,且元件之間程式碼很難完全重複使用,僅僅為了實現治理邏輯而學習SDK也並不是很好的選擇。
綁定於Java技術棧,雖然可以接入其他語言但要手動實現服務治理相關的邏輯,不符合微服務「可以用多種語言進行開發」的原則。
Spring Cloud只是一個開發框架,沒有實現微服務所必須的服務調度、資源分配等功能,這些需求要藉助Kubernetes等平台來完成。 Spring Cloud and Kubernetes have overlapping functionality, and conflicting features create difficulties in smooth collaboration between the two.。
替代Spring Cloud的選擇有沒有呢?有!它就是Istio。
Istio將治理邏輯完全獨立於商業程式碼之外,實現了一個獨立的進程(Sidecar)。部署時,Sidecar和業務代碼共存於同一個Pod中,但業務代碼完全無感知Sidecar的存在。這就實現了治理邏輯對業務代碼的零侵入——實際上不僅是代碼沒有侵入,在運行時兩者也沒有任何的耦合。這使得不同的微服務完全可以使用不同語言、不同技術堆疊來開發,也不用擔心服務治理問題,可以說這是一種很優雅的解決方案了。
所以,「為什麼要使用Istio」這個問題也就迎刃而解了——因為Istio解決了傳統微服務諸如業務邏輯與服務治理邏輯耦合、不能很好地實現跨語言等痛點,而且非常容易使用。學習如何使用Istio並不困難,只要掌握了Kubernetes。
1.1. 為什麼要使用gRPC作為通訊架構?
在微服務架構中,服務之間的通訊是比較大的問題,一般採用RPC或RESTful API來實作。
Spring Boot可以使用RestTemplate呼叫遠端服務,但這種方式不直觀,程式碼也比較複雜,進行跨語言通訊也是個比較大的問題;而gRPC相比Dubbo等常見的Java RPC框架更加輕量,使用起來也很方便,程式碼可讀性高,並且與Istio和Kubernetes可以很好地進行整合,在Protobuf和HTTP2的加持下性能也還不錯,所以這次選擇了gRPC來解決Spring Boot微服務間通訊的問題。而且,雖然gRPC沒有服務發現、負載平衡等能力,但是Istio在這方面就非常強大,兩者形成了完美的互補關係。
由於考慮到各種grpc-spring-boot-starter可能會對Spring Boot與Istio的整合產生不可知的副作用,所以這次我沒有用任何的grpc-spring-boot-starter,而是直接手寫了gRPC與Spring Boot的整合。如果您不想使用第三方框架來整合gRPC和Spring Boot,您可以參考我的簡單實作方法。
1.2. 寫業務程式碼
首先使用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; }
很簡單,就是發送一個name回傳一個帶有name的message。
然後產生服務端和客戶端的程式碼,並且放到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 Server。首先寫一下Server端的啟動、關機等邏輯:
/** * 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(); } })); } }
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 '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镜像构建出来了。
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='{.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中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Jasypt介紹Jasypt是一個java庫,它允許開發員以最少的努力為他/她的專案添加基本的加密功能,並且不需要對加密工作原理有深入的了解用於單向和雙向加密的高安全性、基於標準的加密技術。加密密碼,文本,數字,二進位檔案...適合整合到基於Spring的應用程式中,開放API,用於任何JCE提供者...添加如下依賴:com.github.ulisesbocchiojasypt-spring-boot-starter2. 1.1Jasypt好處保護我們的系統安全,即使程式碼洩露,也可以保證資料來源的

一、Redis實現分散式鎖原理為什麼需要分散式鎖在聊分散式鎖之前,有必要先解釋一下,為什麼需要分散式鎖。與分散式鎖相對就的是單機鎖,我們在寫多執行緒程式時,避免同時操作一個共享變數產生資料問題,通常會使用一把鎖來互斥以保證共享變數的正確性,其使用範圍是在同一個進程中。如果換做是多個進程,需要同時操作一個共享資源,如何互斥?現在的業務應用通常是微服務架構,這也意味著一個應用會部署多個進程,多個進程如果需要修改MySQL中的同一行記錄,為了避免操作亂序導致髒數據,此時就需要引入分佈式鎖了。想要實現分

springboot讀取文件,打成jar包後訪問不到最新開發出現一種情況,springboot打成jar包後讀取不到文件,原因是打包之後,文件的虛擬路徑是無效的,只能通過流去讀取。文件在resources下publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

在Springboot+Mybatis-plus不使用SQL語句進行多表添加操作我所遇到的問題準備工作在測試環境下模擬思維分解一下:創建出一個帶有參數的BrandDTO對像模擬對後台傳遞參數我所遇到的問題我們都知道,在我們使用Mybatis-plus中進行多表操作是極其困難的,如果你不使用Mybatis-plus-join這一類的工具,你只能去配置對應的Mapper.xml文件,配置又臭又長的ResultMap,然後再寫對應的sql語句,這種方法雖然看上去很麻煩,但具有很高的靈活性,可以讓我們

1.自訂RedisTemplate1.1、RedisAPI預設序列化機制基於API的Redis快取實作是使用RedisTemplate範本進行資料快取操作的,這裡開啟RedisTemplate類,查看該類別的源碼資訊publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations,BeanClassLoaderAware{//聲明了value的各種序列化方式,初始值為空@NullableprivateRedisSe

SpringBoot和SpringMVC都是Java開發中常用的框架,但它們之間有一些明顯的差異。本文將探究這兩個框架的特點和用途,並對它們的差異進行比較。首先,我們來了解一下SpringBoot。 SpringBoot是由Pivotal團隊開發的,它旨在簡化基於Spring框架的應用程式的建立和部署。它提供了一種快速、輕量級的方式來建立獨立的、可執行

Golang中使用gRPC實現並發資料傳輸的最佳實踐引言:隨著雲端運算和大數據技術的發展,資料傳輸的需求越來越迫切。而gRPC作為Google開源的高效能遠端過程呼叫框架,以其高效、靈活和跨語言的特性,成為了許多開發者選擇的首選。本文將介紹如何在Golang中使用gRPC實現並發資料傳輸的最佳實踐,包括工程結構的搭建、連接池的使用和錯誤處理等。一、搭建工程結構在開始使

如何使用gRPC實作檔案上傳?建立配套服務定義,包括請求和回應訊息。在客戶端,開啟要上傳的檔案並將其分成區塊,然後透過gRPC串流傳輸到服務端。在服務端,接收文件區塊並將其儲存到文件中。服務端在文件上傳完成後發送回應,指示上傳是否成功。
