Spring Cloud Eureka

https://github.com/carnellj/spmia-chapter4

服务发现对于微服务和基于云的应用程序至关重要,主要原因有两个:

  • 通过服务发现,服务消费者能够将服务的物理位置抽象出来

  • 由于服务消费者不知道实际服务实例的物理位置,因此可以从可用服务池中添加或移除服务实例

在非云的世界中,应用程序定位资源物理位置服务的解析通常由DNS和网络负载均衡器的组合来解决。

在这里插入图片描述

这种模型适用于在企业数据中心内部运行的应用程序,以及在一组静态服务器上运行少量服务的情况,但对基于云的微服务应用程序来说,这种模式并不适用。原因有以下几个:

  • 单点故障:负载均衡器出现故障,依赖它的应用程序也会出现故障,是集中式阻塞点

  • 有限的水平可伸缩性:在服务集中到单个负载均衡器集群的情况下,跨多个服务器水平伸缩负载均衡基础设施的能力有限

  • 静态管理:大多数传统的负载均衡器不是为快速注册和注销服务设计的。它们使用集中式数据库来存储规则的路由,添加新路由的唯一方法通常是通过供应商的专有API来进行添加

  • 复杂:由于负载均衡器充当服务的代理,它必须将服务消费者的请求映射到物理服务

在云中的服务发现

在这里插入图片描述

在这个模型中,当服务消费者需要调用一个服务时:

  • 它将联系服务发现服务,获取它请求的所有服务实例,然后在服务消费者的机器上本地缓存数据

  • 每当客户端需要调用该服务时,服务消费者将从缓存中查找该服务的位置信息。通常客户端缓存将使用简单的负载均衡算法,如“轮询”负载均衡算法,以确保服务调用分布在多个服务实例之间

  • 然后,客户端将定期与服务发现进行联系,并刷新服务实例的缓存。客户端缓存最终是一致的,但是始终存在这样的风险:在客户端联系服务发现实例以进行刷新和调用时,调用可能会被定向到不健康的服务实例上

  • 如果在调用服务的过程中,服务调用失败,那么本地的服务发现缓存失效,服务发现客户端将尝试从服务发现代理刷新数据

Spring Eureka服务

一、Eureka Server

1、pom.xml添加Eureka server依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

2、application.yml配置Eureka server

	eureka:
  		client:
      		fetch-registry: false
      		register-with-eureka: false
  		server:
    		wait-time-in-ms-when-sync-empty: 
	server:
  		port: 8761

参数说明:

  • server.port:设置Eureka服务默认端口,默认端口就是8761

  • eureka.client.register-with-eureka:告知服务,在Spring Boot Eureka应用程序启动时不要通过Eureka服务注册,因为它本身就是Eureak服务(因为这里是Eureka Server,不需要将自己注册进去,只提供Eureka Client被发现)

  • euraka.client.fetch-registry:设置为false以便Eureka服务启动时,它不糊尝试在本地缓存注册表信息(提供给Euraka Client服务消费者就需要本地缓存,方便服务消费者下次访问同一个服务时,从本地缓存直接定位,而不需要再走服务发现)

  • eureka.client.server.wait-time-in-ms-when-sync-empty:Eureka在启动时不会马上通告任何通过它注册的服务,默认情况下它会等待5min,让所有的服务都有机会在通告之前通过它来注册。进行本地测试时添加该属性,将有助于加快Eureka服务启动和显示通过它来注册服务所需的时间

每次服务注册需要30s的时间才能显示在Eureka服务中,因为Eureka需要从服务接收3此连续心跳包ping,每次心跳包ping间隔10s,然后才能使用这个服务

3、Application添加注解启动Eureka Server

@SpringBootApplication
@EnableEurekaServer  // 添加注解启动Eureka Server
public class EurekaServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}
}

4、测试访问

http://localhost:8761

二、Eureka Client

1、pom.xml添加Eureka client依赖

<dependency>
	<groudId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

2、application.yml配置Eureka client

	spring:
		application:
			name: organizationservice
		profile:
			active:
				default
		coud:
			config:
				enabled: true
	eureka:
  		client:
    		fetch-registry: true
    		register-with-eureka: true
    		service-url:
      			defaultZone: http://localhost:8761/eureka
  		instance:
    		prefer-ip-address: true

参数说明:

  • eureka.instance.prefer-ip-address:在默认情况下,Eureka在尝试注册服务时,将会使用主机名让外界与它进行联系。这种方式在基于服务器的环境中运行良好,在这样的环境中,服务会被分配一个DNS支持的主机名。但是,在基于容器的部署(如Docker)中,容器将以随机生成的主机名启动,并且该容器没有DNS记录。

    如果没有将 eureka.instance.prefer-ip-address 设置为true,那么客户端应用程序将无法正确地解析主机名的位置,因为该容器不存在DNS记录

  • spring.application.name:使用Eureka注册的服务名称,当该服务注册到Eureka server时,显示的就是该名称;通常该属性会放在bootstrap.yml中,这里为了测试直接放在这里

  • eureka.client.register-with-eureka:设置Eureka启动的时候注册到Eureka server

  • eureka.client.fetch-registry:注册到Eureka server后,服务消费者访问服务时缓存该服务位置,下次访问时直接使用缓存

  • eureka.server-url.defaultZone:Eureka client注册到Eureka server的ip地址

每个通过Eureka注册的服务都会两个与之相关的组件:应用程序ID和实例ID。应用程序ID始终是由spring.application.name属性设置;实例ID是一个随机数,用于代表单个服务实例。

3、激活Eureka client

@SpringBootApplication
@EnableDiscoveryClient // 激活Eureka client,使应用程序能够使用DiscoveryClient和Ribbon库
public class EurekaClientApplication {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

4、测试访问Eureka client

http:localhost:8761/eureka/organizationservice

5、代码测试

@GetMapping("/{licenseId}/{clientType}")
public License getLicensesWithClient(
		@PathVariable("organizationId") String organizationId,
		@PathVariable("licenseId) String licenseId, 
		@PathVariable("clientType") String clientType) {
	return licenseService.getLicense(organizationId, licenseId, clientType);
}

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())
			.witchComment(config.getExampleProperty());
}
  • 使用DiscoveryClient查找信息(该方式存在问题:没有利用Ribbon负载均衡;开发人员做了太多工作,要硬编码调用的URL地址访问)
@Component
public class OrganizationDiscoveryClient {

	// DiscoveryClient是用于与Ribbon交互的类
	// DiscoveryClient在实际运用中,只有在服务需要查询Ribbon以了解哪些服务和服务实例已经通过它注册时,才应该直接使用
	@Autowired
	private DiscoveryClient discoveryClient;

	public Organization getOrganization(String organizationId) {
		// 这里实例化了RestTemplate而不是通过注解的原因:
		// 一旦应用程序通过@EnableDiscoveryClient注解启用了Spring DiscoveryClient
		// 有Spring管理的RestTemplate都将注入一个启动了Ribbon的拦截器,拦截器将改变使用RestTemplate类创建URL的行为
		// 直接实例化能避免这种行为
		RestTemplate restTemplate = new RestTemplate();
		// ServiceInstance类用于保存关于服务的特定实例(比如主机名、端口和URL)
		List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice"));
		if (instances.size == 0) return null;
		
		// 构建访问服务的URL
		String serviceUri = String.format("%s/v1/organizations/%s", 
								instances.get(0).getUri().toString(), 
								organizationId);

		// 路由到要访问的服务
		ResponseEntity<Organization> restExchange = restTemplate.exchange(
														serviceUri, 
														HttpMethod.GET, 
														null, 
														Organization.class, 
														organizationId);

		return restExchange.getBody();
	}
}
  • 带有Ribbon功能的RestTemplate(因为有Ribbon,所以具备负载均衡,访问该服务后会有缓存)
@SpringBootApplication
public class EurekaClientApplication {

	// @LoadBalanced注解告诉Spring Cloud创建一个支持Ribbon的RestTemplate
	@LoadBalanced
	@Bean
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}

	public static void main(String[] args) {
		SpringApplication.run(EurekaClientApplication.class, args);
	}
}

@Component
public class OrganizationRestTemplateClient {
	
	@Autowired
	RestTemplate restTemplate;
	
	pulbic Organization getOrganization(String organizationId) {
		// http://organizationservice/v1/organizations/{organizationId}
		// 格式:http://{applicationId}/v1/organizations/{organizationId}
		// 启用Ribbon的RestTemplate将解析传递给它的URL,并使用传递的内容作为服务器名称,该服务器名称作为从Ribbon查询服务实例的键
		ResponseEntity<Organization> restExchange = restTemplate.exhange(
				"http://organizationservcie/v1/organizations/{organizationId}",
				HttpMethod.GET,
				null,
				Organization.class,
				organizationId);

		return restExchange.getBody();
	}
}
  • Feign调用服务
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

@SpringBootApplication
@EnableFeignClients // 添加Feign
public class EurekaClientApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaClientApplication.class, args);
	}
}

@FeignClient("organizationservice") // 要访问服务的application name
public interface OrganizationFeignClient {

	// 定义要访问的服务接口,被访问的接口也应该有一个该方法
	@RequestMapping(method=RequestMethod.GET, 
					value="/v1/organizations/{organizationId}", 
					consumes="application/json")
	Organization getOrganization(@PathVariable("organizationId") String organizationId);
}


@Component
public class OrganizationClient {

	@Autowired
	private OrganizationFeignClient client;

	public Organization getOrganization(String organizationId) {
		return client.getOrganization(organizationId);
	}
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值