网易首页 > 网易号 > 正文 申请入驻

使用Spring Boot+gRPC构建微服务并部署到Istio

0
分享至

  作为Service Mesh和云原生技术的忠实拥护者,我却一直没有开发过Service Mesh的应用。正好最近受够了Spring Cloud的“折磨”,对Kubernetes也可以熟练使用了,而且网上几乎没有Spring Boot微服务部署到Istio的案例,我就开始考虑用Spring Boot写个微服务的Demo并且部署到Istio。项目本身不复杂,就是发送一个字符串并且返回一个字符串的最简单的Demo。

题外话:我本来是想用Spring MVC写的——因为周围有的同学不相信Spring MVC也可以开发微服务,但是Spring MVC的各种配置和依赖问题把我整的想吐,为了少掉几根头发,还是用了方便好用的Spring Boot。

  为什么要用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与Kubernetes功能上有重合,且部分功能也存在冲突,二者很难完美配合。

  替代Spring Cloud的选择有没有呢?有!它 就是 。

  Istio 彻底把治理逻辑从业务代码中剥离出来,成为了独立的进程(Sidecar)。部署时两者部署在一起,在一个Pod里共同运行,业务代码完全感知不到Sidecar的存在。这就实现了治理逻辑对业务代码的零侵入——实际上不仅是代码没有侵入,在运行时两者也没有任何的耦合。这使得不同的微服务完全可以使用不同语言、不同技术栈来开发,也不用担心服务治理问题,可以说这是一种很优雅的解决方案了。

  所以,“为什么要使用 ”这个问题也就迎刃而解了——因为Istio解决了传统微服务诸如业务逻辑与服务治理逻辑耦合、不能很好地实现跨语言等痛点,而且非常容易使用。只要会用Kubernetes,学习Istio的使用一点都不困难。

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

  在微服务架构中,服务之间的通信是一个比较大的问题,一般采用RPC或者 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的可以简单参考一下我的实现。

  编写业务代码

  首先使用Spring Initializr建立父级项目spring-boot-istio,并引入gRPC的依赖。pom文件如下:

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">4.0.0spring-boot-istio-apispring-boot-istio-serverspring-boot-istio-clientorg.springframework.bootspring-boot-starter-parent2.2.6.RELEASEsite.wendevspring-boot-istio0.0.1-SNAPSHOTspring-boot-istioDemo project for Spring Boot With Istio.pom
1.8
io.grpcgrpc-all1.28.1

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

  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">spring-boot-istiosite.wendev0.0.1-SNAPSHOT4.0.0
spring-boot-istio-api
io.grpcgrpc-alljavax.annotationjavax.annotation-api1.3.2
kr.motd.mavenos-maven-plugin1.6.2org.xolstice.maven.pluginsprotobuf-maven-plugin0.6.1com.google.protobuf:protoc:3.11.3:exe:${os.detected.classifier}grpc-javaio.grpc:protoc-gen-grpc-java:1.28.1:exe:${os.detected.classifier}/Users/jiangwen/tools/protoc-3.11.3/bin/protoccompilecompile-custom

  建立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返回一个带namemessage

  然后生成服务端和客户端的代码,并且放到java文件夹下。这部分内容可以参考gRPC的官方文档。

  有了API模块之后,就可以编写服务提供者(服务端)和服务消费者(客户端)了。这里我们重点看一下如何整合gRPC和Spring Boot。

  
服务端

  业务代码非常简单:

  /*** 服务端业务逻辑实现** @author 江文* @date 2020/4/12 2:49 下午*/@Slf4j@Componentpublic class HelloServiceImpl extends HelloWorldGrpc.HelloWorldImplBase { @Overridepublic void sayHello(HelloWorldService.HelloRequest request,StreamObserver 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的配置——启动、关闭等* 需要使用@Component注解注册为一个Spring Bean** @author 江文* @date 2020/4/12 2:56 下午*/@Slf4j@Componentpublic class GrpcServerConfiguration { @AutowiredHelloServiceImpl 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 下午*/@Componentpublic class GrpcCommandLineRunner implements CommandLineRunner { @AutowiredGrpcServerConfiguration configuration; @Overridepublic void run(String... args) throws Exception {configuration.start();configuration.block();}}

  之所以要把gRPC的逻辑注册成Spring Bean,就是因为在这里要获取到它的实例并进行相应的操作。

  这样,在启动Spring Boot时,由于CommandLineRunner的存在,gRPC服务端也就可以一同启动了。

  
客户端

  业务代码同样非常简单:

  /*** 客户端业务逻辑实现** @author 江文* @date 2020/4/12 3:26 下午*/@RestController@Slf4jpublic class HelloController { @AutowiredGrpcClientConfiguration 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的客户端,并获取到channelstub以进行RPC通信,来看看gRPC客户端的实现逻辑:

  /*** gRPC Client的配置——启动、建立channel、获取stub、关闭等* 需要注册为Spring Bean** @author 江文* @date 2020/4/12 3:27 下午*/@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() { // 开启channelchannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); // 通过channel获取到服务端的stubstub = HelloWorldGrpc.newBlockingStub(channel);log.info("gRPC client started, server address: {}:{}", host, port);} public void shutdown() throws InterruptedException { // 调用shutdown方法后等待1秒关闭channelchannel.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@Slf4jpublic class GrpcClientCommandLineRunner implements CommandLineRunner { @AutowiredGrpcClientConfiguration configuration; @Overridepublic void run(String... args) { // 开启gRPC客户端configuration.start();// 添加客户端关闭的逻辑Runtime.getRuntime().addShutdownHook(new Thread(() -> { try {configuration.shutdown();} catch (InterruptedException e) {e.printStackTrace();}}));}}

  
编写Dockerfile

  业务代码跑通之后,就可以制作Docker镜像,准备部署到Istio中去了。

  在开始编写Dockerfile之前,先改动一下客户端的配置文件:

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

  接下来编写Dockerfile:

  服务端:

  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

  主要是规定服务端应用的端口为18080,并且在容器启动时让服务端也一起启动。

  客户端:

  FROM openjdk:8u121-jdkRUN /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

  可以看到这里添加了启动参数,配合前面的配置,当这个镜像部署到集群时,就可以在的配合之下通过服务名找到服务端了。

  同时,服务端和客户端的pom文件中添加:

  org.springframework.bootgroupId>spring-boot-maven-pluginartifactId>trueexecutable>configuration>plugin>com.spotifygroupId>dockerfile-maven-pluginartifactId>1.4.13version>javax.activationgroupId>activationartifactId>1.1version>dependency>dependencies>defaultid>buildgoal>pushgoal>goals>execution>executions>wendev-docker.pkg.coding.net/develop/docker/${project.artifactId} repository>${project.version}tag>${project.build.finalName}.jarJAR_FILE>buildArgs>configuration>plugin>plugins>build>

  这样执行mvn clean package时就可以同时把docker镜像构建出来了。

  编写部署文件

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

  服务端:

  apiVersion: v1kind: Servicemetadata:name: spring-boot-istio-serverspec:type: ClusterIPports:- name: httpport: 18080targetPort: 18080- name: grpcport: 18888targetPort: 18888selector:app: spring-boot-istio-server---apiVersion: apps/v1kind: Deploymentmetadata:name: spring-boot-istio-serverspec:replicas: 1selector:matchLabels:app: spring-boot-istio-servertemplate:metadata:labels:app: spring-boot-istio-serverspec:containers:- name: spring-boot-istio-serverimage: wendev-docker.pkg.coding.net/develop/docker/spring-boot-istio-server:0.0.1-SNAPSHOTimagePullPolicy: Alwaystty: trueports:- name: httpprotocol: TCPcontainerPort: 18080- name: grpcprotocol: TCPcontainerPort: 18888

  主要是暴露服务端的端口:18080和gRPC Server的端口18888,以便可以从Pod外部访问服务端。

  客户端:

  apiVersion: v1kind: Servicemetadata:name: spring-boot-istio-clientspec:type: ClusterIPports:- name: httpport: 19090targetPort: 19090selector:app: spring-boot-istio-client---apiVersion: apps/v1kind: Deploymentmetadata:name: spring-boot-istio-clientspec:replicas: 1selector:matchLabels:app: spring-boot-istio-clienttemplate:metadata:labels:app: spring-boot-istio-clientspec:containers:- name: spring-boot-istio-clientimage: wendev-docker.pkg.coding.net/develop/docker/spring-boot-istio-client:0.0.1-SNAPSHOTimagePullPolicy: Alwaystty: trueports:- name: httpprotocol: TCPcontainerPort: 19090

  主要是暴露客户端的端口19090,以便访问客户端并调用服务端。

  如果想先试试把它们部署到k8s可不可以正常访问,可以这样配置Ingress:

  apiVersion: networking.k8s.io/v1beta1kind: Ingressmetadata:name: nginx-webannotations: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.sitehttp:paths:- path: /backend:serviceName: spring-boot-istio-clientservicePort: 19090

  Istio的网关配置文件与k8s不大一样:

  apiVersion: networking.istio.io/v1alpha3kind: Gatewaymetadata:name: spring-boot-istio-gatewayspec:selector:istio: ingressgatewayservers:- port:number: 80name: httpprotocol: HTTPhosts:- "*"---apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata:name: spring-boot-istiospec:hosts:- "*"gateways:- spring-boot-istio-gatewayhttp:- match:- uri:exact: /helloroute:- destination:host: spring-boot-istio-clientport:number: 19090

  主要就是暴露/hello这个路径,并且指定对应的服务和端口。

  部署应用到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-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

  所以,需要设置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.的结果,没有出错则说明部署完成。

  本项目的所有代码都上传到了GitHub,地址:https://github.com/WenDev/spring-boot-istio-demo

  免责声明:

  本公众号部分分享的资料来自网络收集和整理,所有文字和图片版权归属于原作者所有,且仅代表作者个人观点,与本公众号无关,文章仅供读者学习交流使用,并请自行核实相关内容,如文章内容涉及侵权,请联系后台管理员删除。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
笑不活了,中俄免签的第一批受害者出现了,要被评论区笑死了

笑不活了,中俄免签的第一批受害者出现了,要被评论区笑死了

奇特短尾矮袋鼠
2024-06-07 15:54:13
无言以对:普京称乌克兰必须撤军才有谈判?俄军投入70万

无言以对:普京称乌克兰必须撤军才有谈判?俄军投入70万

项鹏飞
2024-06-15 15:40:41
太炸裂!四川妈妈花40万“暗考”被举报:生孩子,就是开盲盒……

太炸裂!四川妈妈花40万“暗考”被举报:生孩子,就是开盲盒……

桌子的生活观
2024-06-19 11:57:33
文人无耻之最……

文人无耻之最……

小刀99
2024-06-18 06:12:26
爸爸晒女儿结婚10年的变化,照片令人心疼,前后对比太明显

爸爸晒女儿结婚10年的变化,照片令人心疼,前后对比太明显

布谷妈妈
2024-06-18 21:30:54
知情人曝陈晓婚变内幕:男方宁愿净身出户也要离婚,女方饱受折磨

知情人曝陈晓婚变内幕:男方宁愿净身出户也要离婚,女方饱受折磨

萌神木木
2024-06-19 13:31:14
央视晚上18点直播有变,中国女排VS日本,蔡斌手握巨大优势

央视晚上18点直播有变,中国女排VS日本,蔡斌手握巨大优势

极度说球
2024-06-19 21:29:15
姓氏太稀有了是什么体验?网友:听到我的名字,大家只有一个反应

姓氏太稀有了是什么体验?网友:听到我的名字,大家只有一个反应

奇特短尾矮袋鼠
2024-06-19 19:15:58
黄一鸣正式接受专访,坦言可以做DNA,支持女儿继承王思聪财产!

黄一鸣正式接受专访,坦言可以做DNA,支持女儿继承王思聪财产!

阿桥侃娱乐
2024-06-20 08:31:22
社会上流行着“不欠祖国只欠父母”的思想,非常可怕

社会上流行着“不欠祖国只欠父母”的思想,非常可怕

雪莉故事汇
2024-06-18 08:56:23
一派出所副所长,头部中弹身亡!

一派出所副所长,头部中弹身亡!

鲁中晨报
2024-06-19 08:47:05
张学友演唱会被投诉唱粤语歌!本尊回应:国语还行但自己来自香港

张学友演唱会被投诉唱粤语歌!本尊回应:国语还行但自己来自香港

娱乐白名单
2024-06-18 10:09:38
枢密院十号:连乌克兰都不要的,台湾还当个宝……

枢密院十号:连乌克兰都不要的,台湾还当个宝……

环球网资讯
2024-06-19 23:58:21
宣布停工停产,昔日跨境巨头已人去楼空

宣布停工停产,昔日跨境巨头已人去楼空

跨通社
2024-06-19 15:30:03
美国博主:我女儿对花生过敏,在中国吃却没事!难道中国是假花生

美国博主:我女儿对花生过敏,在中国吃却没事!难道中国是假花生

咖啡店的老板娘
2024-06-19 20:37:54
姜萍决赛的挑战!

姜萍决赛的挑战!

新动察
2024-06-19 09:45:42
内塔尼亚胡“激怒拜登顾问”!白宫决定取消原定今日举行的美以高级别会议

内塔尼亚胡“激怒拜登顾问”!白宫决定取消原定今日举行的美以高级别会议

环球网资讯
2024-06-20 06:21:17
六大球星拉胯!欧洲杯踢懵曼城,球迷怒怼:瓜帅只培养体系球员

六大球星拉胯!欧洲杯踢懵曼城,球迷怒怼:瓜帅只培养体系球员

祥谈体育
2024-06-19 11:14:51
上海300个小区都发现了!突然窜出、尖叫,不仅不怕人,还主动攻击狗,紧急提醒

上海300个小区都发现了!突然窜出、尖叫,不仅不怕人,还主动攻击狗,紧急提醒

上观新闻
2024-06-18 21:36:20
官宣!中国女排2项重大人事任命,蔡斌未来去向浮现,恭喜朱婷

官宣!中国女排2项重大人事任命,蔡斌未来去向浮现,恭喜朱婷

二哥聊球
2024-06-19 20:42:27
2024-06-20 11:58:45
IT架构师联盟
IT架构师联盟
IT架构实战分享
690文章数 7654关注度
往期回顾 全部

科技要闻

苹果回应AI仅限iPhone15Pro:不是为卖新机

头条要闻

冯德莱恩谋求连任欧委会主席 遭意大利女总理强烈反对

头条要闻

冯德莱恩谋求连任欧委会主席 遭意大利女总理强烈反对

体育要闻

绿军的真老大,开始备战下赛季了

娱乐要闻

离谱!24岁女偶像参加涉毒男星生日聚会,坐在桌边陪赌

财经要闻

茅台大跌,谁的锅?

汽车要闻

售价11.79-14.39万元 新一代哈弗H6正式上市

态度原创

本地
时尚
教育
游戏
公开课

本地新闻

中式沙拉宇宙的天花板,它必须有姓名

“T恤”作为夏季的基础款,竟然有这么多种穿法

教育要闻

2023年985院校初次就业率分析,985高校主要就业途径为升学!

寻求改变?育碧《孤岛惊魂7》或将有第三人称模式

公开课

近视只是视力差?小心并发症

无障碍浏览 进入关怀版