微言Netty:分布式服务框架

本文介绍了如何使用Netty构建分布式服务框架TinyWhale,涵盖整体架构模型、即时通讯框架设计要素,如编解码、粘包拆包处理,以及自定义协议设计、鉴权、心跳包、断线重连等核心功能。文章通过实例代码展示了Netty在分布式服务中的应用,旨在提供一种简洁、可靠的解决方案。
摘要由CSDN通过智能技术生成

整体架构模型

言归正传,我们继续Netty之旅吧。
分布式服务框架,特点在于分布式,功能在于服务提供,目标在于即时通讯框架整合。由于其能够让服务端和客户端进行解耦,让调用方和被调用方处于网络的两端但是通讯毫无障碍,从而能够扩充整体的业务规模。对于一些业务场景稍微大一些的公司,一般都会采用分布式服务框架。包括目前兴起的微服务设计,更是让分布式服务框架炙手可热。那么我们今天的目标,就是来打造一款手写的分布式服务框架TinyWhale,中文名巨小鲸(手写作品,本文讲解专用, 暂无更多精力打造成开源^_^),接下来让我们开始吧。
说道目前比较流行的分布式服务框架,朗朗上口的有Dubbo,gRpc,Spring Cloud等。这些框架无一例外都有着如下图所示的整体架构模型:
 


整体流程解释如下:
 

  1. 启动注册,指服务端开始启动并将服务注册到注册中心中。注册中心一般用ZooKeeper或者Consul实现。
  2. 启动并监听,指客户端启动并监听注册中心的服务列表。
  3. 有变更则通知,指客户端订阅的服务列表发生改变,则将更新客户端缓存。
  4. 接口调用,指客户端进行接口调用,此调用将首先会向服务端发起连接操作,然后进行鉴权,之后发起接口调用操作。
  5. 客户端数据监控,指监控端会监控客户端的行为和数据并做记录。
  6. 服务端数据监控,指监控端会监控服务端的行为和数据并做记录。
  7. 数据分析并衍生出其他业务策略,指监控端会根据服务端和客户端调用数据,来衍生出新的业务策略,比如熔断,防刷,异地多活等。


当然,上面的流程是比较标准的分布式服务框架所涉及的环节。在实际设计过程中,可以根据具体的使用方式进行调整,比如监控端只监控服务端数据,因为客户端我不用关心。或者客户端不设置服务地址列表缓存,每次调用前都从注册中心重新获取最新的服务地址列表等。
TinyWhale,由于设计的初衷是简单,可靠,高性能,所以这里我们去除了监控端,所以流程5、6、7都会拿掉,如果有需要使用到监控端的,可以自行根据提供的接口来实现一套,这里将不再对监控端做过多的赘述。
 

即时通讯框架设计涉及要素

编解码设计

编解码设计任何通讯类框架,编解码处理是无法绕过的一个话题。因为网络上只能流淌字节流,所以这种特性催生了很多的框架。由于这块的工具非常多,诸如ProtoBuf,Marshalling,MessagePack等,所以喜欢用哪个,全凭喜好。这里我使用ProtoStuff来作为我们的编解码工具,原因有二:其一是易用性,无需编写描述文件;其二是高性能,性能属于T0级别梯队。下面来具体看看吧:
首先看看我们的编解码类:
 


其中serialize方法,用于将类对象编码成字节数据,然后通过本机发送出去。而deserialize方法,则用于将缓冲区中的字节数据还原为类对象。考虑到设计的简洁性,我这里并未抽象出一个公共的codecInterface和codecFactory来适配不同的编解码工具,大家可以自行来进行设计和适配。
有了编解码的辅助类了,如何集成到Netty中呢?
在Netty中,将对象编码成字节数据的操作,我们可以使用已有的MessageToByteEncoder类来进行操作,继承自此类,然后override encode方法,即可利用自己实现的protostuff工具类来进行编码操作。
 


同样的,将字节数据解码成对象的操作,我们可以使用已有的ByteToMessageDecoder类来进行操作,继承自此类,然后override decode方法,即可利用自己实现的protostuff工具类来进行解码操作。
 

粘包拆包设计

之前章节已经讲过,我们直接拿来展示下。
粘包拆包,顾名思义,粘包,就是指数据包黏在一块了;拆包,则是指完整的数据包被拆开了。由于TCP通讯过程中,会将数据包进行合并后再发出去,所以会有这两种情况发生,但是UDP通讯则不会。下面我们以两个数据包A,B来讲解具体的粘包拆包过程:
 


第一种情况,A数据包和B数据包被分别接收且都是整包状态,无粘包拆包情况发生,此种情况最佳。
 


第二种情况,A数据包和B数据包在一块儿且一起被接收,此种情况,即发生了粘包现象,需要进行数据包拆分处理。
 


第三种情况,A数据包和B数据包的一部分先被接收,然后收到B数据包的剩余部分,此种情况,即发生了拆包现象,即B数据包被拆分。
 


第四种情况,A数据包的一部分先被接收,然后收到A数据包的剩余部分和B数据包的完整部分,此种情况,即发生了拆包现象,即A数据包被拆分。
 


第五种情况,也是最复杂的一种,先收到A数据包的部分,然后收到A数据包剩余部分和B数据包的一部分,最后收到B数据包的剩余部分,此种情况也发生了拆包现象。
至于为什么会发生这种问题,根本原因在于缓冲区中的数据,Server端不大可能一次性的全部发出去,Client端也不大可能一次性正好把数据全部接收完毕。所以针对这些发生了粘包或者拆包的数据,我们需要找到合适的手段来让其形成整包,以便于进行业务处理。好消息是,Netty已经为我们准备了多种处理工具,我们只需要简单的动动代码,就可以了,他们分别是:LineBasedFrameDecoder,StringDecoder,
LengthFieldBasedFrameDecoder,DelimiterBasedFrameDecoder,FixedLengthFrameDecoder。
由于上节中,我们讲解了其大概用法,所以这里我们以
LengthFieldBasedFrameDecoder来着重讲解其使用方式。

LengthFieldBasedFrameDecoder:顾名思义,固定长度的粘包拆包器,此解码器主要是通过消息头部附带的消息体的长度来进行粘包拆包操作的。由于其配置参数过多(maxFrameLength,lengthFieldOffset,lengthFieldLength,lengthAdjustment,initialBytesToStrip等),所以可以最大程度的保证能用消息体长度字段来进行消息的解码操作。这些不同的配置参数可以组合出不同的粘包拆包处理效果,在此Rpc框架的设计过程中,我的使用方式如下:
 


是不是代码很简单?
翻阅
LengthFieldBasedFrameDecoder源码,实现原理一览无余,由于网上讲解足够多,而且源码中的讲解也足够详细,所以这里不再做过多阐释。具体的原理解释可以看这里:
LengthFieldBasedFrameDecoder。
 

自定义协议设计

在进行网络通讯的时候,数据包从一端传输到另一端,然后被解析,被消化。这里就涉及到一个知识点,数据包是怎样定义的,才能让另一方识别出数据包所代表的业务含义。这就涉及到自定义传输协议的设计,我们来看看具体怎么设计。
首先,我们需要明确自己定义的协议需要承载哪些业务数据,一般说来包含如下的业务要点:
 

  1. 自定义协议需要让双端识别出哪些包是心跳包
  2. 自定义协议需要让双端识别出哪些包是鉴权包
  3. 自定义协议需要让双端识别出哪些包是具体的业务包
  4. 自定义协议需要让双端识别出哪些包是上下线包等等(本条规则适用于IM系统)


不同的系统在设计的时候,自定义协议的设计是不一样的,比如分布式服务框架,其业务包则需要包含客户端调用了哪个方法,入参中传入了哪些参数等。物联网采集框架,其业务包则需要包含底层采集硬件上传的数据中,哪些数值代表空气温度,哪些数值代表光照强度等。同样的,IM系统则需要知道当前的聊天是谁发出的,想发给谁等等。正是由于不同系统承载的业务不同,所以导致自定义协议种类繁多,不一而足。性能表现也是错落有致。复杂程度更是简繁并举。
那么针对要讲解的分布式服务框架,我们来详细看一下设计方式。
首先定义一个NettyMessage泛型类,此泛型类是一个基础类,包含了会话ID,消息类型,消息体三个字段。这三个字段是服务端和客户端进行数据交换过程中,必传的三个字段,所以整体抽取出来,放到了这里。
 


然后,针对客户端,定义一个NettyRequest类,包含基本的请求ID,调用的类名称,方法名称,入参类型,入参值。
 


最后,客户端的请求传送到服务端,服务端需要反射调用方法并将结果返回,服务端的NettyResponse类,则包含了请求ID,用于识别请求来自于哪个客户端,error错误,result结果三个字段:
 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值