资深架构师带你通过手写代码实现服务的注册与发现~ 附代码示例链接!

Posted jinggege795

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了资深架构师带你通过手写代码实现服务的注册与发现~ 附代码示例链接!相关的知识,希望对你有一定的参考价值。

服务的注册与发现

解决了服务的远程调用问题之后,是不是就足够了呢?答案肯定是不够的。言归正传,设想一下,在微服务架构中会有多个服务进行交互,假设我们使用的是HTTP的通信方式,那么系统结构应该为如图2.8所示的网状调用结构,多个微服务直接相互调用。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

HTTP的交互方式显然比消息队列简单得多,多个微服务相互调用也变得简单而直接。但不管是哪种方式,随着服务越来越多,微服务的调用网也将越来越大、越来越复杂。你会发现需要管理的服务信息越来越多,这些信息可能包括服务的IP、端口和URL等数据,而且这些信息需要在每个服务的消费方进行维护。

在图2.8中,服务A需要维护服务B和服务E的信息,服务B则需要维护服务A、服务D和服务E的消息,一旦某一个服务的地址或端口发生变化,所有调用它的消费方都需要进行相应的配置修改。笔者曾经做过拥有一百多个服务组成的产品级项目,当时的架构设计服务之间就是直接相互调用,所有的服务信息都是服务调用方在自己的服务端的配置文件中进行配置。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

当然,一个服务不可能与一百多个服务都进行交互,但可能会与十几个服务进行通信,当服务信息发生变化时,需要非常熟悉系统的人花费接近一周的时间进行人工测试和排查,然后去修改被影响的服务的配置,才能保证服务更新后系统的正常运行,而且就算是再熟悉系统的人也可能会有遗漏。

那么如何才能解决这个问题呢?答案非常简单,既然人工检测和更新效率低下且容易出错,那就改用自动即可。回忆一下之前介绍过的SOA的服务调用设计,微服务架构中沿用了服务注册这一设计,提供服务注册的服务通常称为注册中心,如图2.9所示。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

服务注册中心的设计有效地解决了这个问题,服务提供者可以理解为微服务中的一个服务,服务消费者可以是其他的微服务,也可以是BFF(Backend For Frontend,用于前端的后端)、API网关等服务。关于BFF和API网关将在后续章节陆续介绍。

当服务提供者需要对外提供服务时,会主动向服务注册中心注册,注册中心会保存各个服务提供者的信息,并且通过心跳等机制定期检查服务提供者的健康情况,一旦检测到服务不可用,就要根据一定的规则从注册中心剔除该服务的信息。

服务消费者只需配置注册中心的信息,然后通过唯一标识(如服务提供者的应用名称),就可以查询到服务提供者的信息,也包括服务提供者的健康状态。当服务提供者的信息发生变化时,如修改了一些服务实例的端口号,只要服务提供者在注册时所使用的唯一标识不变,服务消费者是无须修改任何代码或配置的,注册中心会将最新的、可用的服务提供者的信息返回服务消费者。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

那么,具体的技术实现有哪些?目前作为注册中心,比较主流的可能是通过ZooKeeper、Consul和Eureka这样的服务框架来完成,当然也完全可以自己设计一个,笔者曾经用Redis写过一个服务注册中心。要实现注册中心并不难,难的是需要开发全套的、完整的服务治理框架,Spring在这一点上有着天然的优势。因此,我们还是以SpringCloud为例,Spring在众多框架中选择Netflix的Eureka作为默认的基础服务注册框架,当然Spring也分别实现了基于ZooKeeper和Consul的注册中心,同时Spring也在开发一套原生的注册中心,以摆脱第三方的框架,但是无论是从成熟度还是从运用的广度来看,Spring CloudNetflix Eureka还是目前的首选。下面来看看Spring Cloud NetflixEureka的具体实践。

Spring Cloud Netflix Eureka主要分为Server和Client两个概念,注册中心就是Server,其他的服务注册者和服务调用者都是Client。当然,如果你部署的是一个注册中心的集群,那么注册中心之间也会以Client的身份相互注册。

下面先来看如何配置Eureka的Server,首先需要引入相关的包,其中最主要的是spring 
cloud-starter-eureka-server,如果你使用的是Gradle,那么可以按照如下代码配置

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

笔者比较喜欢从Spring的官网上自动生成干净的新项目,然后稍作修改即可投入使用 , 推荐大家一个网址 :https://start.spring.io/,该网站可以自由选择Spring的组件进行组合,然后生成项目初始代码,如图2.10所示的Spring应用初始化工具界面。在完成了项目的基本配置后,只需简单的几个步骤就可以启动注册中心了,首先需要在 Spring Boot 对应的启动类上增加@EnableEurekaServer注解,代码如下

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

然后,需要在application.yml中增加一些Eureka Server的配置,此处只做一些简单的配置,详细配置可以参考Spring的官方教程,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

当然,除了域名也可以使用ip-address来配置注册中心的IP地址,笔者这里配置了计算机的HOSTS,编辑/etc/hosts文件,内容如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

直接使用hostname配置的注册中心,完成这项基本配置后,就可以直接启动服务了,服务启动后会提供一个监控页面,访问地址为http://ms-registry1:8080,监控页面如图2.11所示。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

该页面会显示Eureka Client的注册信息及它们的健康情况,现在只有一个Eureka Server在运行,可以看到在图2.11的最下面,当前在Eureka注册的实例显示的是无可用的实例,在DS Replicas (副本)中显示的是localhost,即没有副本,只有本地一个实例。

我们可以尝试配置一个简单的集群,来看看这个监控页面的变化,首先需要再启动一个Eureka Server的实例。假设我们配置的新的Eureka Server 实 例 的 应 用 名 称 是 ms-register2 ,域 名 是 ms-registry2,application.yml中的配置如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

那么,回到ms-registry1,之前其他的配置都不变,唯一需要修改的是application.yml中的配置,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

我们增加了client下service-url的配置,正如之前所说的,这里将ms-registry1当作ms registry2的Client,这时再次访问http://ms-registry1:8080,来看看现在的变化,监控页面如图2.12所示。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

这时DS Replicas显示的不再是localhost,而是ms-registry2,这表示ms-registry1和ms registry2已经建立同步副本的关系。访问一下ms-registry2的监控页面,监控页面如图2.13所示。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

这说明ms-registry1已经成功注册到了ms-registry2中,通常我们会将多个Eureka Server相互注册,构成一个统一的集群,以达到高可用的目的。

配置完Server,如何配置Client呢?无论是服务提供者还是服务消费者,对于Eureka Server来说都是Client,这里需要引入spring
cloud-starter-netflix-eureka-client的库,新建一个工程,项目可以通过https://start.spring.io/进行初始化,build.gradle代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

application.yml的配置可以参考上文配置Server集群时的client下service-url的配置,毕竟Server相互之间就是Server与Client的关系,如果Server之间都相互进行注册,那么service url只配置一个实例的地址即可,Eureka Server之间可以进行信息复制,具体代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

如果不配置服务的端口,Spring默认为8080,一般我们会设置服务的端口为0,那么Spring就会生成一个随机数作为端口号,如果端口占用就会重新生成一个,这样也省去了我们去关心端口的配置,服务消费者只需通过应用名称(如ms-provider),就可以对该服务进行调用,如果使用的是RestTemplate,那么调用的URL可以写成如下代码。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

Client的配置还有一点不同,就是在SpringBoot的启动类上不再使用@EnableEurekaServer注解,而是使用@EnableDiscoveryClient或@EnableEurekaClient注解,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

当 然 , 有 时 我 们 也 使 用 @EnableEurekaClient , 那么它和@EnableDiscoveryClient有什么区别呢?从两者的所属库就可以看出,@EnableEurekaClient是Netflix Eureka Client中的实现,它只支持Eureka作为注册中心,如果你使用Eureka,那么可以使用@EnableEurekaClient 。 @EnableDiscoveryClient 是 Spring Cloud Common中的实现,使用时Spring会根据你的classpath中的依赖来判断目前使用的是Eureka还是ZooKeeper,或者是Consul,然后动态地去初始化注册服务的配置。

启动后,再次访问注册中心页面,监控页面如图2.14所示。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

在当前注册的实例列表中,除本身两个相互注册的注册中心msregistry1和ms-registry2,ms-provider也出现在列表中,此时表明服务已经注册成功。

负载均衡

如果服务部署了多个实例?这时又如何处理?首先可能想到的是反向代理,提到负载均衡,很多人都想到nginx,确实在以往的单应用模式下的系统中,最常见的做法就是通过Nginx来完成负载均衡策略,如随机、轮询和权重等。例如,我们一般会部署多个Tomcat实例作为多个Web服务器,然后通过Nginx的反向代理来分发客户端的请求到不同的Web服务器,而Nginx可以定义不同的规则来分发这些请求,以达到负责均衡的目的。反向代理好比是集中式的统一入口,所有的服务信息都需要被描述在入口处,然后由入口来决定请求的去向,也就是负载的策略。

当然,也有通过一些中间件的方式来达到负载均衡,如SOA中的消息总线模式,通过中间的消息层来转发请求,从而达到负载均衡的目的。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

那么,在微服务中又是如何设计的呢?微服务强调分离,提倡职责单一,虽然引入了注册中心的概念,但是注册中心除了负责对服务信息的统一微服,还包括对服务的健康状况等信息进行监控,而真正去完成负载均衡任务的是服务的消费者,这样做也比较符合逻辑,消费者自行决定需要采用哪些策略去调用服务提供者。

不过,消费者在做决定时,可以通过注册中心的消息来帮助自己做决定。例如,注册中心告诉消费者服务A的B实例是不可用状态,那么消费者在请求服务A时,则会把B实例剔除,如果注册中心告诉消费者B实例已经恢复为可用状态,那么消费者会重新把B实例加入自己可调用的目标中。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

下面以Spring Cloud为例,一起来看看在Java的微服务项目中远程调用的具体技术实践。

Spring Cloud的设计是让服务之间采用HTTP的方式进行远程调用,所以Spring Web框架本身就提供RestTemplate、WebClient等HTTP通信的实现。而Spring Cloud Ribbon是客户端的负载均衡器,还有Spring Cloud Feign,Feign是对Ribbon的封装,提供了一些便捷的高级功能。如果不考虑使用客户端的负载均衡,就可以完全不集成该组件。在大多数情况下,通过软方法在客户端使用负载均衡也是一个不错的选择,下面介绍Spring Cloud Ribbon和Spring Cloud Feign的用法。

新建一个工程,build.gradle配置如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

然后需要使用和provider相同的方式,将项目实例加入注册中心中,application.yml的配置如下。

1. Spring Cloud Ribbon

Spring Cloud Ribbon的用法十分简单,首先是要引入
spring-cloud-starter-netflix-ribbon的库,下面以使用RestTemplate为例,需要在初始化RestTemplate时加上@LoadBalanced注解,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

如果使用了WebFlux,那么WebClient的用法如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

然后调用时需要build()一下,如调用一个程序用户的接口,其代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

当你在使用RestTemplate请求其他服务时,就会默认使用Ribbon的负载均衡策略进行请求,默认的是轮询,即会对所有可用的服务实例进行轮询访问。可以编写测试程序来测试代码是否生效。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

当然,Ribbon为我们提供了7种负载均衡策略,如表2.2所示。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

然后,可以通过配置application.yml文件来配置我们想要的策略。例如,想将负载均衡策略改成随机选择RandomRule,那么配置如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

配置完成后,还需要配置对应IRule类型的Bean去覆盖原有的Rule实例,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

2. Spring Cloud Feign

Feign其实是对Ribbon的一个高级的封装,负载策略与Ribbon一致。首先,Feign在Ribbon的基础上提供了更加简便的服务调用方式,可以像调用本地方法一样调用远程服务;其次,Feign还集成了断路器:Spring Cloud Netflix Hystrix。这里先来介绍Spring Cloud Feign是如何优化远程调用方式的。

首先,需要引入
spring-cloud-starter-openfeign的库,然后在Spring Boot的启动类上加上@EnableFeignClients注解,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

然后,需要定义一个接口,并加上@FeignClient注解的value指定具体要调用的服务提供者的应用名称,接口的方法必须与服务提供者的Controller中的方法一样,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

服务消费者在需要调用该服务时,直接通过Spring的依赖注入的方式,即可自动注入FeignClient,然后调用响应的方法,代码如下。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

可以编写测试来测试代码是否生效。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

本 文 中 所 有 的 代 码 示 例 均 可 在 GitHub 中 找 到 , 地 址 为
https://github.com/FutureElement/microservice-patterns-book。

资深架构师带你通过手写代码实现服务的注册与发现 附代码示例链接

觉得文章不错的朋友可以点赞+转发+收藏此文关注小编,有需要的可以私信小编获取~

感谢大家的支持!

以上是关于资深架构师带你通过手写代码实现服务的注册与发现~ 附代码示例链接!的主要内容,如果未能解决你的问题,请参考以下文章

现代C++实战30讲,资深架构师带你编写高性能代码

一小时架构师带你实践 Spring Cloud微服务架构搭建。分分钟钟让你从小白变为大佬

腾讯资深架构师带你深入解析spring boot相关面试题 让你不在恐惧面试,轻松进大厂

资深架构师带你了解分布式架构的演进过程

RPC实战与核心原理,京东首席架构师带你解决分布式通信难题

云智慧10年资深架构师带你了解:普通程序员向架构师成长必经之路