微服务与RPC问题汇总

微服务与RPC问题汇总

1. 什么是微服务?

wiki:微服务(英语:Microservices)是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic)的API集相互通信。

简单来说,微服务的架构风格就是将单个应用程序作为一组小型服务来开发,每个服务程序都在自己的进程中运行,并以轻量级通信方式进行通信。这些服务可以使用不同的编程语言编写,使用不同的数据存储技术,并尽量不用集中式方式进行管理。

用一句话来概括就是“分而治之,合而用之”。

2. 微服务与单体架构的对比?

单体架构的缺点如下:

  1. 复杂度随时间不断变大,代码量到几十万行的大项目难以维护;
  2. 技术债务逐渐上升,留下的bug越来越多;
  3. 耦合度太高,维护成本大——修复bug的时候引入新的bug;
  4. 部署、交付的时间长,新人熟悉环境、业务的时间越来越长;
  5. 技术选型成本高,后面想要引入新的技术或框架,成本和风险都很高;
  6. 可扩展性差。

微服务架构的优点如下:

  1. 单一职责。每个服务都有自己独立的业务逻辑,是一个高内聚、低耦合、单一原则的单元。
  2. 轻量级通信。服务之间用过轻量级的通信机制实现互联互通,通常是语言无关、平台无关的交互方式;
  3. 独立性。每个服务可以独立地进行开发、测试和部署;
  4. 进程隔离。每个服务高度自治,可以部署到不同的主机上。当其中一个服务挂掉时,不会影响其他服务。

当然,微服务也有它的不足之处

  1. 运行维护困难,任意一个模块的问题,可能会影响整个项目,debug困难;
  2. 分布式的复杂性。会使得项目变得更复杂;
  3. 接口成本高,若接口发生大变动,所有依赖的微服务都要发生调整;
  4. 重复劳动。某段业务多个模块共同使用,如果是单体架构,可以抽象为一个工具类。但是微服务架构中每个服务都可能需要,会导致代码重复;
  5. 业务不好分离。程序员对业务理解可能不同。

3. 什么是RPC?

RPC(Remote Procedure Call Protocol)是远程过程调用的缩写。它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。

比如,我们写这样一个代码:

1
Result := Add(1,2)

它会调用远程的(另一个进程,很可能在另一台服务器上)函数:

1
2
3
func Add(a int, b int)int{
return a + b
}

4. 为什么微服务需要RPC

使用微服务的一个好处就是,不限定服务的提供方使用什么技术选型,能够实现公司跨团队的技术解耦。但是这样子,如果没有一个统一的服务框架——RPC框架,则各个团队需要各自实现一套序列化、反序列化、网络框架、连接池、收发线程、超时处理等业务之外的重复技术劳动。统一RPC框架将上述的劳动进行了统一处理。

5. RPC的底层原理

一个完整的RPC主要包括三部分:

  1. 服务注册中心(Registry),负责将本地服务发布成远程服务,管理远程服务,提供给服务消费者使用。

  2. 服务提供者(RPC Server),负责提供服务接口定义与服务实现类。

  3. 服务消费者(RPC Client),负责通过远程代理对象调用远程服务。

服务提供者(Server)启动后主动向服务注册中心(Registry)注册机器IP、端口以及提供的服务列表;服务消费者(Client)启动时向服务注册中心(Registry)获取服务提供方地址列表;服务注册中心(Registry)可实现负载均衡和故障切换。

img

RPC调用过程为:

  1. 客户端想要发起一个远程过程调用,首先通过调用本地客户端Stub程序的方式调用想要使用的功能方法名;
  2. 客户端Stub程序接收到了客户端的功能调用请求,将客户端请求调用的方法名,携带的参数等信息做序列化操作,并打包成数据包。
  3. 客户端Stub查找到远程服务器程序的IP地址,调用Socket通信协议,通过网络发送给服务端。
  4. 服务端Stub程序接收到客户端发送的数据包信息,并通过约定好的协议将数据进行反序列化,得到请求的方法名和请求参数等信息。
  5. 服务端Stub程序准备相关数据,调用本地Server对应的功能方法进行,并传入相应的参数,进行业务处理。
  6. 服务端程序根据已有业务逻辑执行调用过程,待业务执行结束,将执行结果返回给服务端Stub程序。
  7. 服务端Stub程序将程序调用结果按照约定的协议进行序列化,并通过网络发送回客户端Stub程序。
  8. 客户端Stub程序接收到服务端Stub发送的返回数据,对数据进行反序列化操作,并将调用返回的数据传递给客户端请求发起者。
  9. 客户端请求发起者得到调用结果,整个RPC调用过程结束。

RPC要面临的三个问题:

(1)Call ID映射。本地调用中,函数体是直接通过函数指针来指定的,调用函数时,编译器会自动调用它相应的函数指针。但在远程调用中,函数指针是不行的,因为两个进程的地址空间完全不一样。所以,在RPC中所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 :Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查该表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

(2)序列化和反序列化。客户端如何将参数值传给远程的函数就成了一个问题在本地调用中,只需要把参数压到栈里,函数自己去栈里读即可。在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化(也叫编码和解码)。同理,从服务端返回的值也需要序列化反序列化的过程。

(3)网络传输。远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2,Java的Netty也属于这层的东西。

RPC与HTTP协议的对比

RPC HTTP
传输协议 可以基于TCP协议,也可以基于HTTP协议 基于HTTP协议
传输效率 RPC使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率 如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装一下是可以作为一个RPC来使用的,这时标准RPC框架更多的是服务治理
性能消耗 可以基于thrift实现高效的二进制传输 大部分是通过json来实现的,字节大小和序列化耗时都比thrift要更消耗性能
负载均衡 基本都自带了负载均衡策略 需要配置Nginx,HAProxy来实现
服务治理 能做到自动通知,不影响上游 需要事先通知,修改Nginx/HAProxy配置
总结 主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。 主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。

6. 什么是ProtoBuf

Protocol Buffers (Protobuf) 是一种谷歌开发的二进制通信格式,用于序列化结构数据,截至目前,谷歌使用 protobuf 已经超过十年了。 Protobuf 的出现并不是为了解决新的问题,而是用更加现代的方式让网络传输更加高效,总的来说它有如下几个特点:

  1. 二进制格式,而不是像 JSON 与 XML 是基于文本的,因此非常节省空间。
  2. 对各种模式丰富且易上手的支持。
  3. 支持多种语言下的解析。

原理

  • 使用varint来表示数字,越小的数字占用越少的字节数。

  • 使用msb(最高有效位)来进行标识,当msb为1时,下一个字节继续表示这个数字;当msb为0时,这是最后一个字节。

  • 对于字符串而言,不需要msb,因为有表示string长度的字段。

具体流程(以数字2009为例):

  1. 将2009 转换为二进制 11111011001

  2. 将其分割为 0001111 1011001

  3. 反转其顺序 1011001 0001111

  4. 添加 MSB 标识为 01011001 00001111

  5. 设置MSB ,11011001 00001111

最后用16进制表示为 D9 0F

小结

  1. 在微服务之间通信时,protobuf 更合适,在公开 API 或与浏览器通信时JSON 与 xml 更合适。
  2. protobuf 更加节省空间,高效。
  3. protobuf 放弃了可读性。
  4. protobuf 使用的编码方式可以使数据更加紧凑。
  5. protobuf 依赖生成的代码,需要一个protobuf 编译器根据你编写的 .proto 定义的数据生成,也称作消息。
  6. 你可以根据生成的代码初始化或解析发生的数据或接受的数据。

7. 几种框架的对比

待续

参考资料

[1] https://blog.csdn.net/weixin_46742102/article/details/112169275

[2] https://blog.csdn.net/weixin_41667472/article/details/124703669

[3] https://zh.wikipedia.org/zh-cn/%E5%BE%AE%E6%9C%8D%E5%8B%99


微服务与RPC问题汇总
https://wuhlan3.gitee.io/2022/08/05/微服务问题汇总/
Author
Wuhlan3
Posted on
August 5, 2022
Licensed under