微服务实战(四):服务发现的可行方案以及实践案例
˙zhun36
9年前
<p>这是关于使用微服务架构创建应用系列的第四篇文章。<a href="http://www.open-open.com/lib/view/open1462432867913.html">第一篇</a>介绍了微服务架构的模式,讨论了使用微服务架构的优缺点。<a href="http://www.open-open.com/lib/view/open1436089902667.html">第二</a>和<a href="http://www.open-open.com/lib/view/open1462433241328.html">第三篇</a>描述了微服务架构内部的通讯机制。这篇文章中,我们将会探讨服务发现相关问题。</p> <h2>为什么要使用服务发现?</h2> <p>设想一下,我们正在写代码使用了提供REST API或者Thrift API的服务,为了完成一次服务请求,代码需要知道服务实例的网络位置(IP地址和端口)。传统应用都运行在物理硬件上,服务实例的网络位置都是相对固定的。例如,代码可以从一个经常变更的配置文件中读取网络位置。<br> <br> 而对于一个现代的,基于云微服务的应用来说,这却是一个很麻烦的问题。其架构如图所示:</p> <p><a href="https://simg.open-open.com/show/ced5f67e220f00e32d95d9384e8df146.png" rel="lightbox"><img alt="微服务实战(四):服务发现的可行方案以及实践案例" src="https://simg.open-open.com/show/f505bc1de662983e3340774bcc6b462e.png" width="1005" height="1024"></a></p> <p><br> 服务实例的网络位置都是动态分配的,而且因为扩展、失效和升级等需求,服务实例会经常动态改变,因此,客户端代码需要使用一种更加复杂的服务发现机制。<br> <br> 目前有两大类服务发现模式:<a href="/misc/goto?guid=4959638282196896803">客户端发现</a>和<a href="/misc/goto?guid=4959638282112092760">服务端发现</a>。<br> <br> 我们先来来讨论一下客户端发现。</p> <h2>客户端发现模式</h2> <p>当使用<a href="/misc/goto?guid=4959638282196896803">客户端发现模式</a>时,客户端负责决定相应服务实例的网络位置,并且对请求实现负载均衡。客户端从一个服务注册服务中查询,其中是所有可用服务实例的库。客户端使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。<br> <br> 下图显示的是这种模式的架构图:</p> <p><a href="https://simg.open-open.com/show/158f6c8bdd2e38b2184b592e665e5b2e.png" rel="lightbox"><img alt="微服务实战(四):服务发现的可行方案以及实践案例" src="https://simg.open-open.com/show/f07540ed0f930ef01d32a904ccd57986.png" width="1024" height="967"></a></p> <p><br> 服务实例的网络位置是在启动时注册到服务注册表中,并且在服务终止时从注册表中删除。服务实例注册信息一般是使用心跳机制来定期刷新的。<br> <br> <a href="/misc/goto?guid=4958977502680516177">Netflix OSS</a>提供了一种非常棒的客户端发现模式。<a href="/misc/goto?guid=4958523703461021995">Netflix Eureka</a>是一个服务注册表,为服务实例注册管理和查询可用实例提供了REST API接口。<a href="/misc/goto?guid=4958537445571683620">Netflix Ribbon</a>是一种IPC客户端,与Eureka合同工作实现对请求的负载均衡。我们会在后面详细讨论Eureka。<br> <br> 客户端发现模式也是优缺点分明。这种模式相对比较直接,而且除了服务注册表,没有其它改变的因素。除此之外,因为客户端知道可用服务注册表信息,因此客户端可以通过使用哈希一致性(hashing consistently)变得更加聪明,更加有效的负载均衡。<br> <br> 而这种模式一个<strong>最大的缺点</strong>是需要针对不同的编程语言注册不同的服务,在客户端需要为每种语言开发不同的服务发现逻辑。<br> <br> 我们分析过客户端发现后,再看看服务端发现。</p> <h2>服务端发现模式</h2> <p>另外一种服务发现的模式是服务端发现模式(server-side discovery pattern),下图展现了这种模式的架构图:</p> <p><a href="https://simg.open-open.com/show/7d6e5510f4175cd04580a84a0218386e.png" rel="lightbox"><img alt="微服务实战(四):服务发现的可行方案以及实践案例" src="https://simg.open-open.com/show/e2d10c6c9f78352335df3dd74540f448.png" width="1024" height="631"></a></p> <p><br> 客户端通过负载均衡器向某个服务提出请求,负载均衡器向服务注册表发出请求,将每个请求转发往可用的服务实例。跟客户端发现一样,服务实例在服务注册表中注册或者注销。<br> <br> AWS Elastic Load Balancer(ELB)是一种服务端发现路由的例子,ELB一般用于均衡从网络来的访问流量,也可以使用ELB来均衡VPC内部的流量。客户端使用DNS,通过ELB发出请求(HTTP或者TCP)。ELB负载均衡器负责在注册的EC2实例或者ECS容器之间均衡负载,并不存在一个分离的服务注册表,而EC2实例和ECS实例也向ELB注册。<br> <br> HTTP服务和类似NGINX和<a href="/misc/goto?guid=4958972392892539317">NGINX Plus</a>的负载均衡器都可以作为服务端发现均衡器。例如,<a href="/misc/goto?guid=4959629379934561146">这篇博文</a>就描述如何使用<a href="/misc/goto?guid=4958967317608731033">Consul Template</a>来动态配置NGINX反向代理。Consul Template是周期性从存放在<a href="/misc/goto?guid=4958967317511997269">Consul Template注册表</a>中配置数据重建配置文件的工具。当文件发生变化时,会运行一个命令。在如上博客中,Consul Template产生了一个nginx.conf文件,用于配置反向代理,然后运行一个命令,告诉NGINX重新调入配置文件。更复杂的例子可以用HTTP API或者DNS动态重新配置NGINX Plus。<br> <br> 某些部署环境,例如<a href="/misc/goto?guid=4959672348047837050">Kubernetes</a>和<a href="/misc/goto?guid=4959672348151133194">Marathon</a>在集群每个节点上运行一个代理,此代理作为服务端发现负载均衡器。为了向服务发出请求,客户端使用主机IP地址和分配的端口通过代理将请求路由出去。代理将次请求透明的转发到集群中可用的服务实例。<br> <br> 服务端发现模式也有优缺点。最大的优点是客户端无需关注发现的细节,客户端只需要简单的向负载均衡器发送请求,实际上减少了编程语言框架需要完成的发现逻辑。而且,如上说所,某些部署环境免费提供以上功能。<br> <br> 这种模式也有缺陷,除非部署环境提供负载均衡器,否则负载均衡器是另外一个需要配置管理的高可用系统功能。</p> <h2>服务注册表</h2> <p><a href="/misc/goto?guid=4959638282276454834">服务注册表</a>是服务发现很重要的部分,它是包含服务实例网络地址的数据库。服务注册表需要高可用而且随时更新。客户端可以缓存从服务注册表获得的网络地址。然而,这些信息最终会变得过时,客户端也无法发现服务实例。因此,服务注册表由若干使用复制协议保持同步的服务器构成。<br> <br> 如前所述,<a href="/misc/goto?guid=4958523703461021995">Netflix Eureka</a>是一个服务注册表很好地例子,提供了REST API注册和请求服务实例。 服务实例使用POST请求注册网络地址,每30秒必须使用PUT方法更新注册表,使用HTTP DELETE请求或者实例超时来注销。可以想见,客户端可以使用HTTP GET请求接受注册服务实例信息。<br> <br> Netflix通过在每个AWS EC2域运行一个或者多个Eureka服务<a href="/misc/goto?guid=4959672348286002848">实现高可用性</a>,每个Eureka服务器都运行在拥有<a href="/misc/goto?guid=4959672348374908565">弹性IP地址</a>的EC2实例上。DNS TEXT记录用于存储Eureka集群配置,其中存放从可用域到一系列Eureka服务器网络地址的列表。当Eureka服务启动时,向DNS请求接受Eureka集群配置,确认同伴位置,给自己分配一个未被使用的弹性IP地址。<br> <br> Eureka客户端—服务和服务客户端—向DNS请求发现Eureka服务的网络地址,客户端首选使用同一域内的服务。然而,如果没有可用服务,客户端会使用另外一个可用域的Eureka服务。<br> <br> 另外一些服务注册表例子包括:</p> <ul> <li><a href="/misc/goto?guid=4958826104637417261">etcd</a> – 是一个高可用,分布式的,一致性的,键值表,用于共享配置和服务发现。两个著名案例包括Kubernetes和Cloud Foundry。</li> <li><a href="/misc/goto?guid=4958967317511997269">consul</a> – 是一个用于发现和配置的服务。提供了一个API允许客户端注册和发现服务。Consul可以用于健康检查来判断服务可用性。</li> <li><a href="/misc/goto?guid=4958192107847702038">Apache ZooKeeper</a> – 是一个广泛使用,为分布式应用提供高性能整合的服务。Apache ZooKeeper最初是Hadoop的子项目,现在已经变成顶级项目。</li> </ul> <p><br> 另外,前面强调过,某些系统,例如Kubernetes、Marathon和AWS并没有独立的服务注册表,对他们来说,服务注册表只是一个内置的功能。<br> <br> 现在我们来看看服务注册表的概念,看看服务实例是如何在注册表中注册的。</p> <h2>服务注册选项</h2> <p>如前所述,服务实例必须向注册表中注册和注销,如何注册和注销也有一些不同的方式。一种方式是服务实例自己注册,也叫自注册模式(self-registration pattern);另外一种方式是为其它系统提供服务实例管理的,也叫第三方注册模式(third party registration pattern)。我们来看看自注册模式。</p> <h3>自注册方式</h3> <p>当使用自注册模式时,服务实例负责在服务注册表中注册和注销。另外,如果需要的话,一个服务实例也要发送心跳来保证注册信息不会过时。下图描述了这种架构:</p> <p><a href="https://simg.open-open.com/show/88594b4caa2dd35f7fe1a06b9ada513a.png" rel="lightbox"><img alt="微服务实战(四):服务发现的可行方案以及实践案例" src="https://simg.open-open.com/show/dc1c196281d8925397dd8cb4f455b817.png" width="1024" height="893"></a></p> <p><br> 一个很好地例子是 Netflix OSS Eureka client。Eureka客户端负责处理服务实例的注册和注销。Spring Cloud project,实现了多种模式,包括服务发现,使得向Eureka服务实例自动注册时更容易。可以用@EnableEurekaClient注释Java配置类。<br> <br> 自注册模式也有优缺点。一个优点是,相对简单,不需要其他系统功能。而一个主要缺点则是,把服务实例跟服务注册表联系起来。必须在每种编程语言和框架内部实现注册代码。<br> <br> 另外一个方法,不需要连接服务和注册表,则是第三方注册模式。</p> <h2>第三方注册模式</h2> <p>当使用<a href="/misc/goto?guid=4959672348554114282">第三方注册模式</a>时,服务实例并不负责向服务注册表注册,而是由另外一个系统模块,叫做服务管理器,负责注册。服务管理器通过查询部署环境或订阅事件来跟踪运行服务的改变。当管理器发现一个新可用服务,会向注册表注册此服务。服务管理器也负责注销终止的服务实例。下图是这种模式的架构图。</p> <p><a href="https://simg.open-open.com/show/d091abe0e2441c523798d34a6fae11bb.png" rel="lightbox"><img alt="微服务实战(四):服务发现的可行方案以及实践案例" src="https://simg.open-open.com/show/d091abe0e2441c523798d34a6fae11bb.png" width="1024" height="593"></a></p> <p><br> 一个服务管理器的例子是开源项目<a href="/misc/goto?guid=4958967317393867577">Registrator</a>,负责自动注册和注销被部署为Docker容器的服务实例。Reistrator支持多种服务管理器,包括etcd和Consul。<br> <br> 另外一个服务管理器例子是<a href="/misc/goto?guid=4959672348682523752">NetflixOSS Prana</a>,主要面向非JVM语言开发的服务,也称为附带应用(sidecar application),Prana使用Netflix Eureka注册和注销服务实例。<br> <br> 服务管理器是部署环境内置的模块。有自动扩充组创建的EC2实例可以自向ELB自动注册,Kubernetes服务自动注册并且对发现服务可用。<br> <br> 第三方注册模式也是优缺点都有。主要的优点是服务跟服务注册表是分离的,不需要为每种编程语言和架构完成服务注册逻辑,替代的,服务实例是通过一个集中化管理的服务进行管理的。<br> <br> 一个缺点是,除非这种服务被内置于部署环境中,否则也需要配置管理一个高可用的系统。</p> <h2>总结</h2> <p>在一个微服务应用中,服务实例运行环境是动态变化的。实例网络地址也是动态变化的,因此,客户端为了访问服务必须使用服务发现机制。<br> <br> 服务发现关键部分是<a href="/misc/goto?guid=4959638282276454834">服务注册表</a>,也就是可用服务实例的数据库。服务注册表提供一种注册管理API和请求API。服务实例使用注册管理API来实现注册和注销。<br> <br> 请求API用于发现可用服务实例,相对应的,有两种主要服务发现模式:<a href="/misc/goto?guid=4959638282196896803">客户端发现</a>和<a href="/misc/goto?guid=4959638282112092760">服务端发现</a>。<br> <br> 在使用客户端发现的系统中,客户端向服务注册表发起请求,选择可用实例,然后发出服务请求<br> <br> 而在使用服务端发现的系统中,客户端通过路由转发请求,路由器向服务注册表发出请求,转发此请求到某个可用实例。<br> <br> 服务实例注册和注销主要有两类方式。一种是服务实例自动注册到服务注册表中,也就是自注册模式;另外一种则是某个系统模块负责处理注册和注销,也就是第三方注册模式。<br> <br> 在某些部署环境中,需要配置自己的服务发现架构,例如:<a href="/misc/goto?guid=4958523703461021995">Netflix Eureka</a>、<a href="/misc/goto?guid=4958826104637417261">etcd</a>或者<a href="/misc/goto?guid=4958192107847702038">Apache ZooKeeper</a>。而在另外一些部署环境中,则自带了这种功能,例如Kubernetes和Marathon 负责处理服务实例的注册和注销。他们也在每个集群节点上运行代理,来实现服务端发现路由器的功能。<br> <br> HTTP反向代理和负载据衡器(例如NGINX)可以用于服务发现负载均衡器。服务注册表可以将路由信息推送到NGINX,激活一个实时配置更新;例如,可以使用 Consul Template。NGINX Plus 支持<a href="/misc/goto?guid=4959642209926393978">额外的动态重新配置机制</a>,可以使用DNS,将服务实例信息从注册表中拉下来,并且提供远程配置的API。<br> <br> 在未来的博客中,我们还将深入探讨微服务其它特点。可以注册NGINX邮件列表来获得最新产品更新提示。<br> <br> 此篇其它博客译文参见如下地址:</p> <ul> <li><a href="http://www.open-open.com/lib/view/open1462432867913.html">微服务架构的优势与不足</a></li> <li><a href="http://www.open-open.com/lib/view/open1436089902667.html">使用API Gateway</a></li> <li><a href="http://www.open-open.com/lib/view/open1462433241328.html">深入微服务架构的进程间通信</a></li> </ul> <p> </p> <p>来自:http://dockone.io/article/771 </p>