4.4 通过Spring Eureka注册服务

现在有一个基于Spring的Eureka服务器正在运行。在本节中,我们将配置组织服务和许可证服务,以便通过Eureka服务器来注册它们自身。这项工作是为了让服务客户端从Eureka注册表中查找服务做好准备。在本节结束时,读者应该对如何通过Eureka注册Spring Boot微服务有一个明确的认识。
    
通过Eureka注册一个基于Spring Boot的微服务是非常简单的。出于本章的目的,这里不会详细介绍编写服务所涉及的所有Java代码(本书故意将代码量保持得很少),而是专注于如何使用在上一节创建的Eureka服务注册表来注册服务。
    
首先需要做的是将Spring Eureka依赖项添加到组织服务的pom.xml文件中:
<dependency>   
<groupId>org.springframework.cloud</groupId>   
<artifactId>spring-cloud-starter-eureka</artifactId>  ⇽---  引入Eureka库,以便可以使用Eureka注册服务 </dependency>
    
唯一使用的新库是spring-cloud-starter-eureka库。
spring-cloud-starter-eureka拥有Spring Cloud用于与Eureka服务进行交互的jar文件。
    
在创建好pom.xml文件后,需要告诉Spring Boot通过Eureka注册组织服务。这个注册是通过组织服务的src/main/java/resources/application.yml文件中的额外配置来完成的,如代码清单4-4所示。
    
代码清单4-4 修改组织服务的application.yml文件以便与Eureka通信
    spring:   
       application:     
          name: organizationservice  ⇽--将使用Eureka注册的服务的逻辑名称   
        profiles:     
           active:       
               default   
         cloud:    
            config:      
                enabled: true 
        eureka:
           instance:     
             preferIpAddress: true  ⇽---注册服务的IP,而不是服务器名称   
           client:     
             registerWithEureka: true  ⇽---向Eureka注册服务     
             fetchRegistry: true   ⇽---拉取注册表的本地副本     
               serviceUrl:    
                 defaultZone: http://localhost:8761/eureka/  ⇽---Eureka服务的位置                      
每个通过Eureka注册的服务都会有两个与之相关的组件:应用程序ID和实例ID。应用程序ID用于表示一组服务实例。在基于Spring Boot的微服务中,应用程序ID始终是由spring. application.name属性设置的值。对于上述组织服务,spring.application.name被命名为organizationservice。
实例ID是一个随机数,用于代表单个服务实例。
 
注意
 
记住,通常spring.application.name属性写在bootstrap.yml文件中。为了便于说明,我把它包含在application.yml文件中。上述代码将与spring.application.name一起使用,但是从长远来看,这个属性的适当位置是在bootstrap.yml文件中。
    
配置的第二部分提供了如何通过Eureka注册服务以及将服务注册在哪里。eureka.instance.preferIpAddress属性告诉Eureka,要将服务的IP地址而不是服务的主机名注册到Eureka。
    
为什么偏向于IP地址
    
在默认情况下,Eureka在尝试注册服务时,将会使用主机名让外界与它进行联系。这种方式在基于服务器的环境中运行良好,在这样的环境中,服务会被分配一个DNS支持的主机名。但是,在基于容器的部署(如Docker)中,容器将以随机生成的主机名启动,并且该容器没有DNS记录。
    
如果没有将eureka.instance.preferIpAddress设置为true,那么客户端应用程序将无法正确地解析主机名的位置,因为该容器不存在DNS记录。设置preferIpAddress属性将通知Eureka服务,客户端想要通过IP地址进行通告。
    
就本书而言,我们始终将这个属性设置为true。基于云的微服务应该是短暂的和无状态的,它们可以随意启动和关闭。IP地址更适合这些类型的服务。
    
eureka.client.registerWithEureka属性是一个触发器,它可以告诉组织服务通过Eureka注册它本身。eureka.client.fetchRegistry属性用于告知Spring Eureka客户端以获取注册表的本地副本。将此属性设置为 true将在本地缓存注册表,而不是每次查找服务都调用Eureka服务。每隔30 s,客户端软件就会重新联系Eureka服务,以便查看注册表是否有任何变化。
    
最后一个属性eureka.serviceUrl.defaultZone包含客户端用于解析服务位置的Eureka服务的列表,该列表以逗号进行分隔。对于本书而言,只有一个Eureka服务。
    
Eureka高可用性
    建立多个URL服务并不足以实现高可用性。eureka.serviceUrl.defaultZone属性仅为客户端提供一个进行通信的Eureka服务列表。
除此之外,还需要建立多个Eureka服务,以便相互复制注册表的内容。
    一组Eureka注册表相互之间使用点对点通信模型进行通信,在这种模型中,必须对每个Eureka服务进行配置,以了解集群中的其他节点。建立Eureka集群的内容超出了本书的范围。读者如果有兴趣建立Eureka集群,可以访问Spring Cloud项目的网站以获取更多信息。
    
到目前为止,已经有一个通过Eureka服务注册的服务。
    读者可以使用Eureka的REST API来查看注册表的内容。要查看服务的所有实例,可以以GET方法访问端点:
    http://<eureka service>:8761/eureka/apps/<APPID>
    例如,要查看注册表中的组织服务,可以访问http://localhost:8761/eureka/apps/organizationservice
    Eureka服务返回的默认格式是XML。Eureka还可以将图4-5中的数据作为JSON净荷返回,但是必须将HTTP首部Accept设置为application/json。图4-6展示了一个JSON净荷的例子。
图4-5 调用Eureka REST API来查看组织服务,返回结果将展示在Eureka中注册的 服务实例的IP地址以及服务状态
图4-6 调用Eureka REST API,以JSON格式返回调用结果
    
 
在Eureka和服务启动时要保持耐心
    当服务通过Eureka注册时,Eureka将在30 s内等待3次连续的健康检查,然后才能通过Eureka获取该服务。这个热身过程让开发者们感到疑惑,因为如果他们在服务启动后立即调用他们的服务,他们会认为Eureka还没有注册他们的服务。这一点在Docker环境运行的代码示例中很明显,因为Eureka服务和应用程序服务(许可证服务和组织服务)都是在同一时间启动的。请注意,在启动应用程序后,尽管服务本身已经启动,读者可能会收到关于未找到服务的404错误。等待30 s,然后再尝试调用服务。
    在生产环境中,Eureka服务已经在运行,如果读者正在部署现有的服务,那么旧服务仍然可以用于接收请求。
    4.5 使用服务发现来查找服务
    现在已经有了通过Eureka注册的组织服务。我们还可以让许可证服务调用该组织服务,而不必直接知晓任何组织服务的位置。许可证服务将通过Eureka来查找组织服务的实际位置。
    
为了达成我们的目的,我们将研究3个不同的Spring/Netflix客户端库,服务消费者可以使用它们来和Ribbon进行交互。从最低级别到最高级别,这些库包含了不同的与Ribbon进行交互的抽象层次。这里将要探讨的库包括:
    Spring DiscoveryClient;
    启用了RestTemplate的Spring DiscoveryClient;
    Netflix Feign客户端。
    
本章将介绍这些客户端,并在许可证服务的上下文中介绍它们的用法。在开始详细介绍客户端的细节之前,我在代码中编写了一些便利的类和方法,以便读者可以使用相同的服务端点来处理不同的客户端类型。
    
首先,我修改了src/main/java/com/thoughtmechanix/licenses/controllers/LicenseServiceController.java以包含许可证服务的新路由。这个新路由允许指定要用于调用服务的客户端的类型。
 
这是一个辅助路由,因此,当我们探索通过Ribbon调用组织服务的各种不同方法时,可以通过单个路由来尝试每种机制。LicenseServiceController类中新路由的代码
如代码清单4-5所示。
    
代码清单4-5 使用不同的REST客户端调用许可证服务
 
@RequestMapping(value="/{licenseId}/{clientType}", method = RequestMethod.GET) 
⇽---  clientType确定Spring REST要使用的客户端的类型
public License getLicensesWithClient(  @PathVariable("organizationId") String organizationId,  @PathVariable("licenseId") String licenseId,  @PathVariable("clientType") String clientType) {     
return licenseService.getLicense(organizationId, licenseId, clientType); 
}
    
在上述代码中,该路由上传递的clientType参数决定了我们将在代码示例中使用的客户端类型。可以在此路由上传递的具体类型包括:
    Discovery——使用DiscoveryClient和标准的Spring RestTemplate类来调用组织服务;
    Rest——使用增强的Spring RestTemplate来调用基于Ribbon的服务;
 
Feign——使用Netflix的Feign客户端库来通过Ribbon调用服务。
    
注意
因为我对这3种类型的客户端使用同一份代码,所以读者可能会看到代码中出现某些客户端的注解,即使在某些情况下并不需要它们。例如,读者可以在代码中同时看到@EnableDiscoveryClient和@EnableFeignClients注解,即使运行的代码只解释了其中一种客户端类型。通过这种方式,我就可以为我的示例共用一份代码。我会在遇到它们的时候指出这些冗余和代码。
    
src/main/java/com/thoughtmechanix/licenses/services/LicenseService.java中的LicenseService类添加了一个名为retrieveOrgInfo()的简单方法,该方法将根据传递到路由的clientType类型进行解析,以用于查找组织服务实例。LicenseService类上的getLicense()方法将使用retrieveOrgInfo()方法从Postgres数据库中检索组织数据。代码清单4-6展示了getLicense()方法。
    
代码清单4-6 getLicense()方法将使用多个方法来执行REST调用
 
public License getLicense(String organizationId, String licenseId, String clientType) {    
License license = licenseRepository.findByOrganizationIdAndLicenseId(      organizationId, licenseId);     
Organization org = retrieveOrgInfo(organizationId, clientType);     
return license .withOrganizationName( org.getName())    .withContactName( org.getContactName())        .withContactEmail( org.getContactEmail())        .withContactPhone( org.getContactPhone() )         
.withComment(config.getExampleProperty()); 
}
    
读者可以在licensing-service源代码的src/main/java/com/thoughtmechanix/licenses/clients包中找到使用Spring DiscoveryClient、Spring RestTemplate或Feign库构建的客户端。
    
4.5.1 使用Spring DiscoveryClient查找服务实例
    
Spring DiscoveryClient提供了对Ribbon和Ribbon中缓存的注册服务的最低层次访问。使用DiscoveryClient,可以查询通过Ribbon注册的所有服务以及这些服务对应的URL。
    
接下来,我们将创建一个简单的示例,使用DiscoveryClient从Ribbon中检索组织服务URL,然后使用标准的RestTemplate类调用该服务。要开始使用DiscoveryClient,需要先使用@EnableDiscoveryClient注解来标注src/main/java/com/thoughtmechanix/ licenses/Application.java中的Application类,如代码清单4-7所示。
    
代码清单4-7 创建引导类以使用Spring Discovery Client
    
@SpringBootApplication 
@EnableDiscoveryClient ⇽激活Spring DiscoveryClient
@EnableFeignClients ⇽现在忽略这个注解,本章稍后将进行介绍 
public class Application {     
public static void main(String[] args) {         SpringApplication.run(Application.class, args);    
 } 
}
    
@EnableDiscoveryClient注解是Spring Cloud的触发器,其作用是使应用程序能够使用DiscoveryClient和Ribbon库。现在可以忽略@EnableFeignClients注解,因为本章稍后就会介绍它。
    
如代码清单4-8所示,我们现在来看看如何通过Spring DiscoveryClient调用组织服务。读者可以在src/main/java/com/thoughtmechanix/licenses/OrganizationDiscoveryClient.java中找到这段代码。
    
代码清单4-8 使用DiscoveryClient查找信息
    /*为了简洁,省略了package和import部分*/ @Component 
public class OrganizationDiscoveryClient {     
@Autowired     
private DiscoveryClient discoveryClient;
 
public Organization getOrganization(String organizationId) {        
RestTemplate restTemplate = new RestTemplate();         
List<ServiceInstance> instances =         discoveryClient.getInstances("organizationservice");  ⇽---  获取组织服务的所有实例的列表         
if (instances.size()==0) return null;         
String serviceUri = String.format("%s/v1/organizations/%s",        instances.get(0).getUri().toString(),          organizationId);  ⇽---  检索要调用的服务端点         
ResponseEntity<Organization> restExchange =  
⇽-使用标准的Spring REST 模板类去调用服务         restTemplate.exchange(serviceUri,HttpMethod.GET,       null, Organization.class, organizationId);         
 return restExchange.getBody();     
}
 
在这段代码中,我们首先感兴趣的是DiscoveryClient。这是用于与Ribbon交互的类。要检索通过Eureka注册的所有组织服务实例,可以使用getInstances()方法传入要查找的服务的关键字,以检索ServiceInstance对象的列表。
    
ServiceInstance类用于保存关于服务的特定实例(包括它的主机名、端口和URI)的信息。
    
在代码清单4-8中,我们使用列表中的第一个ServiceInstance去构建目标URL,此URL可用于调用服务。一旦获得目标URL,就可以使用标准的Spring RestTemplate来调用组织服务并检索数据。
    
DiscoveryClient与实际运用
    通过介绍DiscoveryClient,我完成了使用Ribbon来构建服务消费者的过程。然而,在实际运用中,只有在服务需要查询Ribbon以了解哪些服务和服务实例已经通过它注册时,才应该直接使用DiscoveryClient。上述代码存在以下几个问题。
    
没有利用Ribbon的客户端负载均衡——尽管通过直接调用DiscoveryClient可以获得服务列表,但是要调用哪些返回的服务实例就成为了开发人员的责任。开发人员做了太多的工作——现在,开发人员必须构建一个用来调用服务的URL。尽管这是一件小事,但是编写的代码越少意味着需要调试的代码就越少。
    
善于观察的Spring开发人员可能已经注意到,上述代码中直接实例化了RestTemplate类。这与正常的Spring REST调用相反,通常情况下,开发人员会利用Spring框架,通过@Autowired注解将RestTemplate注入使用RestTemplate的类中。
 
代码清单4-8实例化了RestTemplate类,这是因为一旦在应用程序类中通过@EnableDiscoveryClient注解启用了Spring DiscoveryClient,由Spring框架管理的所有RestTemplate都将注入一个启用了Ribbon的拦截器,这个拦截器将改变使用RestTemplate类创建URL的行为。直接实例化RestTemplate类可以避免这种行为。
    总而言之,有更好的机制来调用支持Ribbon的服务。
在第四章的organization-service和licensing-service例子里的bootstrap.yml里都有:
spring.cloud.config.enabled的值为true,如下所示:
spring:
  application:
    name: organizationservice
  profiles:
    active:
      default
  cloud:
    config:
      enabledtrue
如果spring.cloud.config.enabled的值为true,则表示该微服务使用spring cloud config拉取应用程序配置,
而默认的spring cloud config微服务所在的uri为:http://localhost:8888.
当启动organization-service和licensing-service时,会看到控制台日志输出如下:
Fetching config from server at: http://localhost:8888
 
这里再补充一点,对于某个微服务,如果其pom文件中有包含对postsql库的依赖,但应用程序配置里却没有数据库配置,则不会创建到
postsql的数据接连接池。
数据库配置如:
spring.datasource.platform:  "postgres"
spring.jpa.show-sql: "true"
spring.database.driverClassName: "org.postgresql.Driver"
spring.datasource.username: "postgres"
spring.datasource.password: "{cipher}4788dfe1ccbe6485934aec2ffeddb06163ea3d616df5fd75be96aadd4df1da91"
spring.datasource.testWhileIdle: "true"
spring.datasource.validationQuery: "SELECT 1"
spring.jpa.properties.hibernate.dialect: "org.hibernate.dialect.PostgreSQLDialect"
原文地址:https://www.cnblogs.com/mongotea/p/11973382.html