在传统的单体应用中,组件之间的调用通过有规范约束的接口进行,实现不同模块间良好协作。在微服务架构中,原本的’巨石’应用按照业务被分割成相对独立的、提供特定功能的服务,每一个微服务都可以通过集群或者其他方式进行动态的扩展,每一个微服务实例的网络地址都可能动态变化,这使得原本通过硬编码地址的调用方式失去了适用性。微服务架构中,服务跨度之大,数量之多,迫切需要架构建立一个去中心化的组件对各个微服务实例的信息进行登记和管理,同时提供能力让各个服微务实例之间能够互相发现,从而达到互相调用的结果。
通常来说服务注册与发现包括两部分,一个是Server端,另一个是Client。Server是一个公用组件,为Client提供服务注册和发现的功能,维护着注册到自身的Client的相关信息,同时提供接口给Client获取到注册表中其他服务的信息,使得动态变化的Client在服务地址稳定的时间节点能够进行服务间调用。Client将自己的服务信息通过一定的方式登记到Server上,并在正常范围内维护自己信息的一致性,方便其他服务发现自己,同时可以通过Server获取到自己依赖的其他服务信息,完成服务调用。
Eureka这个词来源于古希腊语,意为“我找到了!我发现了!”,据传,阿基米德在洗澡时发现浮力原理,高兴得来不及穿上裤子,跑到街上大喊:“Eureka(我找到了)!”。
在Netflix中,Eureka是一个REST风格的服务注册与发现的基础服务组件,主要是应用在AWS中定位中间层服务的负载均衡和故障转移。Eureka由两部分组成,一个是Eureka Server,提供服务注册和发现的功能,就是我们上面所说的Server端;另一个是Java客户端,称为Eureka Client,是为了使得与服务端交互更加简单,Eureka Client会定时将自己的信息注册到Eureka Server中,并从Server中发现其他服务。客户端中有一个内置的负载均衡器,用来进行基本的循环负载均衡。
Spring Cloud的版本基于Finchley.M9,spring-cloud-netflix的版本基于2.0.0.M8,Eureka的版本基于v1.8.7。
可以通过IDEA快速搭建包含Eurake Server依赖的SpringBoot项目
主要依赖
<dependency> // eureka-client相关依赖 <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> // actuator相关依赖 <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> // Spring Web MVC相关依赖 <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
在启动类中的注解 @EnableEurekaServer
@SpringBootApplication // `@EnableEurekaServer`注解会为项目自动配置必须的配置类,标识该服务为注册中心 @EnableEurekaServer public class Chapter4EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(Chapter2EurekaServerApplication.class, args); } }
在 application.yml
配置文件中添加以下配置,配置注册中心的端口和标识自己为Eureka Server。
server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false // 表明该服务不会向Eureka Server注册自己的信息 fetch-registry: false // 表明该服务不会向Eureka Server获取注册信息 service-url: // Eureka Server注册中心的地址,用于client与server进行交流 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
Eureka Server既可以独立部署,也可以集群部署,在集群部署的条件下,Eureka Server间会进行注册表信息同步的操作,这时被同步注册表信息的Eureka Server将会被同步注册表信息的Eureka Server称为 peer
。
请注意上述配置中service-url其实指向的注册中心为实例本身,通常来讲,每一个Eureka Server也是一个Eureka Client,它会尝试注册自己,所以需要最少一个注册中心的URL来定位对等点 peer
。如果不提供这样一个注册端点,注册中心也能工作,但是会在日志中打出无法向 peer
注册自己。在独立(Standalone)Eureka Server的模式下,Eureka Server一般会关闭作为client端注册自己的行为,注册中心将无注册到它的 peer
中。
Eureka Server与Eureka Client之间的维系主要通过心跳的方式实现,心跳(Heartbeat)即Eureka Client定时向Eureka Server汇报当前的本服务实例的状态。
Eureka Server需要随时维持最新的服务实例信息,所以在注册表中的每个服务实例都需要定期发送心跳到Server中以使自己的注册保持最新的状态(数据一般直接保存在内存中)。这意味着Eureka Client不需要为每次服务间请求都向注册中心请求依赖服务实例信息,Eureka Client将定时从Eureka Server中拉取Eureka Server中的注册表中的所有信息,并将注册表信息缓存到本地,用于服务发现。
启动Eureka Server后,应用会有一个主页面用来展示当前注册表中的服务实例信息和暴露一些基于HTTP协议的的endpoint在 /eureka
路径下被Eureka Client用于注册自身、获取注册表信息以及发送心跳等。
可以通过IDEA快速搭建包含Eurake Client依赖的SpringBoot项目。
主要依赖有:
<dependency>// eureka-server相关依赖 <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
启动类
@SpringBootApplication public class Chapter4EurekaClientApplication { public static void main(String[] args) { SpringApplication.run(Chapter2EurekaClientApplication.class, args); } }
在 Finchley
版本中的Spring Cloud中,只要引入 spring-cloud-starter-netflix-eureka-client
的依赖,应用就会自动注册到Eureka Server,但是需要在配置文件中添加Eureka Server的地址。
在 application.yml
添加以下配置:
eureka: instance: prefer-ip-address: true client: service-url: // Eureka Server注册中心的地址,用于client与server进行交流 defaultZone: http://localhost:8761/eureka/ server: port: 8765 spring: application: name: eureka-client
搭建好上述的两个的Eureka应用后,依次启动Server和Client。
访问地址Eureka Server的主页 http://localhost:8761
,可以看到以下界面:
DiscoveryClient
来源于spring-cloud-client-discovery,是SpringCloud中定义的用来服务发现的顶级接口,在SpringCloud的各类服务发现组件中(如Netflix Eureka或者Consul)都有相应的实现。它提供从服务注册中心中根据serviceId的获取到对应的服务实例信息的能力。
当一个服务实例拥有 DiscoveryClient
的具体实现时,就可以从Eureka Server中发现其他的服务实例。
在Eureka Client中注入 DiscoveryClient
,并从Eureka Server获取服务实例的信息。
在 chapter2-eureka-client
添加一个 ServiceInstanceRestController
的controller
@RestController public class ServiceInstanceRestController { @Autowired private DiscoveryClient discoveryClient; @RequestMapping("/service-instances/{applicationName}") public List<ServiceInstance> serviceInstancesByApplicationName( @PathVariable String applicationName) { return this.discoveryClient.getInstances(applicationName); } }
启动应用后,访问地址 http://localhost:8765/service-instances/eureka-client
,获取应用名为 eureka-client
的(服务本身)服务实例的元数据,结果如下:
[ { "host":"192.168.1.168", "port":8765, "metadata":{ "management.port":"8765", "jmx.port":"59110" }, "uri":"http://192.168.1.168:8765", "secure":false, "serviceId":"EUREKA-CLIENT", "instanceInfo":{ "instanceId":"192.168.1.168:eureka-client:8765", "app":"EUREKA-CLIENT", "appGroupName":null, "ipAddr":"192.168.1.168", "sid":"na", "homePageUrl":"http://192.168.1.168:8765/", "statusPageUrl":"http://192.168.1.168:8765/info", "healthCheckUrl":"http://192.168.1.168:8765/health", "secureHealthCheckUrl":null, "vipAddress":"eureka-client", "secureVipAddress":"eureka-client", "countryId":1, "dataCenterInfo":{ "@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", "name":"MyOwn" }, "hostName":"192.168.1.168", "status":"UP", "leaseInfo":{ "renewalIntervalInSecs":30, "durationInSecs":90, "registrationTimestamp":1515585341831, "lastRenewalTimestamp":1515585341831, "evictionTimestamp":0, "serviceUpTimestamp":1515585341260 }, "isCoordinatingDiscoveryServer":false, "metadata":{ "management.port":"8765", "jmx.port":"59110" }, "lastUpdatedTimestamp":1515585341831, "lastDirtyTimestamp":1515581890364, "actionType":"ADDED", "asgName":null, "overriddenStatus":"UNKNOWN" } } ]
Eureka中的标准元数据有主机名、IP地址、端口号、状态页url和健康检查url等,这些元数据都会保存在Eureka Server的注册信息表中,Eureka Client通过根据服务名读取这些元数据来发现和调用其他的服务实例。元数据可以自定义以适应特定的业务场景,这些将在下面章节进行讲解。
详细了解本书: 地址