我终于搞懂了微服务,太不容易了...( 三 )


③存储结构
注册中心存储相关信息一般采取目录化的层次结构,一般分为服务-接口-节点信息 。
同时注册中心一般还会进行分组,分组的概念很广,可以是根据机房划分也可以根据环境划分 。
节点信息主要会包括节点的地址(ip 和端口号),还有一些节点的其他信息,比如请求失败的重试次数、超时时间的设置等等 。
当然很多时候,其实可能会把接口这一层给去掉,因为考虑到接口数量很多的情况下,过多的节点会造成很多问题,比如之前说的网络风暴 。
④服务健康监测
服务存活状态监测也是注册中心的一个必要功能 。在 Zookeeper 中,每个客户端都会与服务端保持一个长连接,并生成一个 Session 。
在 Session 过期周期内,通过客户端定时向服务端发送心跳包来检测链路是否正常,服务端则重置下次 Session 的过期时间 。
如果 Session 过期周期内都没有检测到客户端的心跳包,那么就会认为它已经不可用了,将其从节点列表中移除 。
⑤状态变更通知
在注册中心具备服务健康检测能力后,还需要将状态变更通知到客户端 。在 Zookeeper 中,可以通过监听器 Watcher 的 Process 方法来获取服务变更 。
服务的远程通信
在上面,服务消费者已经正确引用了服务,并发现了该服务的地址,那么如何向这个地址发起请求呢?
要解决服务间的远程通信问题,我们需要考虑一些问题:

  • 网络 I/O 的处理
  • 传输协议
  • 序列化方式
①网络 I/O 的处理
简单来说,就是客户端是怎么处理请求?服务端又是怎么处理请求的?
先从客户端来说,我们创建连接的时机可以是从注册中心获取到节点信息的时候,但更多时候,我们会选择在第一次请求发起调用的时候去创建连接 。此外,我们往往会为该节点维护一个连接池,进行连接复用 。
如果是异步的情况下,我们还需要为每一个请求编号,并维护一个请求池,从而在响应返回时找到对应的请求 。当然这并不是必须的,很多框架会帮我们干好这些事情,比如 rxNetty 。
从服务端来说,处理请求的方式就可以追溯到 Unix 的 5 种 IO 模型了 。我们可以直接使用 Netty、MINA 等网络框架来处理服务端请求,或者如果你有十分的兴趣,可以自己实现一个通信框架 。
②传输协议
最常见的当然是直接使用 HTTP 协议,使用双方无需关注和了解协议内容,方便直接,但自然性能上会有所折损 。
还有就是目前比较火热的 HTTP2 协议,拥有二进制数据、头部压缩、多路复用等许多优良特性 。
但从自身的实践上看,HTTP2 要走到生产仍有一段距离,一个最简单的例子,升级到 HTTP2 后所有的 header names 都变成小写,同时不是 case-insenstive 了,这时候就会有兼容性问题 。
当然如果追求更高效与可控的传输,可以定制私有协议并基于 TCP 进行传输 。私有协议的定制需要通信双方都了解其特性,设计上还需要注意预留好扩展字段,以及处理好粘包分包等问题 。
③序列化方式
在网络传输的前后,往往都需要在发送端进行编码,在服务端进行解码,这样主要是为了在网络传输时候减少数据传输量 。
常用的序列化方式包括文本类的,例如 XML/JSON,还有二进制类型的,例如 Protobuf/Thrift 等 。
在选择序列化的考虑上:
一是性能,Protobuf 的压缩大小和压缩速度都会比 JSON 快很多,性能也更好 。
二是兼容性上,相对来说,JSON 的前后兼容性会强一些,可以用于接口经常变化的场景 。
在此还是需要强调,使用每一种序列化都需要了解过其特性,并在接口变更的时候拿捏好边界 。
例如 jackson 的 FAIL_ON_UNKNOW_PROPERTIES 属性、kryo 的 CompatibleFieldSerializer、jdk 序列化会严格比较 serialVersionUID 等等 。
微服务的稳定性
当一个单体应用改造成多个微服务之后,在请求调用过程中往往会出现更多的问题,通信过程中的每一个环节都可能出现问题 。
而在出现问题之后,如果不加处理,还会出现链式反应导致服务雪崩 。服务治理功能就是用来处理此类问题的 。
我们将从微服务的三个角色:注册中心、服务消费者以及服务提供者一一说起 。
注册中心如何保障稳定性
注册中心主要是负责节点状态的维护,以及相应的变更探测与通知操作 。
一方面,注册中心自身的稳定性是十分重要的 。另一方面,我们也不能完全依赖注册中心,需要时常进行类似注册中心完全宕机后微服务如何正常运行的故障演练 。


推荐阅读