微服务之协议概述

微服务协议

互联网协议很多,TCP IP 是基础协议,在它之上有众多应用层协议,这里关注的微服务以什么协议向外提供服务, 即以什么方式, 或者说以什么手段, 通过什么媒介来提供面向用户或者其他服务提供他们所需要的服务。传统的单体服务对外一般提供RPC (远程方法调用)的接口, 对内的组件之间通过方法调用或者线程/进程间通信就行了.

而微服务一般所提供的服务都是节点与节点之间的远程分布式调用, 使用基于流式的TCP 连接或基于数据包的UDP 连接之上的应用层协议

协议都是分层的, OSI的七层网络模型中, 我们关心的是传输层以上, 主要是应用层的协议

协议分类

按照它所针对的语义可以分为

面向资源

比如 REST, 主要是对于资源的存取和修改

面向命令

比如SOAP, RMI,RPC , 主要是指对于方法, 命令及过程的远程调用

面向事件

比如 XMPP, JMS,AMQP, 主要是对于消息的传递和转发

按照远程调用协议的编码可以分为

文本协议

例如 HTTP + JSON/XML, SIP

二进制协议

例如 WebSocket + BSon/Protobuf

按照协议的用途可以分为

信令及控制协议

例如 SIP, SDP, Jingle, ROAP

媒体传输协议

例如 HTTP, RTP, RTMP

安全相关协议

例如 TLS, DTLS, oAuth2

按交互方式分类

基本上是 Request 请求/ Response 响应 方式, 这其中的响应有所区别, 主要就是看是不是最终响应

请求/搞定

client->server: POST /calls
server-->client: 201 created

或者

client->server: send request PDU
server-->client: send response PDU

请求/收到

这时候, 一般客户端只是收到一个接受的响应, 最终结果需要客户端轮询和给一个回调的地址

client->server: POST /orders (including callbackUrl)
server-->client: 202 accepted
client->server: GET /orders/tasks/taskid
server->client: 200 OK (state: doing)
server->client: POST /callbackurl
client->server: 200 OK (state:finished)

如果是基于长连接的协议, 比如 WebSocket, 应用层的TCP协议包, 这种方式用得最多

client->server: send PDU(request)
server-->client: reply PDU(ack)
server->client: send PDU (result)
server->client: reply PDU (ack)

请求/协商/确认

典型的 SIP 会话搭建流程

client->server: SIP Invite
server-->client: SIP 200 OK
server->client: SIP Ack

Pub/sub 发布/订阅

image.png
client1->broker: subscribe
broker->client1: ok
client2->broker: publish event
broker-->client2: ok
broker->client1: event
client1-->broker: ok

按数据传输格式分类

任何一个服务会使用一个协议栈, 从下到上采用相应的协议, 上层服务应用协议所使用的数据传输格式很多,但无非表示为文本和二进制格式

HTTP 是互联网的最主要的协议, 也是微服务中最流行的协议, 在它之上, 又有 RPC (Remote Procedure Call 远程过程调用) 和 REST (Representational state transfer 表述性状态转移) 两种风格.
前者多使用 HTTP + XML(SOAP), 后者多使用 HTTP + JSON(REST)

WebSocket + Protobuf 也是一个比较流行的做法, 发挥了长连接双向通讯的优势, 并且编解码速度比较快

文本格式

常用的有

  • 键值对
  • Json
  • Xml

二进制格式

常用的有

  • Proto buffer
  • BSON
  • Thrift
  • PDU

基于 TCP 的流式传输, 在数据包之间必需有易于识别的分隔符, 文本格式中用得最多的就是 \r\n, 或者类似于 xml/json 之类的 tag, 成对的 {} 或 []. 二进制格式中一般会在包头指定长度, 以便分割, 例如 SMTP 和 HTTP 协议,

$ curl https://tools.ietf.org/rfc/rfc768.txt -vvv

*   Trying 4.31.198.61...
* TCP_NODELAY set
* Connected to tools.ietf.org (4.31.198.61) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate: *.tools.ietf.org
* Server certificate: Starfield Secure Certificate Authority - G2
* Server certificate: Starfield Root Certificate Authority - G2
> GET /rfc/rfc768.txt HTTP/1.1
> Host: tools.ietf.org
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sun, 20 May 2018 02:29:29 GMT
< Server: Apache/2.2.22 (Debian)
< Last-Modified: Thu, 15 Oct 1992 21:56:01 GMT
< ETag: "108828d-1708-28e1893a75e40"
< Accept-Ranges: bytes
< Content-Length: 5896
< Vary: Accept-Encoding
< Strict-Transport-Security: max-age=3600
< X-Frame-Options: SAMEORIGIN
< X-Xss-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Clacks-Overhead: GNU Terry Pratchett
< Content-Type: text/plain; charset=UTF-8
<


RFC 768                                                        J. Postel
...

UDP 本身就是以数据包为单位的, 一个一个包收就好了, 不过一般在包中会承载序号信息, 以便对丢包或乱序进行处理, 之后我们来详细分析下文本协议的代表 HTTP, 以及二进制协议的代表 RTP

协议分析

协议标准理解

分析协议的第一步是阅读相应的协议标准 RFC(Request for Comments), 互联网的核心是 TCP/IP 协议簇

  • Internet Protocol (IP) -- RFC 791(https://tools.ietf.org/rfc/rfc791.txt)
    就OSI模型而言,IP是网络层协议。 它在应用程序之间提供数据报服务,支持TCP和UDP

IP packet 的包头为20个字节, IP 包头中包含 IP源地址, 目的地址, TTL 存活时间 (所经过路由器的跳数)

ip packet header

上层协议不必关心下层协议, 上层协议是包裹在下层协议的荷载 payload 中的,就象著名的俄罗斯套娃

但是要知道IP是3层协议, 2层所使用的数据链路协议如果是以太网, 以最大传输单元(MTU)为 1500 字节, 如果你的包的长度超过这个长度, 网络设备会将这个IP数据包分片传输, 划分成多个 IP packet

  • Transmission Control Protocol (TCP) - RFC 793(https://tools.ietf.org/rfc/rfc793.txt)

TCP是一种传输层协议。 它在应用程序之间提供可靠的虚拟电路连接; 即在数据传输开始之前建立连接。 数据发送时没有错误或重复,并且按照发送的顺序接收数据。 没有对数据施加任何界限; TCP将数据视为一个字节流。

TCP 包头中包含源端口, 目的端口, 序列号, 确认号, 类型标记, 窗口大小, 校验和等.
TCP是一个很复杂和强大的协议

tcp packet
  • User Datagram Protocol (UDP) - RFC 768(https://tools.ietf.org/rfc/rfc768.txt)

UDP也是一种传输层协议,是TCP的一种替代方案。 它在应用程序之间提供了不可靠的数据报连接。 数据通过链路传输; 没有端到端的连接。 该服务不提供任何担保。 数据可能丢失或重复,数据报可能无序到达。

UDP 包头包含源端口, 目的端口, 内容长度, 校验和, 它非常简单, 所以你可以发现基于它之上的众多协议会建立许多类似于TCP的序号, 标记等机制

udp packet

协议包分析

第二步就是抓包, 最常用的是 tcpdump 和 wireshark, 通常在服务器端用 tcpdump 抓完包后, 用 wireshark 打开抓包文件 *. pcap 进行详细分析

这里还推荐一个 python 工具 scapy , 它是一个功能强大的基于Python的交互式数据包操作程序和库。
可以从 https://github.com/secdev/scapy 下载, 或者直接用 pip 安装

pip install --pre scapy[complete]

它能够伪造或解码大量协议的数据包,在线上发送它们,捕获它们,使用pcap文件存储或读取它们,匹配请求和回复等等。 它旨在通过使用默认值来实现快速数据包原型设计。

简单用它来看来 IP , TCP, UDP 的包头

>>> ls(IP)
version    : BitField (4 bits)                   = (4)
ihl        : BitField (4 bits)                   = (None)
tos        : XByteField                          = (0)
len        : ShortField                          = (None)
id         : ShortField                          = (1)
flags      : FlagsField (3 bits)                 = ()
frag       : BitField (13 bits)                  = (0)
ttl        : ByteField                           = (64)
proto      : ByteEnumField                       = (0)
chksum     : XShortField                         = (None)
src        : SourceIPField                       = (None)
dst        : DestIPField                         = (None)
options    : PacketListField                     = ([])
>>> ls(TCP)
sport      : ShortEnumField                      = (20)
dport      : ShortEnumField                      = (80)
seq        : IntField                            = (0)
ack        : IntField                            = (0)
dataofs    : BitField (4 bits)                   = (None)
reserved   : BitField (3 bits)                   = (0)
flags      : FlagsField (9 bits)                 = ()
window     : ShortField                          = (8192)
chksum     : XShortField                         = (None)
urgptr     : ShortField                          = (0)
options    : TCPOptionsField                     = (b'')
>>> ls(UDP)
sport      : ShortEnumField                      = (53)
dport      : ShortEnumField                      = (53)
len        : ShortField                          = (None)
chksum     : XShortField                         = (None)

$ scapy

>>> pkt =IP(ttl=10, dst="192.168.1.1")/TCP(flags="SF")
>>> pkt.summary()
'IP / TCP 10.79.102.90:ftp_data > 192.168.1.1:http FS'
>>> pkt.show()
###[ IP ]###
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags=
  frag= 0
  ttl= 10
  proto= tcp
  chksum= None
  src= 10.79.102.90
  dst= 192.168.1.1
  \options\
###[ TCP ]###
     sport= ftp_data
     dport= http
     seq= 0
     ack= 0
     dataofs= None
     reserved= 0
     flags= FS
     window= 8192
     chksum= None
     urgptr= 0
     options= {}

再来看看 HTTP 包的格式

from scapy.layers import http

http_pkt = IP() / TCP() / http.HTTP()
http_pkt.show()
###[ IP ]### 
  version   = 4
  ihl       = None
  tos       = 0x0
  len       = None
  id        = 1
  flags     = 
  frag      = 0
  ttl       = 64
  proto     = tcp
  chksum    = None
  src       = 127.0.0.1
  dst       = 127.0.0.1
  \options   \
###[ TCP ]### 
     sport     = http
     dport     = http
     seq       = 0
     ack       = 0
     dataofs   = None
     reserved  = 0
     flags     = S
     window    = 8192
     chksum    = None
     urgptr    = 0
     options   = []
###[ HTTP 1 ]### 

http.HTTPRequest().show()
###[ HTTP Request ]### 
  Method    = 'GET'
  Path      = '/'
  Http_Version= 'HTTP/1.1'
  A_IM      = None
  Accept    = None
  Accept_Charset= None
  Accept_Datetime= None
  Accept_Encoding= None
  Accept_Language= None
  Access_Control_Request_Headers= None
  Access_Control_Request_Method= None
  Authorization= None
  Cache_Control= None
  Connection= None
  Content_Length= None
  Content_MD5= None
  Content_Type= None
  Cookie    = None
  DNT       = None
  Date      = None
  Expect    = None
  Forwarded = None
  From      = None
  Front_End_Https= None
  HTTP2_Settings= None
  Host      = None
  If_Match  = None
  If_Modified_Since= None
  If_None_Match= None
  If_Range  = None
  If_Unmodified_Since= None
  Keep_Alive= None
  Max_Forwards= None
  Origin    = None
  Permanent = None
  Pragma    = None
  Proxy_Authorization= None
  Proxy_Connection= None
  Range     = None
  Referer   = None
  Save_Data = None
  TE        = None
  Upgrade   = None
  Upgrade_Insecure_Requests= None
  Upgrade_Insecure_Requests= None
  User_Agent= None
  Via       = None
  Warning   = None
  X_ATT_DeviceId= None
  X_Correlation_ID= None
  X_Csrf_Token= None
  X_Forwarded_For= None
  X_Forwarded_Host= None
  X_Forwarded_Proto= None
  X_Http_Method_Override= None
  X_Request_ID= None
  X_Requested_With= None
  X_UIDH    = None
  X_Wap_Profile= None
  Unknown_Headers= None

http.HTTPResponse().show()
###[ HTTP Response ]### 
  Http_Version= 'HTTP/1.1'
  Status_Code= '200'
  Reason_Phrase= 'OK'
  Accept_Patch= None
  Accept_Ranges= None
  Access_Control_Allow_Credentials= None
  Access_Control_Allow_Headers= None
  Access_Control_Allow_Methods= None
  Access_Control_Allow_Origin= None
  Access_Control_Expose_Headers= None
  Access_Control_Max_Age= None
  Age       = None
  Allow     = None
  Alt_Svc   = None
  Cache_Control= None
  Connection= None
  Content_Disposition= None
  Content_Encoding= None
  Content_Language= None
  Content_Length= None
  Content_Location= None
  Content_MD5= None
  Content_Range= None
  Content_Security_Policy= None
  Content_Type= None
  Date      = None
  Delta_Base= None
  ETag      = None
  Expires   = None
  IM        = None
  Keep_Alive= None
  Last_Modified= None
  Link      = None
  Location  = None
  P3P       = None
  Permanent = None
  Permanent = None
  Pragma    = None
  Proxy_Authenticate= None
  Public_Key_Pins= None
  Refresh   = None
  Retry_After= None
  Server    = None
  Set_Cookie= None
  Status    = None
  Strict_Transport_Security= None
  Timing_Allow_Origin= None
  Tk        = None
  Trailer   = None
  Transfer_Encoding= None
  Upgrade   = None
  Vary      = None
  Via       = None
  WWW_Authenticate= None
  Warning   = None
  X_Content_Duration= None
  X_Content_Security_Policy= None
  X_Content_Type_Options= None
  X_Correlation_ID= None
  X_Frame_Options= None
  X_Powered_By= None
  X_Request_ID= None
  X_UA_Compatible= None
  X_WebKit_CSP= None
  X_XSS_Protection= None
  Unknown_Headers= None

参考资料

  • Scapy 官网:https://github.com/secdev/scapy

你可能感兴趣的:(微服务之协议概述)