gRPC Java 和Golang下server 端消息发送源码分析比较

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: gRPC Java 和Golang下server 端消息发送源码比较 概述 RPC是平时开发中经常用到的通信框架,gRPC是Google版本的rpc,开发中涉及到跨语言或者单纯通信需求时,gRPC是个不错的选择。

gRPC Java 和Golang下server 端消息发送源码比较

概述

RPC是平时开发中经常用到的通信框架,gRPC是Google版本的rpc,开发中涉及到跨语言或者单纯通信需求时,gRPC是个不错的选择。
gRPC的接口定义使用谷歌自己的protobuf,java+golang 两种开发语言下protoc的编译详见https://yq.aliyun.com/articles/679610,这里不解释protobuf概念了,自行github。这里重点从源码层对比下如下通信设计中,从服务端实时推送消息到服务端,java和golang两种语言环境下服务端实现的差异性,以及编码过程中的注意点。

接口定义

先看下接口文件的定义:

service EasyAgentService {
    rpc registerSidecar(RegisterRequest) returns (RegisterResponse);
    rpc readyForControl(ControlRequest)  returns (stream ControlResponse);
    rpc reportEvent(Event)               returns (EmptyResponse);
}

想要实现的功能

服务端开启服务监听,客户端主动与服务端连接并注册信息,服务端hold住连接流stream并实时推送消息到客户端,客户端通过reportEvent上报执行结果。

接口说明

接口 作用 调用次数
registerSidecar 客户端主动连接服务端 连接成功仅调用一次,失败重试
readyForControl 客户端发起通话 连接成功仅调用一次,失败重试
reportEvent 客户端上报执行结果 多次

java/golang 服务端生成代码比较

java 服务端源码分析

文件列表
image

java 服务端需要继承实现的三个主要方法

image

三个方法都有两个参数,第一个参数是客户端报上来的请求参数,第二个参数服务端业务代码里可以把反馈给到客户端的顶层对象,为什么说是顶层对象,因为到io.grpc.stub.StreamObserver这里已经封装了底层通信的细节,业务代码只能通过以下三个interface来响应客户端:

image

有一个细节可以注意一下,三个接口的返回值都是 void,这会直接影响到服务端的业务逻辑编写,下面对比了golang版本生成的代码我们仔具体比较下。这里简单说下这三个接口的作用:

接口名 作用 返回值
onNext 服务端推送消息 void
onCompleted 服务端推送EOF void
onError 服务端推送错误信息 void

golang 服务端关键源码

文件列表

image

golang 服务端需要继承实现的三个主要方法

image

跟java版本比较,golang 服务端生成的三个接口着重看下ReadyForControl的EasyAgentService_ReadyForControlServer参数,该接口提供了一个Send方法,且有返回值 error,这个跟java的代码实现不一样,这回给业务代码带来哪些影响呢,再回顾下开头设计这三个接口的初衷:

服务端hold住连接流stream并实时推送消息到客户端

下面我们来看下由于服务端生成代码差异带来的两种不同的业务代码写法。

java golang 服务端关键业务代码比较

golang 版本 readyForControl实现

image

服务端接收到readyForControl以后,保存一个EasyAgentService_ReadyForControlServer对象到Map里,然后启动一个for select 循环,for循环里通过281行的channel与其他业务模块进行交互,正常情况channel里没数据时,for 循环会阻塞在281行,当有数据时则能实时拿到数据并通过stream下发。着重关注下276/277/287行,在stream结束或者有异常情况下服务端可以实时感知到,服务端可以根据stream的状态判断是否需要退出循环。小结下,golang这边使用channel的特性+对stream状态的实时感知,可以完美达到“服务端hold住连接流stream并实时推送消息到客户端”的功效,没有其他后顾之忧。下面我们再来看下java端实现同样逻辑的代码。

java 版本 readyForControl实现

image

跟golang版本比较,java版本readyForControl的实体也是一个大循环。java没有channel这么好用的数据结构,不过可以通过redis等强大的支撑实现跟channel一样的机制,184行用brpop的方式阻塞监听redis里的消息队列,功效跟golang的channel一样;但java这边循环体里的处理就没有golang那边那么干净利落了,原因如下:

  • java这边的StreamObserver 只提供三个无返回值的操作接口;
  • java这变在流中断的情况下,onNext/onCompleted仍然可以正常调用;
  • 流异常或者结束并无通知。

所以java的循环里不知道何时需要退出循环,如果异常情况不退出循环,在客户端多次调用readyForControl时服务端就灾难了,服务端无法判断 哪个流是正确的,已经关闭的流有很大概率能消费redis的数据并成功onNext出去,这样活着的流就拿会不到数据。为了解决这个问题,再次使用了redis,客户端每次注册上来时保存一个注册时间到redis里【agentid->time】,注册时间传入while循环,循环体里每次取到数据判断一下redis里的注册时间有无发生变化,如果变化了说明客户端再次
注册上来,需要把任务放回redis,然后退出当前循环;新注册上来的循环不会退出。这样解决了矛盾,但增加了程序复杂度,后续维护起来成本较高。相比之下,实现同样的业务逻辑,golang的实现方式显得优雅很多。

总结

通过上面两个代码片段,比较了java和golang 生成grpc 服务端代码的差异,以及完成同样业务逻辑的差别。带来差别的主要原因是java版本grpc服务端通信的实现是无发送确认的,同时通信流也是无状态的,这个差异最终导致了两种不同的代码逻辑。

思考

java的实现还可以优化么,如果redis这边用订阅模式是不是效果更好,小熊熊在这抛砖引玉了,希望大神阅后出招。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7天前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
36 3
Golang语言之gRPC程序设计示例
|
3月前
|
SQL IDE Java
Java连接SQL Server数据库的详细操作流程
Java连接SQL Server数据库的详细操作流程
|
24天前
|
前端开发 Java
Java HotSpot(TM) 64-Bit Server VM warning
Java HotSpot(TM) 64-Bit Server VM warning
391 1
|
24天前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
26天前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
18天前
|
Java 开发工具 git
【Azure 应用服务】本地Git部署Java项目到App Server,访问无效的原因
【Azure 应用服务】本地Git部署Java项目到App Server,访问无效的原因
|
3月前
|
存储 Java 测试技术
滚雪球学Java(66):Java之HashMap详解:深入剖析其底层实现与源码分析
【6月更文挑战第20天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
36 3
滚雪球学Java(66):Java之HashMap详解:深入剖析其底层实现与源码分析
|
2月前
|
缓存 监控 Java
(十)深入理解Java并发编程之线程池、工作原理、复用原理及源码分析
深入理解Java并发编程之线程池、工作原理、复用原理及源码分析
|
3月前
|
存储 并行计算 算法
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
|
3月前
|
SQL IDE Java
Java连接SQL Server数据库的详细操作流程
Java连接SQL Server数据库的详细操作流程