微服务现在是一个很火的话题,好像不管项目的大小,适用范围都在往微服务上去靠。这也使得现在如果不会微服务出去都没法和别人聊了。
本文就来谈一谈在微服务中为什么要注册中心?
为什么需要注册中心?
在微服务中首先需要面对的问题就是不同的服务之间如何进行通信呢?在单体服务中如果不同的服务之间需要通信,一般就是服务将接口暴露,然后其他的服务通过http进行请求,那么很明显在微服务中也可以这样。
但这里存在的问题在于单体服务中我们需要请求的目标是我们在请求的url中直接写死的,因为服务不多可以这样。而微服务中需要考虑的是存在大量服务时手动维护服务列表是否合适?如果服务横向扩展时如何通知其他的服务?服务宕机后,如何及时下线等等问题。
几种注册中心的区别
注册中心在分布式应用中是经常用到的,也是必不可少的,那注册中心,又分为以下几种:eureka(springcloud推荐的),zookeeper(与dubbo无缝结合),consul(HashiCorp开源),nacos(阿里开源的);
从CAP理论看:
CAP: CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)
Zk
用过zk做注册中心的小伙伴都知道,zk集群下一旦leader节点gg了,在短时间内服务都不可通讯,因为它们在一定时间内follower进行选举来推出新的leader,因为在这段时间内,所有的服务通信将受到影响,而且leader选取时间比较长,需要花费30~120s时间,因此:可以理解为 ZK是实现的CP,也就是将失去A(可用性)
Eureka
eureka集群下每个节点之间(P2P)都会定时发送心跳,定时同步数据,并没有master/slave之分,因此每个注册到eureka下的实例都会定时同步ip等,服务之间的调用也是根据eureka拿到的缓存服务数据进行调用。但是,如果一台eureka服务g掉了,其他eureka在一定时间内未感知到这台eureka服务g了,各个服务之间还是可以正常调用。 Eureka的集群中,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。 除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
Eureka不再从注册表中移除因为长时间没有收到心跳而过期的服务; Eureka仍然能够接受新服务注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用); 当网络稳定时,当前实例新注册的信息会被同步到其它节点中; 因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使得整个注册服务瘫痪。
Consul
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X)。
Consul 内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等),使用起来也较为简单。
Consul 遵循CAP原理中的CP原则,保证了强一致性和分区容错性,且使用的是Raft算法,比zookeeper使用的Paxos算法更加简单。虽然保证了强一致性,但是可用性就相应下降了,例如服务注册的时间会稍长一些,因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 ;在leader挂掉了之后,重新选举出leader之前会导致Consul 服务不可用。
Consul强一致性(C)带来的是:
服务注册相比Eureka会稍慢一些。因为Consul的raft协议要求必须过半数的节点都写入成功才认为注册成功 Leader挂掉时,重新选举期间整个consul不可用。保证了强一致性但牺牲了可用性。 Eureka保证高可用(A)和最终一致性:
服务注册相对要快,因为不需要等注册信息replicate到其他节点,也不保证注册信息是否replicate成功 当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。 其他方面,eureka就是个servlet程序,跑在servlet容器中; Consul则是go编写而成。
Nacos(性能最好)
他同时支持AP和CP模式,他根据服务注册选择临时和永久来决定走AP模式还是CP模式,
他这里支持CP模式对于我的理解来说,应该是为了配置中心集群,因为nacos可以同时作为注册中心和配置中心,
因为他的配置中心信息是保存在nacos里面的,假如因为nacos其中一台挂掉后,还没有同步配置信息,
就可能发生配置不一致的情况., 配置中心的配置变更是服务端有监听器,配置中心发生配置变化,
然后服务端会监听到配置发生变化,从而做出改变。
Nacos除了服务的注册发现之外,还支持动态配置服务。动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
一句话概括就是Nacos = Spring Cloud注册中心 + Spring Cloud配置中心。
注册中心的功能
注册中心的存在是为了更好更方便的管理应用中的每一个服务,是各个分布式节点之间的纽带。注册中心主要提供以下核心功能:
- 服务注册与发现:动态的增减服务节点,服务节点增减后动态的通知服务消费者,而不需要由消费者来更新配置。
- 服务配置:动态修改服务配置,并将其推送到服务提供者和服务消费者而不需要重启服务。
- 健康检查和服务摘除:主动的检查服务健康情况,对于宕机的服务将其摘除服务列表。
注册中心的实现
注册中心的主要功能就是保存服务的具体信息,然后由服务消费者读取这些信息。从整体的流程上来说大概就是这样:
除了将服务注册到注册中心,实际还存在服务的反注册
目前对注册中心的实现分为两种模式,分别为客户端(应用内注册)和服务端(应用外注册)。
客户端注册中心
目前客户端实现的注册中心比较有代表性的就是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简单来说就是我们需要在服务的可用性和服务间的一致性进行一个抉择。是牺牲可用性来保证强一致性还是保证可用性牺牲强一致性呢?
CP类型注册中心
Zookeeper是一个典型的CP类型的高可用组件。zookeeper实现来paxos算法,zookeeper集群有一个节点作为leader,如果leader节点挂了zk会通过ZAB协议来进行leader选举,但是在选举的过程中zk是不能对外提供服务的。
而且当因为网络分区导致zk集群出现脑裂问题时,由于ZAB协议需要半数以上的节点参与,所以可能会有部分或者全部的zk节点无法对外提供服务。但是zk可以保证所有可用节点都数据都是一致,即使节点崩溃,在恢复后也会和其他节点保持一致,这就是zk牺牲了可用性而保证的强一致性。
AP类型注册中心
而类似与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等。在网络正常时心跳检测也恢复正常。
小结
注册中心是微服务架构中一个很关键的组件,其保证来各个服务之间正常调用,以及服务的横向扩容等功能。在进行注册中心选型时需要考虑业务场景。