微服务现在是一个很火的话题,好像不管项目的大小,适用范围都在往微服务上去靠。这也使得现在如果不会微服务出去都没法和别人聊了。
仅从我自己的工作经历来看,尽管我们的项目也是微服务化了的。但是说实话在业务开发过程中并没有体会到微服务的开发和单体项目开发中存在很大的区别(仅业务开发这一块来说)。
微服务的学习单独的写几个demo并没有啥用,现在的框架封装程度都很高,就算是代码跑起来来依旧是云里雾里。微服务的学习更多的是搞清楚微服务中每一个组件到底是什么?为什么有这些组件?没有会怎么样?功能大概是怎么实现的?
搞清楚来这些基本上就可以说对微服务有个大体的掌握了。
在微服务中首先需要面对的问题就是不同的服务之间如何进行通信呢?在单体服务中如果不同的服务之间需要通信,一般就是服务将接口暴露,然后其他的服务通过http进行请求,那么很明显在微服务中也可以这样。
但这里存在的问题在于单体服务中我们需要请求的目标是我们在请求的url中直接写死的,因为服务不多可以这样。而微服务中需要考虑的是存在大量服务时手动维护服务列表是否合适?如果服务横向扩展时如何通知其他的服务?服务宕机后,如何及时下线等等问题。
注册中心的存在是为了更好更方便的管理应用中的每一个服务,是各个分布式节点之间的纽带。注册中心主要提供以下核心功能:
注册中心的主要功能就是保存服务的具体信息,然后由服务消费者读取这些信息。从整体的流程上来说大概就是这样:
除了将服务注册到注册中心,实际还存在服务的反注册。
目前对注册中心的实现分为两种模式,分别为客户端(应用内注册)和服务端(应用外注册)。
目前客户端实现的注册中心比较有代表性的就是eureka,虽然eureka2.0版本在此前宣布闭源,但目前好像没有太大的影响。后续的话可以考虑阿里开源的nacos。
eureka是一个基于JAVA语言实现的费用与服务发现与注册的组件,包含服务端和客户端两部分。
服务端主要用来存储服务信息,定时进行服务检查。而客户端用于完成向服务端注册服务信息以及从服务端拉取服务信息等。
上图是eureka官给出的eureka的架构图。
从图中可以很明显的看到eureka的服务端也是作为一个服务部署,而客户端则是通过sdk的方式接入应用。客户端向服务端进行服务注册以及更新服务等,同时客户端从服务端获取服务信息,不同服务之间根据获得的服务信息进行远程调用。
为了满足服务的高可用,通过移动多个eureka server服务,不同eureka server相互注册实现服务的高可用。
eureka是由注册中心提供服务端和客户端的SDK,业务端通过引入注册中心提供的SDK,来实现服务的注册和发现。而服务端的注册中心则是通过应用外的组件来实现服务注册功能的。目前用的比较多的服务端注册中心组件如zookeeper、consul等。这里以zookeeper为例。
Zookeeper 是 Apache Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,是目前Dubbo官方主推搭配的注册中心。
上图是dubbo官网提供的zookeeper作为注册中心的基本原理。
以dubbo为根目录,创建一服务接口全限定名的目录,在其下创建存放服务提供者接口信息的providers目录、存放服务消费者接口信息的consumers目录、存放服务配置信息的configurators目录等。
服务提供者启动的时候在providers下创建一条服务信息,该信息可以被consumer获取。通过zk提供的watcher机制注册一个watcher在服务提供者的providers目录下,这样当服务出现扩容或者宕机的时候就可以立即被consumer消费。
在微服务中,注册中心作为一个存储所有服务中心的地方必然不可能是单机的。因为如果注册中心无法使用,那么服务提供者就无法对外暴露自己的服务,消费者也无法获取自己需要调用的服务的具体地址,整个应用将会崩溃。首先需要保证的就是注册中心的高可用。
说到高可用,CAP理论是绕不过去的,CAP简单来说就是我们需要在服务的可用性和服务间的一致性进行一个抉择。是牺牲可用性来保证强一致性还是保证可用性牺牲强一致性呢?
Zookeeper是一个典型的CP类型的高可用组件。zookeeper实现来paxos算法,zookeeper集群有一个节点作为leader,如果leader节点挂了zk会通过ZAB协议来进行leader选举,但是在选举的过程中zk是不能对外提供服务的。
而且当因为网络分区导致zk集群出现脑裂问题时,由于ZAB协议需要半数以上的节点参与,所以可能会有部分或者全部的zk节点无法对外提供服务。但是zk可以保证所有可用节点都数据都是一致,即使节点崩溃,在恢复后也会和其他节点保持一致,这就是zk牺牲了可用性而保证的强一致性。
而类似与eureka的注册中心则是AP类型的。eureka实现高可用是通过启动多个eureka server服务,每一个eureka server即是提供者也是消费者,每个eureka server将自己作为服务注册给其他的eureka server,这样每个eureka server都是其他server 的replica。eureka这种模式中每个节点都是平等的,不存在leader、flower。
当集群中某一个server节点宕机,请求可以直接转移到其他节点。即使发生了网络分区,尽管不同分区之间无法进行通信,但是在每一个分区内的节点是通信并且可以正常对外提供服务,这样就保证了在server节点宕机的情况下的注册中心的可用性。
而问题在于由于不同分区无法通信,就导致可能一部分节点的数据和另一部分有差别,这就是牺牲了强一致性来换取系统可用性。
首先我们应该明确的是其实注册中心的存在与否应该是不能直接影响服务的调用的。服务之间的调用时通过http或者rpc等协议直接调用的,注册中心只是提供一个调用地址。服务是否可以正常使用不能直接靠注册中心节点信息来决定。
即使注册中心出现问题,只要服务本身没有宕机,理论上来说还是应该正常对外提供服务。所以很明显对于我们来说AP类型的注册中心是要优于CP类型的注册中心的。
当然在实际实现过程中很多框架都会尽可能的去修补这些问题,比如eureka会定时的同步各个节点的数据,尽可能的做到数据一致性。dubbo的zk注册中心实现过程中也会本地缓存服务信息并不是完全通过zk信息来决定是否剔除服务等。所以实际在选择时还是需要根据自身实际以及是否还有其他需求来确定。
在微服务中,每一个服务都是一个独立的整体。各个服务之间并不向单体应用中的不同模块大都要求是同一语言实现。现如今各大公司开发语言是多样化的,不同的业务线开发的语言可能都不尽相同,混合语言是我们必须要面对的一个问题,所以出现了多语言之间服务调用的问题。
对于像eureka这样的应用内的注册中心。由于服务的注册与发现都是依赖于SDK,所以如果要使用eureak那需要对应的语言实现的SDK,目前有不少语言都有提供如Python、node.js。目前springclould的Sidecar组件也可以做到将其他语言纳入到springclould体系中来。
对于应用外的注册中心,一般会由这些中间件提供客户端操作实现。然后由语言本身来实现服务注册和治理等功能。目前很多语言实际上也有相关的开源工程。
前面已经说过了保证CP类型的强一致性注册中心以及AP类型保证可用性的注册中心。而对于一个注册中心来说最重要的就是管理其中的节点信息。服务启动的时候需要将服务信息记录,当服务出现问题的时候需要将服务摘除。
但是问题在于如何实现这个摘除的操作,目前注册中心的实现都是通过心跳检测来判断服务的健康状态。正常情况下如果服务宕机了,心跳检测无法和服务通信判断该节点无法正常服务,将服务从注册中心摘除,这个过程是没有问题的。但是如果发生网络问题,比如网络抖动或者网络分区,这时候心跳检测肯定是无法正常通信的,但如果就因此直接将服务摘除那肯定是有问题的,因为节点实际上是能够正常提供服务的。所以需要有机制来确保这个过程不会粗暴的将节点从注册中心摘除。
目前很多的框架实现都会在本地缓存微服务的节点信息。实际上使用这些节点的是各个服务,服务是否能正常使用,这些节点才是最有发言权的。
客户端缓存了节点信息,当服务端判定服务出现问题后发出更新请求时,客户端并不立刻删除,可以先做一个标识。后续的业务请求过来后,仍旧判定该服务时可用的,只有当发出的请求无法收到正常回应时才将该服务摘除。由客户端来决定节点是否可用,不过这需要容错机制来支持。
在网络频繁抖动的情况下,注册中心的节点信息会快速变化,也会给客户端发送大量的信息,当服务较多的时候可能会有大量的带宽被占用导致正常的请求都无法处理。
所以需要一种动态的调整注册中心心跳检测频率的机制,当检测到网络抖动频繁时,根据网络情况调整心跳检测的频率,比如调整为正常情况下的1/2,10/1等。在网络正常时心跳检测也恢复正常。
注册中心是微服务架构中一个很关键的组件,其保证来各个服务之间正常调用,以及服务的横向扩容等功能。在进行注册中心选型时需要考虑业务场景。
作者:不能摸鱼啦
链接:
https://juejin.im/post/5ebe0ae36fb9a0432a3c43b6