亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

      
      

            <dl id="hur0q"><div id="hur0q"></div></dl>

                Spring Boot+gRPC構(gòu)建微服務(wù)并部署到Istio(詳細(xì)教程)

                作為Service Mesh和云原生技術(shù)的忠實(shí)擁護(hù)者,我卻一直沒有開發(fā)過(guò)Service Mesh的應(yīng)用。正好最近受夠了Spring Cloud的“折磨”,對(duì)Kubernetes也可以熟練使用了,而且網(wǎng)上幾乎沒有Spring Boot微服務(wù)部署到Istio的案例,我就開始考慮用Spring Boot寫個(gè)微服務(wù)的Demo并且部署到Istio。項(xiàng)目本身不復(fù)雜,就是發(fā)送一個(gè)字符串并且返回一個(gè)字符串的最簡(jiǎn)單的Demo。

                題外話:我本來(lái)是想用Spring MVC寫的——因?yàn)橹車械耐瑢W(xué)不相信Spring MVC也可以開發(fā)微服務(wù),但是Spring MVC的各種配置和依賴問題把我整的想吐,為了少掉幾根頭發(fā),還是用了方便好用的Spring Boot。

                為什么要用Istio?

                目前,對(duì)于Java技術(shù)棧來(lái)說(shuō),構(gòu)建微服務(wù)的最佳選擇是Spring Boot而Spring Boot一般搭配目前落地案例很多的微服務(wù)框架Spring Cloud來(lái)使用。

                Spring Cloud看似很完美,但是在實(shí)際上手開發(fā)后,很容易就會(huì)發(fā)現(xiàn)Spring Cloud存在以下比較嚴(yán)重的問題:

                • 服務(wù)治理相關(guān)的邏輯存在于Spring Cloud Netflix等SDK中,與業(yè)務(wù)代碼緊密耦合。
                • SDK對(duì)業(yè)務(wù)代碼侵入太大,SDK發(fā)生升級(jí)且無(wú)法向下兼容時(shí),業(yè)務(wù)代碼必須做出改變以適配SDK的升級(jí)——即使業(yè)務(wù)邏輯并沒有發(fā)生任何變化。
                • 各種組件令人眼花繚亂,質(zhì)量也參差不齊,學(xué)習(xí)成本太高,且組件之間代碼很難完全復(fù)用,僅僅為了實(shí)現(xiàn)治理邏輯而學(xué)習(xí)SDK也并不是很好的選擇。
                • 綁定于Java技術(shù)棧,雖然可以接入其他語(yǔ)言但要手動(dòng)實(shí)現(xiàn)服務(wù)治理相關(guān)的邏輯,不符合微服務(wù)“可以用多種語(yǔ)言進(jìn)行開發(fā)”的原則。
                • Spring Cloud僅僅是一個(gè)開發(fā)框架,沒有實(shí)現(xiàn)微服務(wù)所必須的服務(wù)調(diào)度、資源分配等功能,這些需求要借助Kubernetes等平臺(tái)來(lái)完成。但Spring Cloud與Kubernetes功能上有重合,且部分功能也存在沖突,二者很難完美配合。

                替代Spring Cloud的選擇有沒有呢?有!它就是Istio。

                Istio徹底把治理邏輯從業(yè)務(wù)代碼中剝離出來(lái),成為了獨(dú)立的進(jìn)程(Sidecar)。部署時(shí)兩者部署在一起,在一個(gè)Pod里共同運(yùn)行,業(yè)務(wù)代碼完全感知不到Sidecar的存在。這就實(shí)現(xiàn)了治理邏輯對(duì)業(yè)務(wù)代碼的零侵入——實(shí)際上不僅是代碼沒有侵入,在運(yùn)行時(shí)兩者也沒有任何的耦合。這使得不同的微服務(wù)完全可以使用不同語(yǔ)言、不同技術(shù)棧來(lái)開發(fā),也不用擔(dān)心服務(wù)治理問題,可以說(shuō)這是一種很優(yōu)雅的解決方案了。

                所以,“為什么要使用Istio”這個(gè)問題也就迎刃而解了——因?yàn)镮stio解決了傳統(tǒng)微服務(wù)諸如業(yè)務(wù)邏輯與服務(wù)治理邏輯耦合、不能很好地實(shí)現(xiàn)跨語(yǔ)言等痛點(diǎn),而且非常容易使用。只要會(huì)用Kubernetes,學(xué)習(xí)Istio的使用一點(diǎn)都不困難。

                為什么要使用gRPC作為通信框架?

                在微服務(wù)架構(gòu)中,服務(wù)之間的通信是一個(gè)比較大的問題,一般采用RPC或者RESTful API來(lái)實(shí)現(xiàn)。

                Spring Boot可以使用RestTemplate調(diào)用遠(yuǎn)程服務(wù),但這種方式不直觀,代碼也比較復(fù)雜,進(jìn)行跨語(yǔ)言通信也是個(gè)比較大的問題;而gRPC相比Dubbo等常見的Java RPC框架更加輕量,使用起來(lái)也很方便,代碼可讀性高,并且與Istio和Kubernetes可以很好地進(jìn)行整合,在Protobuf和HTTP2的加持下性能也還不錯(cuò),所以這次選擇了gRPC來(lái)解決Spring Boot微服務(wù)間通信的問題。并且,雖然gRPC沒有服務(wù)發(fā)現(xiàn)、負(fù)載均衡等能力,但是Istio在這方面就非常強(qiáng)大,兩者形成了完美的互補(bǔ)關(guān)系。

                微信搜索公眾號(hào):Java項(xiàng)目精選,回復(fù):java 領(lǐng)取資料 。

                由于考慮到各種grpc-spring-boot-starter可能會(huì)對(duì)Spring Boot與Istio的整合產(chǎn)生不可知的副作用,所以這一次我沒有用任何的grpc-spring-boot-starter,而是直接手寫了gRPC與Spring Boot的整合。不想借助第三方框架整合gRPC和Spring Boot的可以簡(jiǎn)單參考一下我的實(shí)現(xiàn)。

                編寫業(yè)務(wù)代碼

                首先使用Spring Initializr建立父級(jí)項(xiàng)目spring-boot-istio,并引入gRPC的依賴。pom文件如下:

                4.0.0 spring-boot-istio-api spring-boot-istio-server spring-boot-istio-client org.springframework.boot spring-boot-starter-parent 2.2.6.RELEASE site.wendev spring-boot-istio 0.0.1-SNAPSHOT spring-boot-istio Demo project for Spring Boot With Istio. pom 1.8 io.grpc grpc-all 1.28.1

                然后建立公共依賴模塊spring-boot-istio-api,pom文件如下,主要就是gRPC的一些依賴:

                spring-boot-istio site.wendev 0.0.1-SNAPSHOT 4.0.0 spring-boot-istio-api io.grpc grpc-all javax.annotation javax.annotation-api 1.3.2 kr.motd.maven os-maven-plugin 1.6.2 org.xolstice.maven.plugins protobuf-maven-plugin 0.6.1 com.google.protobuf:protoc:3.11.3:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:1.28.1:exe:${os.detected.classifier} /Users/jiangwen/tools/protoc-3.11.3/bin/protoc compile compile-custom

                建立src/main/proto文件夾,在此文件夾下建立hello.proto,定義服務(wù)間的接口如下:

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

                很簡(jiǎn)單,就是發(fā)送一個(gè)name返回一個(gè)帶name的message。

                然后生成服務(wù)端客戶端的代碼,并且放到j(luò)ava文件夾下。這部分內(nèi)容可以參考gRPC的官方文檔。

                有了API模塊之后,就可以編寫服務(wù)提供者(服務(wù)端)和服務(wù)消費(fèi)者(客戶端)了。這里我們重點(diǎn)看一下如何整合gRPC和Spring Boot。

                服務(wù)端

                業(yè)務(wù)代碼非常簡(jiǎn)單:

                /** * 服務(wù)端業(yè)務(wù)邏輯實(shí)現(xiàn) * * @author 江文 */@Slf4j@Componentpublic class HelloServiceImpl extends HelloWorldGrpc.HelloWorldImplBase { @Override public void sayHello(HelloWorldService.HelloRequest request, StreamObserver responseObserver) { // 根據(jù)請(qǐng)求對(duì)象建立響應(yīng)對(duì)象,返回響應(yīng)信息 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()); }}

                光有業(yè)務(wù)代碼還不行,我們還需要在應(yīng)用啟動(dòng)時(shí)把gRPC Server也給一起啟動(dòng)起來(lái)。首先寫一下Server端的啟動(dòng)、關(guān)閉等邏輯:

                /** * gRPC Server的配置——啟動(dòng)、關(guān)閉等 * 需要使用@Component注解注冊(cè)為一個(gè)Spring Bean * * @author 江文 */@Slf4j@Componentpublic class GrpcServerConfiguration { @Autowired HelloServiceImpl service; /** 注入配置文件中的端口信息 */ @Value(“${grpc.server-port}”) private int port; private Server server; public void start() throws IOException { // 構(gòu)建服務(wù)端 log.info(“Starting gRPC on port {}.”, port); server = ServerBuilder.forPort(port).addService(service).build().start(); log.info(“gRPC server started, listening on {}.”, port); // 添加服務(wù)端關(guān)閉的邏輯 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) { // 關(guān)閉服務(wù)端 server.shutdown(); } } public void block() throws InterruptedException { if (server != null) { // 服務(wù)端啟動(dòng)后直到應(yīng)用關(guān)閉都處于阻塞狀態(tài),方便接收請(qǐng)求 server.awaitTermination(); } }}

                定義好gRPC的啟動(dòng)、停止等邏輯后,就可以使用CommandLineRunner把它加入到Spring Boot的啟動(dòng)中去了:

                /** * 加入gRPC Server的啟動(dòng)、停止等邏輯到Spring Boot的生命周期中 * * @author 江文 */@Componentpublic class GrpcCommandLineRunner implements CommandLineRunner { @Autowired GrpcServerConfiguration configuration; @Override public void run(String… args) throws Exception { configuration.start(); configuration.block(); }}

                之所以要把gRPC的邏輯注冊(cè)成Spring Bean,就是因?yàn)樵谶@里要獲取到它的實(shí)例并進(jìn)行相應(yīng)的操作。

                這樣,在啟動(dòng)Spring Boot時(shí),由于CommandLineRunner的存在,gRPC服務(wù)端也就可以一同啟動(dòng)了。

                客戶端

                業(yè)務(wù)代碼同樣非常簡(jiǎn)單:

                /** * 客戶端業(yè)務(wù)邏輯實(shí)現(xiàn) * * @author 江文 */@RestController@Slf4jpublic class HelloController { @Autowired GrpcClientConfiguration configuration; @GetMapping(“/hello”) public String hello(@RequestParam(name = “name”, defaultValue = “JiangWen”, required = false) String name) { // 構(gòu)建一個(gè)請(qǐng)求 HelloWorldService.HelloRequest request = HelloWorldService.HelloRequest .newBuilder() .setName(name) .build(); // 使用stub發(fā)送請(qǐng)求至服務(wù)端 HelloWorldService.HelloResponse response = configuration.getStub().sayHello(request); log.info(“Server response received: [{}]”, response.getMessage()); return response.getMessage(); }}

                在啟動(dòng)客戶端時(shí),我們需要打開gRPC的客戶端,并獲取到channel和stub以進(jìn)行RPC通信,來(lái)看看gRPC客戶端的實(shí)現(xiàn)邏輯:

                /** * gRPC Client的配置——啟動(dòng)、建立channel、獲取stub、關(guān)閉等 * 需要注冊(cè)為Spring Bean * * @author 江文 */@Slf4j@Componentpublic 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(); // 通過(guò)channel獲取到服務(wù)端的stub stub = HelloWorldGrpc.newBlockingStub(channel); log.info(“gRPC client started, server address: {}:{}”, host, port); } public void shutdown() throws InterruptedException { // 調(diào)用shutdown方法后等待1秒關(guān)閉channel channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); log.info(“gRPC client shut down successfully.”); } public HelloWorldGrpc.HelloWorldBlockingStub getStub() { return this.stub; }}

                比服務(wù)端要簡(jiǎn)單一些。

                最后,仍然需要一個(gè)CommandLineRunner把這些啟動(dòng)邏輯加入到Spring Boot的啟動(dòng)過(guò)程中:

                /** * 加入gRPC Client的啟動(dòng)、停止等邏輯到Spring Boot生命周期中 * * @author 江文 */@Component@Slf4jpublic class GrpcClientCommandLineRunner implements CommandLineRunner { @Autowired GrpcClientConfiguration configuration; @Override public void run(String… args) { // 開啟gRPC客戶端 configuration.start(); // 添加客戶端關(guān)閉的邏輯 Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { configuration.shutdown(); } catch (InterruptedException e) { e.printStackTrace(); } })); }}

                編寫Dockerfile

                業(yè)務(wù)代碼跑通之后,就可以制作Docker鏡像,準(zhǔn)備部署到Istio中去了。

                在開始編寫Dockerfile之前,先改動(dòng)一下客戶端的配置文件:

                server: port: 19090spring: application: name: spring-boot-istio-clientserver-host: ${server-host}server-port: ${server-port}

                接下來(lái)編寫Dockerfile:

                服務(wù)端:

                FROM openjdk:8u121-jdkRUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo ‘Asia/Shanghai’ >/etc/timezoneADD /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

                可以看到這里添加了啟動(dòng)參數(shù),配合前面的配置,當(dāng)這個(gè)鏡像部署到Kubernetes集群時(shí),就可以在Kubernetes的配合之下通過(guò)服務(wù)名找到服務(wù)端了。

                同時(shí),服務(wù)端和客戶端的pom文件中添加:

                org.springframework.boot spring-boot-maven-plugin true com.spotify dockerfile-maven-plugin 1.4.13 javax.activation activation 1.1 default build push wendev-docker.pkg.coding.net/develop/docker/${project.artifactId} ${project.version} ${project.build.finalName}.jar

                這樣執(zhí)行mvn clean package時(shí)就可以同時(shí)把docker鏡像構(gòu)建出來(lái)了。

                編寫部署文件

                有了鏡像之后,就可以寫部署文件了:

                服務(wù)端:

                apiVersion: v1kind: Servicemetadata: name: spring-boot-istio-serverspec: type: ClusterIP ports: – name: http port: 18080 targetPort: 18080 – name: grpc port: 18888 targetPort: 18888 selector: app: spring-boot-istio-server—apiVersion: apps/v1kind: Deploymentmetadata: name: spring-boot-istio-serverspec: 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

                主要是暴露服務(wù)端的端口:18080和gRPC Server的端口18888,以便可以從Pod外部訪問服務(wù)端。

                客戶端:

                apiVersion: v1kind: Servicemetadata: name: spring-boot-istio-clientspec: type: ClusterIP ports: – name: http port: 19090 targetPort: 19090 selector: app: spring-boot-istio-client—apiVersion: apps/v1kind: Deploymentmetadata: name: spring-boot-istio-clientspec: 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,以便訪問客戶端并調(diào)用服務(wù)端。

                如果想先試試把它們部署到k8s可不可以正常訪問,可以這樣配置Ingress:

                apiVersion: networking.k8s.io/v1beta1kind: 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的網(wǎng)關(guān)配置文件與k8s不大一樣:

                apiVersion: networking.istio.io/v1alpha3kind: Gatewaymetadata: name: spring-boot-istio-gatewayspec: selector: istio: ingressgateway servers: – port: number: 80 name: http protocol: HTTP hosts: – “*”—apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: spring-boot-istiospec: hosts: – “*” gateways: – spring-boot-istio-gateway http: – match: – uri: exact: /hello route: – destination: host: spring-boot-istio-client port: number: 19090

                主要就是暴露/hello這個(gè)路徑,并且指定對(duì)應(yīng)的服務(wù)和端口。

                部署應(yīng)用到Istio

                首先搭建k8s集群并且安裝istio。我使用的k8s版本是1.16.0,Istio版本是最新的1.6.0-alpha.1,使用istioctl命令安裝Istio。建議跑通官方的bookinfo示例之后再來(lái)部署本項(xiàng)目。

                注:以下命令都是在開啟了自動(dòng)注入Sidecar的前提下運(yùn)行的

                我是在虛擬機(jī)中運(yùn)行的k8s,所以istio-ingressgateway沒有外部ip:

                $ kubectl get svc istio-ingressgateway -n istio-systemNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEistio-ingressgateway NodePort 10.97.158.232 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

                所以,需要設(shè)置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

                必須要等到兩個(gè)pod全部變?yōu)镽unning而且Ready變?yōu)?/2才算部署完成。

                接下來(lái)就可以通過(guò)

                curl -s http://${GATEWAY_URL}/hello

                訪問到服務(wù)了。如果成功返回了Hello, JiangWen. This message comes from gRPC.的結(jié)果,沒有出錯(cuò)則說(shuō)明部署完成。

                來(lái)源:blog.csdn.net/weixin_43887447/article/

                details/109605564

                鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。
                用戶投稿
                上一篇 2022年6月25日 09:23
                下一篇 2022年6月25日 09:23

                相關(guān)推薦

                聯(lián)系我們

                聯(lián)系郵箱:admin#wlmqw.com
                工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息