为了开始讨论服务发现架构,我们需要了解4个概念。这些一般概念在所有服务发现实现中是共通的。
图4-2 展示了这4 个概念的流程,以及在服务发现模式实现中通常发生的情况。
当服务消费者需要调用一个服务时:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
要设置的关键属性是server.port 属性,它用于设置Eureka 服务的默认端口。eureka.client.registerWithEureka属性会告知服务,在Spring Boot Eureka应用程序启动时不要通过Eureka服务注册,因为它本身就是Eureka服务。eureka.client. fetchRegistry属性设置为false,以便Eureka 服务启动时,它不会尝试在本地缓存注册表信息。在运行Eureka 客户端时,为了缓存通过Eureka 注册的Spring Boot服务,我们需要更改eureka.client.fetchRegistry的值。
server:
port: ${PORT:50101} #服务端口
spring:
application:
name: eureka_server #指定服务名
eureka:
client:
registerWithEureka: false #服务注册,是否将自己注册到Eureka服务中
fetchRegistry: false #服务发现,是否从Eureka中获取注册信息
server:
wait-time-in-ms-when-sync-empty: 5
只需要使用一个新的注解@EnableEurekaServer,就可以让我们的服务成为一个Eureka服务。
@EnableEurekaServer //标识此工程是一个EurekaServer
@SpringBootApplication
public class GovernCenterApplication {
public static void main(String[] args) {
SpringApplication.run(GovernCenterApplication.class,args);
}
}
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
每个通过Eureka注册的服务都会有两个与之相关的组件: 应用程序ID和实例ID 。应用程序ID用于表示一组服务实例。在基于Spring Boot 的微服务中,应用程序ID始终是由spring.application.name 属性设置的值。实例ID是一个随机数,用于代表单个服务实例。一个服务可以开多个实例,每个实例都使用相同的服务名。
在默认情况下,Eureka 在尝试注册服务时,将会使用主机名让外界与它进行联系。这种方式在基于服务器的环境中运行良好,在这样的环境中,服务会被分配一个DNS支持的主机名。但是,在基于容器的部署(如Docker)中,容器将以随机生成的主机名启动,并且该容器没有DNS记录。如果没有将eureka.instance.preferipAddress 设置量为true,那么客户端应用程序将无法正确地解析主机名的位置,因为该容器不存在DNS记录。设置preferipAddress属性将通知Eureka服务,客户端想要通过IP地址进行通告。
server:
port: ${PORT:31001}
spring:
application:
name: helloworld
eureka:
client:
registerWithEureka: true #服务注册开关
fetchRegistry: true #服务发现开关
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/}
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: ${IP_ADDRESS:127.0.0.1}
instance-id: ${spring.application.name}:${server.port} #指定实例id
package com.hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Hello {
@RequestMapping("/hello")
public String sayHello(){
return "hello";
}
}
访问Eurake服务,查看注册的helloworld服务
有3个不同的Spring/Netflix客户端库:
在启动类中添加@EnableDiscoveryClient注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
DiscoveryClient是与Ribbon交互的类。要检索通过Eureka注册的所有服务实例,可以使用getInstances( )方法传入要查找的服
务的关键字,以检索Service Instance对象的列表。
@Component
public class HelloworldDiscoveryClient {
@Autowired
private DiscoveryClient discoveryClient;
public String callHello() {
RestTemplate restTemplate = new RestTemplate();
List instances =
discoveryClient.getInstances("helloworld");
if (instances.size() == 0) {
return null;
}
String serviceUri =
String.format("%s/hello",
instances.get(0).getUri().toString());
String forObject = restTemplate.getForObject(
serviceUri, String.class);
return forObject;
}
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestDiscoveryClient {
@Autowired
HelloworldDiscoveryClient helloworldDiscoveryClient;
@Test
public void discoveryTest(){
helloworldDiscoveryClient.callHello();
}
}
在实际运用中,只有在服务需要查询Ribbon以了解哪些服务和服务实例巳经通过它注册时,才应该直接使用DiscoveryClient。上述代码存在以下几个问题。
为了解决以上问题,我们可以直接使用带有Ribbon功能的Spring RestTemplate调用服务
要使用带有Ribbon 功能的Rest Template 类,需要使用Spring Cloud注解@LoadBalanced来定义RestTemplate bean的构造方法。
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
URL比较特殊:URL中的服务器名称与通过Eureka 注册的组织服务的应用程序ID相同。
@Component
public class HelloworldRestTemplateClient {
@Autowired
private RestTemplate restTemplate;
public String callHello() {
String serviceUri = "http://helloworld/hello";
String forObject = restTemplate.getForObject(
serviceUri, String.class);
return forObject;
}
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestRestTemplateClient {
@Autowired
HelloworldRestTemplateClient restTemplateClient;
@Test
public void discoveryTest(){
restTemplateClient.callHello();
}
}
Feign库采用不同的方法来调用REST服务,方法是让开发人员首先定义一个Java 接口,然后使用Spring Cloud 注解来标注接口, 以映射Ribbon将要调用的基于Eureka的服务。Spring Cloud 框架将动态生成一个代理类, 用于调用目标REST服务。除了编写接口定义,开发人员不需要编写其他调用服务的代码。
推荐使用这个方法来发现服务。
org.springframework.cloud
spring-cloud-starter-openfeign
添加新的注解@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
我们通过使用@FeignClient 注解来开始这个Feign 示例,并将这个接口代表的服务的应用程序ID传递给它。接下来,在这个接口中定义一个callHello()方法,该方法可以由客户端调用以触发组织服务。注意:该客户端利用了Spring MVC的注解@RequestMapping。
@FeignClient("helloworld")
public interface HelloworldFeignClient {
@RequestMapping("/hello")
String callHello();
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestFeignClient {
@Autowired
HelloworldFeignClient feignClient;
@Test
public void discoveryTest(){
String hello = feignClient.callHello();
System.out.println(hello);
}
}