带你读《Spring Cloud微服务:入门、实战与进阶》之三:Eureka注册中心(en)

 容器服务Docker K8S(en)     |      2019-11-07 00:00:00

点击查看第一章
点击查看第二章

第3章

Eureka注册中心
注册中心在微服务架构中是必不可少的一部分,主要用来实现服务治理功能,本章我们将学习如何用 Netflix 提供的 Eureka 作为注册中心,来实现服务治理的功能。

3.1 Eureka

Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分,基于 Netflix Eureka 做了二次封装,主要负责实现微服务架构中的服务治理功能。Spring Cloud Eureka 是一个基于 REST 的服务,并且提供了基于 Java 的客户端组件,能够非常方便地将服务注册到Spring Cloud Eureka 中进行统一管理。
服务治理是微服务架构中必不可少的一部分,阿里开源的 Dubbo 框架就是针对服务治理的。服务治理必须要有一个注册中心,除了用 Eureka 作为注册中心外,我们还可以使用 Consul、Etcd、Zookeeper等来作为服务的注册中心。
用过 Dubbo 的读者应该清楚,Dubbo中也有几种注册中心,比如基于Zookeeper、基于 Redis 等,不过用得最多的还是 Zookeeper方式。至于使用哪种方式都是可以的,注册中心无非就是管理所有服务的信息和状态。若用我们生活中的例子来说明的话,笔者觉得12306 网站比较合适。
首先,12306网站就好比一个注册中心,顾客就好比调用的客户端,当他们需要坐火车时,就会登录12306 网站上查询余票,有票就可以购买,然后获取火车的车次、时间等,最后出发。
程序也是一样,当你需要调用某一个服务的时候,你会先去 Eureka 中去拉取服务列表,查看你调用的服务在不在其中,在的话就拿到服务地址、端口等信息,然后调用。
注册中心带来的好处就是,不需要知道有多少提供方,你只需要关注注册中心即可,就像顾客不必关心有多少火车在开行,只需要去12306网站上看有没有票就可以了。
为什么Eureka 比 Zookeeper 更适合作为注册中心呢?主要是因为 Eureka 是基于 AP 原则构建的,而 ZooKeeper 是基于 CP 原则构建的。在分布式系统领域有个著名的 CAP 定理, 即 C 为数据一致性;A 为服务可用性;P 为服务对网络分区故障的容错性。这三个特性在任何分布式系统中都不能同时满足,最多同时满足两个。
Zookeeper 有一个 Leader,而且在这个Leader 无法使用的时候通过 Paxos(ZAB)算法选举出一个新的 Leader。这个 Leader 的任务就是保证写数据的时候只向这个 Leader 写入, Leader 会同步信息到其他节点。通过这个操作就可以保证数据的一致性。
总而言之,想要保证 AP 就要用 Eureka,想要保证 CP 就要用 Zookeeper。Dubbo 中大部分都是基于 Zookeeper 作为注册中心的。Spring Cloud 中当然首选 Eureka。

3.2 使用 Eureka 编写注册中心服务

首先创建一个 Maven 项目,取名为 eureka-server,在 pom.xml 中配置 Eureka 的依赖信息,如代码清单 3-1 所示。

image.png
image.png

创建一个启动类 EurekaServerApplication,如代码清单 3-2 所示。

image.png

这里所说的启动类,跟我们之前讲的 Spring Boot 几乎完全一样,只是多了一个 @EnableEurekaServer 注解,表示开启 Eureka Server。
接下来在 src/main/resources 下面创建一个 application.properties 属性文件,增加下面的配置:
image.png

eureka.client.register-with- eureka 一定要配置为 false,不然启动时会把自己当作客户端向自己注册,会报错。
接下来直接运行EurekaServerApplication 就可以启动我们的注册中心服务了。我们在 application.properties 配置的端口是 8761,则可以直接通过 http://localhost:8761/ 去浏览器中访问,然后便会看到Eureka 提供的 Web 控制台,如图3-1 所示。

image.png

3.3 编写服务提供者

3.3.1 创建项目注册到Eureka

注册中心已经创建并且启动好了,接下来我们实现将一个服务提供者eureka-client-user-service 注册到 Eureka 中,并提供一个接口给其他服务调用。
首先还是创建一个 Maven 项目,然后在 pom.xml 中增加相关依赖,如代码清单 3-3
所示。

image.png

创建一个启动类App,代码如代码清单 3-4 所示。

image.png

启动类的方法与之前没有多大区别,只是注解换成@EnableDiscoveryClient,表示当前服务是一个 Eureka 的客户端。
接下来在src/main/resources下面创建一个 application.properties属性文件,增加下面的配置:
image.png

eureka.client.serviceUrl.defaultZone 的地址就是我们之前启动的 Eureka 服务的地址,在启动的时候需要将自身的信息注册到 Eureka 中去。
执行 App 启动服务,我们可以看到控制台中有输出注册信息的日志:
DiscoveryClient_EUREKA-CLIENT-USER-SERVICE/eureka-client-user-service:192.168.31.245:8081 - registration status: 204
我们可以进一步检查服务是否注册成功。回到之前打开的Eureka 的 Web 控制台,刷新页面,就可以看到新注册的服务信息了,如图3-2 所示。

image.png

3.3.2 编写提供接口

创建一个 Controller,提供一个接口给其他服务查询,如代码清单 3-5 所示。

image.png
image.png

重启服务,访问 http://localhost:8081/user/hello ,如果能看到我们返回的 Hello 字符串,就证明接口提供成功了。

3.4 编写服务消费者

3.4.1 直接调用接口

创建服务消费者,消费我们刚刚编写的 user/hello 接口,同样需要先创建一个 Maven 项目eureka-client-article-service,然后添加依赖,依赖和服务提供者的一样,这里就不贴代码了。
创建启动类App,启动代码与前面所讲也是一样的。唯一不同的就是 application.properties 文件中的配置信息:
spring.application.name=eureka-client-article-service
server.port=8082
RestTemplate 是Spring提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法,能够大大提高客户端的编写效率。我们通过配置RestTemplate 来调用接口,如代码清单 3-6 所示。

image.png

创建接口,在接口中调用 user/hello 接口,代码如代码清单 3-7 所示。

image.png
image.png

执行App启动消费者服务,访问/article/callHello 接口来看看有没有返回 Hello 字符串,如果返回了就证明调用成功。访问地址为 http://localhost:8082/article/callHello

3.4.2 通过Eureka来消费接口

上面提到的方法是直接通过服务接口的地址来调用的,和我们之前的做法一样,完全没有用到 Eureka 带给我们的便利。既然用了注册中心,那么客户端调用的时候肯定是不需要关心有多少个服务提供接口,下面我们来改造之前的调用代码。
首先改造 RestTemplate 的配置,添加一个 @LoadBalanced 注解,这个注解会自动构造LoadBalancerClient 接口的实现类并注册到 Spring 容器中,如代码清单 3-8 所示。

image.png

接下来就是改造调用代码,我们不再直接写固定地址,而是写成服务的名称,这个名称就是我们注册到 Eureka 中的名称,是属性文件中的 spring.application.name,相关代码如代码清单 3-9 所示。

image.png

3.5 开启 Eureka 认证

Eureka 自带了一个 Web 的管理页面,方便我们查询注册到上面的实例信息,但是有一个问题:如果在实际使用中,注册中心地址有公网 IP 的话,必然能直接访问到,这样是不安全的。所以我们需要对 Eureka 进行改造,加上权限认证来保证安全性。
改造我们的 eureka-server,通过集成 Spring-Security 来进行安全认证。
在 pom.xml 中添加 Spring-Security 的依赖包,如代码清单 3-10 所示。

image.png

然后在 application.properties 中加上认证的配置信息:
spring.security.user.name=yinjihuan #用户名
spring.security.user.password=123456 #密码
增加Security配置类:

image.png

重新启动注册中心,访问 http://localhost:8761/ ,此时浏览器会提示你输入用户名和密码,输入正确后才能继续访问 Eureka 提供的管理页面。
在 Eureka 开启认证后,客户端注册的配置也要加上认证的用户名和密码信息:
eureka.client.serviceUrl.defaultZone=

            http://yinjihuan:123456@localhost:8761/eureka/

3.6 Eureka高可用搭建

3.6.1 高可用原理

前面我们搭建的注册中心只适合本地开发使用,在生产环境中必须搭建一个集群来保证高可用。Eureka 的集群搭建方法很简单:每一台 Eureka 只需要在配置中指定另外多个Eureka 的地址就可以实现一个集群的搭建了。
下面我们以 2 个节点为例来说明搭建方式。假设我们有 master 和 slaveone 两台机器, 需要做的就是:

  • 将 master 注册到 slaveone 上面。
  • 将 slaveone 注册到 master 上面。

如果是 3 台机器,以此类推:

  • 将 master 注册到 slaveone 和 slavetwo 上面。
  • 将 slaveone 注册到 master 和 slavetwo 上面。
  • 将 slavetwo 注册到 master 和 slaveone 上面。

3.6.2 搭建步骤

创建一个新的项目eureka-server-cluster,配置跟eureka-server一样。
首先,我们需要增加 2 个属性文件,在不同的环境下启动不同的实例。增加 application-master.properties:

image.png

在A机器上默认用 master启动,然后在B机器上加上 --spring.profiles.active= slaveone 启动即可。
这样就将master注册到了slaveone中,将slaveone 注册到了master中,无论谁出现问题,应用都能继续使用存活的注册中心。
之前在客户端中我们通过配置eureka.client.serviceUrl.defaultZone来指定对应的注册中心,当我们的注册中心有多个节点后,就需要修改eureka.client.serviceUrl.defaultZone的配置为多个节点的地址,多个地址用英文逗号隔开即可:
eureka.client.serviceUrl.defaultZone=http://yinjihuan:123456@localhost:8761

           /eureka/,http://yinjihuan:123456@localhost:8762/eureka/

3.7 常用配置讲解

3.7.1 关闭自我保护

保护模式主要在一组客户端和 Eureka Server 之间存在网络分区场景时使用。一旦进入保护模式,Eureka Server 将会尝试保护其服务的注册表中的信息,不再删除服务注册表中的数据。当网络故障恢复后,该 Eureka Server 节点会自动退出保护模式。
如果在 Eureka 的 Web 控制台看到图3-3 所示的内容,就证明Eureka Server进入保护模式了。

image.png

可以通过下面的配置将自我保护模式关闭,这个配置是在eureka-server 中:
eureka.server.enableSelfPreservation=false

3.7.2 自定义Eureka的InstanceID

客户端在注册时,服务的 Instance ID 的默认值的格式如下:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application. instance_id:${server.port}}
翻译过来就是“主机名:服务名称:服务端口”。当我们在 Eureka 的 Web 控制台查看服务注册信息的时候,就是这样的一个格式:user-PC:eureka-client-user-service:8081。
很多时候我们想把 IP 显示在上述格式中,此时,只要把主机名替换成 IP 就可以了,或者调整顺序也可以。可以改成下面的样子,用“服务名称:服务所在IP:服务端口”的格式来定义:
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
定义之后我们看到的就是 eureka-client-user-service:192.168.31.245:8081,一看就知道是哪个服务,在哪台机器上,端口是多少。
我们还可以点击服务的 Instance ID 进行跳转,这个时候显示的名称虽然变成了 IP,但是跳转的链接却还是主机名,请看图3-4 所示界面的左下角。

image.png

所以还需要加一个配置才能让跳转的链接变成我们想要的样子,使用 IP 进行注册,如图3-5 所示:
eureka.instance.preferIpAddress=true

image.png

3.7.3 自定义实例跳转链接

在3.7.2中,我们通过配置实现了用 IP 进行注册,当点击 Instance ID 进行跳转的时候,就可以用 IP 跳转了,跳转的地址默认是 IP+Port/info。我们可以自定义这个跳转的地址:
eureka.instance.status-page-url=http://cxytiandi.com
效果如图3-6 所示。

image.png

3.7.4 快速移除已经失效的服务信息

在实际开发过程中,我们可能会不停地重启服务,由于 Eureka 有自己的保护机制,故节点下线后,服务信息还会一直存在于 Eureka 中。我们可以通过增加一些配置让移除的速度更快一点,当然只在开发环境下使用,生产环境下不推荐使用。
首先在我们的eureka-server中增加两个配置,分别是关闭自我保护和清理间隔:
image.png

eureka.client.healthcheck.enabled 用于开启健康检查,需要在 pom.xml 中引入 actuator
的依赖,如代码清单 3-12 所示。

image.png

其中:

  • eureka.instance.lease-renewal-interval-in-seconds表示Eureka Client发送心跳给server端的频率。
  • eureka.instance.lease-expiration-duration-in-seconds 表示 Eureka Server 至上一次收到client 的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则移除该 Instance。

更多的Instance 配置信息可参考源码中的配置类:org.springframework.cloud.netflix. eureka.
EurekaInstanceConfigBean。
更多的Server配置信息可参考源码中的配置类:org.springframework.cloud.netflix. eureka. server.EurekaServerConfigBean。

3.8 扩展使用

3.8.1 Eureka REST API

Eureka 作为注册中心,其本质是存储了每个客户端的注册信息,Ribbon 在转发的时候会获取注册中心的服务列表,然后根据对应的路由规则来选择一个服务给 Feign 来进行调用。如果我们不是 Spring Cloud 技术选型,也想用 Eureka,可以吗?完全可以。
如果不是 Spring Cloud 技术栈,笔者推荐用 Zookeeper,这样会方便些,当然用 Eureka 也是可以的,这样的话就会涉及如何注册信息、如何获取注册信息等操作。其实 Eureka 也考虑到了这点,提供了很多 REST 接口来给我们调用。
我们举一个比较有用的案例来说明,比如对 Nginx 动态进行 upstream 的配置。
在架构变成微服务之后,微服务是没有依赖的,可以独立部署,端口也可以随机分配, 反正会注册到注册中心里面,调用方也无须关心提供方的 IP和Port,这些都可以从注册中心拿到。但是有一个问题:API 网关的部署能这样吗?API网关大部分会用 Nginx 作为负载,那么Nginx就必须知道API网关有哪几个节点,这样网关服务就不能随便启动了,需要固定。
当然网关是不会经常变动的,也不会经常发布,这样其实也没什么大问题,唯一不好的就是不能自动扩容了。
其实利用 Eureka 提供的 API 我们可以获取某个服务的实例信息,也就是说我们可以根据 Eureka 中的数据来动态配置 Nginx 的 upstream。
这样就可以做到网关的自动部署和扩容了。网上也有很多的方案,结合 Lua 脚本来做, 或者自己写 Sheel 脚本都可以。
下面举例说明如何获取 Eureka 中注册的信息。具体的接口信息请查看官方文档:
https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
获取某个服务的注册信息,可以直接 GET 请求:http://localhost:8761/eureka/apps/eureka-client-user-service 。其中,eureka-client-user-service 是应用名称,也就是 spring.application.name。
在浏览器中,数据的显示格式默认是 XML 格式的,如图3-7 所示。

image.png

如果想返回 Json 数据的格式,可以用一些接口测试工具来请求,比如 Postman,在请求头中添加下面两行代码即可,具体如图3-8 所示。
Content-Type:application/json Accept:application/json
如果 Eureka 开启了认证,记得添加认证信息,用户名和密码必须是 Base64 编码过的 Authorization:Basic 用户名:密码,其余的接口就不做过多讲解了,大家可以自己去尝试。Postman 直接支持了 Basic 认证,将选项从 Headers 切换到 Authorization,选择认证方式为 Basic Auth 就可以填写用户信息了,如图3-9 所示。
填写完之后,直接发起请求就可以了。我们切换到 Headers 选项中,就可以看到请求头中已经多了一个 Authorization 头。

image.png

image.png

3.8.2 元数据使用

Eureka 的元数据有两种类型,分别是框架定好了的标准元数据和用户自定义元数据。标准元数据指的是主机名、IP 地址、端口号、状态页和健康检查等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。自定义元数据可以使用 eureka.instance. metadataMap 进行配置。
自定义元数据说得通俗点就是自定义配置,我们可以为每个 Eureka Client 定义一些属于自己的配置,这个配置不会影响 Eureka 的功能。自定义元数据可以用来做一些扩展信息,比如灰度发布之类的功能,可以用元数据来存储灰度发布的状态数据,Ribbon 转发的时候就可以根据服务的元数据来做一些处理。当不需要灰度发布的时候可以调用 Eureka 提供的 REST API 将元数据清除掉。
下面我们来自定义一个简单的元数据,在属性文件中配置如下:
eureka.instance.metadataMap.yuantiandi=yinjihuan
上述代码定义了一个 key 为 yuantiandi 的配置,value 是 yinjihuan。重启服务,然后通过 Eureka 提供的 REST API 来查看刚刚配置的元数据是否已经存在于 Eureka 中,如图3-10 所示。

image.png

3.8.3 EurekaClient使用

当我们的项目中集成了 Eureka 之后,可以通过 EurekaClient 来获取一些我们想要的数据,比如上节讲的元数据。我们就可以直接通过 EurekaClient 来获取(见代码清单 3-13),不用再去调用 Eureka 提供的 REST API。

image.png

通过 PostMan 来调用接口看看有没有返回我们想要的数据,如图3-11 所示。

image.png

可以看到,通过 EurekaClient 获取的数据跟我们自己去掉 API 获取的数据是一样的, 从使用角度来说前者比较方便。
除了使用 EurekaClient,还可以使用 DiscoveryClient(见代码清单 3-14),这个不是 Feign 自带的,是 Spring Cloud 重新封装的,类的路径为 org.springframework.cloud.client. discovery.DiscoveryClient。

image.png
image.png

3.8.4 健康检查

默认情况下,Eureka 客户端是使用心跳和服务端通信来判断客户端是否存活,在某些场景下,比如MongoDB出现了异常,但你的应用进程还是存在的,这就意味着应用可以继续通过心跳上报,保持应用自己的信息在Eureka中不被剔除掉。
Spring Boot Actuator 提供了 /actuator/health 端点,该端点可展示应用程序的健康信息,当MongoDB异常时,/actuator/health 端点的状态会变成DOWN,由于应用本身确实处于存活状态,但是MongoDB的异常会影响某些功能,当请求到达应用之后会发生操作失败的
情况。
在这种情况下,我们希望可以将健康信息传递给 Eureka 服务端。这样Eureka中就能及时将应用的实例信息下线,隔离正常请求,防止出错。通过配置如下内容开启健康检查:
eureka.client.healthcheck.enabled=true
我们可以通过扩展健康检查的端点来模拟异常情况,定义一个扩展端点,将状态设置为DOWN,如代码清单 3-15 所示。

image.png

扩展好后我们访问/actuator/health可以看到当前的状态是DOWN,如图3-12所示。

image.png

Eureka中的状态是UP,如图3-13所示。

image.png

这种情况下请求还是能转发到这个服务中,下面我们开启监控检查,再次查看Eureka中的状态,如图3-14所示。

image.png

3.8.5 服务上下线监控

在某些特定的需求下,我们需要对服务的上下线进行监控,上线或下线都进行邮件通知,Eureka 中提供了事件监听的方式来扩展。
目前支持的事件如下:

  • EurekaInstanceCanceledEvent 服务下线事件。
  • EurekaInstanceRegisteredEvent 服务注册事件。
  • EurekaInstanceRenewedEvent 服务续约事件。
  • EurekaRegistryAvailableEvent Eureka 注册中心启动事件。
  • EurekaServerStartedEvent Eureka Server 启动事件。

基于 Eureka 提供的事件机制,可以监控服务的上下线过程,在过程发生中可以发送邮件来进行通知。代码清单3-16只是演示了监控的过程,并未发送邮件。

image.png
image.png

注意:在Eureka集群环境下,每个节点都会触发事件,这个时候需要控制下发送通知的行为,不控制的话每个节点都会发送通知。

3.9 本章小结

通过本章的学习,我们已经能够独立搭建一个高可用的 Eureka 注册中心,通过服务提供者和服务消费者的案例我们也对 Eureka 进行了实际的运用。本章最后讲解了一些经常用到的配置信息及 Eureka 的 REST API,通过 API 可以做一些扩展。

(en)