Spring Cloud微服务开发笔记1——Eureka实战

关于SpringCloud的整体组成和架构,本来打算作为本系列开篇的,但今天兴致不高,这种宏观的东西哪天再补写一篇吧。

Spring Cloud 集成了 Netflix OSS 的多个项目, 形成了 spring-cloud-netflix 项目, 该项目包含了多个子模块, 这些子模块对集成的 Netflix 旗下框架进行了封装。

本文打算直蹦内容,先从Netflix框架中的Eureka开始介绍。Eureka是spring-cloud-netflix 项目中的服务管理框架。

Eureka是作什么用的呢?如果你了解和使用过Zookeeper,可以先把它当做ZK的同类框架吧。

Eureka分别提供了服务器端和客户端,通过REST进行通信,实现微服务集群中服务的管理。使用Eureka框架可以将业务组件注册到Eureka服务器上面。Eureka会自动维护服务列表,并自动检查它们的状态。

让我们来看一个很小型的微服务集群架构:

架构分为服务器和客户端,客户端又分为服务提供者和服务调用者。客户端向Eureka服务器进行两个操作:一个是进行注册(服务提供者客户端会将自己的服务注册),并获取服务器上已经注册的信息(服务列表)。然后服务调用者获取注册信息之后,会自行决定在什么时候调用谁的服务,不再需要通过Eureka服务器。

打个比方来说,Eureka就好比114平台,服务提供者客户端就好比自来水公司、派出所、煤气公司等服务提供机构,他们会将官方联系电话注册到114平台,服务调用者客户端就好比我们自己,打电话给114查询这些机构的电话。然后独自通过这些查询来的机构电话号码,向对应的机构拨打电话请求服务。至于查电话号码之后,什么时候打,打给谁,不再需要114来管,我们自己决定就好。

构建一个Eureka服务

需要三步构建:

建立服务器端

建立服务提供者

建立服务调用者

下面我们尝试构建以上三个端应用。

建立EUREKA服务器端

我们通过STS的spring starter来构建一个springboot项目作为eureka服务器端。

具体配置如下:

我们选择springboot版本为1.5.10,由于2.0刚刚才正式发布,前端时间的反复改动让我对新版本仍心有余悸,我们还是选在支持度和稳定性更好的1.5吧。

注意在spring starter构建向导里,我们可以选择必要的组件依赖。

这里对于一个Eureka服务器端而言,自然要搜索勾选Eureka Server。一会儿项目构建好之后,你就可以看到pom文件中对应已经将eureka server依赖配置了。

下一步不同管,指明的是springboot项目构建的模板来源等信息。STS会自动从这里配置的网址去获取对应的项目模板。

点击完成。如果你是第一次构建项目,可能需要同步下载很多jar包,构建项目的时间可能会有点长。

构建好后,我们可以看到整个springboot的maven项目结构了。

我们看一眼pom,eureka server等依赖已经为我们配置好了。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.happybks</groupId>
	<artifactId>eurekaServer</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>eurekaServer</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.10.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Edgware.SR2</spring-cloud.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

项目的启动类,默认生成如下,一个典型的springboot启动类:

package com.happybks.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class EurekaServerApplication {

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

如果你没有用STS的spring starter构建工具,而是自己通过maven构建eureka服务应用,可以参见官方文档:

http://cloud.spring.io/spring-cloud-static/Dalston.SR5/single/spring-cloud.html#spring-cloud-eureka-server

12.1 How to Include Eureka Server

To include Eureka Server in your project use the starter with group org.springframework.cloud and artifact id spring-cloud-starter-eureka-server. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.

12.2 How to Run a Eureka Server

Example eureka server

@SpringBootApplication
@EnableEurekaServer
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

The server has a home page with a UI, and HTTP API endpoints per the normal Eureka functionality under /eureka/*.

--------------------------------------------------------------------------------------------------

这里,STS默认构建的项目配置文件是properties文件。我们还是把它改成springboot推荐的yml配置文件吧。

我们按照官方文档中介绍的方法修改项目的启动类,加上@EnableEurekaServer注解。main方法中的也按照web(true)配置。

package com.happybks.demo;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(EurekaServerApplication.class).web(true).run(args);
    }
}

然后我们启动应用。(运行时选择按照spring boot app启动)

但是我们发现,启动Eureka服务的时候报错了:

原因是Eureka默认将自己也作为eureka客户端向自己注册。

解决办法是,修改一个配置项:

我们查询文档,看到这个配置项:默认是true,我们把它配置成false

eureka.client.register-with-eureka true

Indicates whether or not this instance should register its information with eureka server for discovery by others.

In some cases, you do not want your instances to be discovered whereas you just want do discover other instances.

我们在appliation.yml中加入:

eureka:
  client:
    register-with-eureka: false

客户端默认会将自己注册到本地的8761的端口上,因此这里推荐再配置一个server.port为8761。

另外还有一个选项,客户端向eureka服务器获取注册信息,默认也是true

eureka.client.fetch-registry true Indicates whether this client should fetch eureka registry information from eureka server.

为了避免这个eureka服务又将自己当成客户端 向自己请求获取eureka上的注册信息,我们也将这个配置设置成false

所以综上所述,eureka服务器端的app需要加上一下配置,请大家拿个小本儿记一下:)

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

然后我们再启动这个eureka服务器应用。就没问题了。我把控制台输出的也复制黏贴出来,可以看到这个应用启动和加载的过程。请留意一下倒数几行,eureka服务自动更新端口为8761

2018-03-03 17:28:30.736  INFO 36724 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@67a20f67: startup date [Sat Mar 03 17:28:30 GMT+08:00 2018]; root of context hierarchy
2018-03-03 17:28:30.947  INFO 36724 --- [           main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-03-03 17:28:30.992  INFO 36724 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'configurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$d2ac3242] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v1.5.10.RELEASE)

2018-03-03 17:28:32.681  INFO 36724 --- [           main] c.happybks.demo.EurekaServerApplication  : No active profile set, falling back to default profiles: default
2018-03-03 17:28:32.700  INFO 36724 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@517d4a0d: startup date [Sat Mar 03 17:28:32 GMT+08:00 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@67a20f67
2018-03-03 17:28:33.483  INFO 36724 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=b66a293a-7c49-39e2-82e8-f02d93e67ffe
2018-03-03 17:28:33.505  INFO 36724 --- [           main] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-03-03 17:28:33.571  INFO 36724 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.netflix.metrics.MetricsInterceptorConfiguration$MetricsRestTemplateConfiguration' of type [org.springframework.cloud.netflix.metrics.MetricsInterceptorConfiguration$MetricsRestTemplateConfiguration$$EnhancerBySpringCGLIB$$e8bed586] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-03-03 17:28:33.579  INFO 36724 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$d2ac3242] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-03-03 17:28:33.841  INFO 36724 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8761 (http)
2018-03-03 17:28:33.852  INFO 36724 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-03-03 17:28:33.853  INFO 36724 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.27
2018-03-03 17:28:33.966  INFO 36724 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-03-03 17:28:33.966  INFO 36724 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1267 ms
2018-03-03 17:28:34.506  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'metricsFilter' to: [/*]
2018-03-03 17:28:34.506  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-03-03 17:28:34.506  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-03-03 17:28:34.506  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-03-03 17:28:34.506  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-03-03 17:28:34.507  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'webRequestTraceFilter' to: [/*]
2018-03-03 17:28:34.507  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'servletContainer' to urls: [/eureka/*]
2018-03-03 17:28:34.507  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'applicationContextIdFilter' to: [/*]
2018-03-03 17:28:34.507  INFO 36724 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-03-03 17:28:34.564  INFO 36724 --- [ost-startStop-1] c.s.j.s.i.a.WebApplicationImpl           : Initiating Jersey application, version 'Jersey: 1.19.1 03/11/2016 02:08 PM'
2018-03-03 17:28:34.642  INFO 36724 --- [ost-startStop-1] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
2018-03-03 17:28:34.643  INFO 36724 --- [ost-startStop-1] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
2018-03-03 17:28:34.722  INFO 36724 --- [ost-startStop-1] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
2018-03-03 17:28:34.722  INFO 36724 --- [ost-startStop-1] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
2018-03-03 17:28:35.117  WARN 36724 --- [           main] o.s.c.n.a.ArchaiusAutoConfiguration      : No spring.application.name found, defaulting to 'application'
2018-03-03 17:28:35.120  WARN 36724 --- [           main] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2018-03-03 17:28:35.120  INFO 36724 --- [           main] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2018-03-03 17:28:35.124  WARN 36724 --- [           main] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2018-03-03 17:28:35.124  INFO 36724 --- [           main] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2018-03-03 17:28:35.374  INFO 36724 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@517d4a0d: startup date [Sat Mar 03 17:28:32 GMT+08:00 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@67a20f67
2018-03-03 17:28:35.457  INFO 36724 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-03-03 17:28:35.458  INFO 36724 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-03-03 17:28:35.462  INFO 36724 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/],methods=[GET]}" onto public java.lang.String org.springframework.cloud.netflix.eureka.server.EurekaController.status(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.Object>)
2018-03-03 17:28:35.463  INFO 36724 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/lastn],methods=[GET]}" onto public java.lang.String org.springframework.cloud.netflix.eureka.server.EurekaController.lastn(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.Object>)
2018-03-03 17:28:35.494  INFO 36724 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-03 17:28:35.494  INFO 36724 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-03 17:28:35.535  INFO 36724 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-03-03 17:28:37.157  INFO 36724 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2018-03-03 17:28:38.667  INFO 36724 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2018-03-03 17:28:39.024  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/refresh || /refresh.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
2018-03-03 17:28:39.024  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/service-registry/instance-status],methods=[GET]}" onto public org.springframework.http.ResponseEntity org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint.getStatus()
2018-03-03 17:28:39.024  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/service-registry/instance-status],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint.setStatus(java.lang.String)
2018-03-03 17:28:39.026  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/loggers/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.get(java.lang.String)
2018-03-03 17:28:39.026  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)
2018-03-03 17:28:39.026  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/loggers || /loggers.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.027  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.027  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/health || /health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest,java.security.Principal)
2018-03-03 17:28:39.027  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/auditevents || /auditevents.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public org.springframework.http.ResponseEntity<?> org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint.findByPrincipalAndAfterAndType(java.lang.String,java.util.Date,java.lang.String)
2018-03-03 17:28:39.027  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/features || /features.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.030  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.030  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.031  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/archaius || /archaius.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.031  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/heapdump || /heapdump.json],methods=[GET],produces=[application/octet-stream]}" onto public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException
2018-03-03 17:28:39.032  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.032  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2018-03-03 17:28:39.032  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env || /env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.033  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2018-03-03 17:28:39.033  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.034  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.value(java.util.Map<java.lang.String, java.lang.String>)
2018-03-03 17:28:39.034  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/reset],methods=[POST]}" onto public java.util.Map<java.lang.String, java.lang.Object> org.springframework.cloud.context.environment.EnvironmentManagerMvcEndpoint.reset()
2018-03-03 17:28:39.034  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/info || /info.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.034  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.035  INFO 36724 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-03-03 17:28:39.118  INFO 36724 --- [           main] o.s.ui.freemarker.SpringTemplateLoader   : SpringTemplateLoader for FreeMarker: using resource loader [org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@517d4a0d: startup date [Sat Mar 03 17:28:32 GMT+08:00 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@67a20f67] and template loader path [classpath:/templates/]
2018-03-03 17:28:39.119  INFO 36724 --- [           main] o.s.w.s.v.f.FreeMarkerConfigurer         : ClassTemplateLoader for Spring macros added to FreeMarker configuration
2018-03-03 17:28:39.250  INFO 36724 --- [           main] o.s.c.n.eureka.InstanceInfoFactory       : Setting initial instance status as: STARTING
2018-03-03 17:28:39.281  INFO 36724 --- [           main] com.netflix.discovery.DiscoveryClient    : Initializing Eureka in region us-east-1
2018-03-03 17:28:39.281  INFO 36724 --- [           main] com.netflix.discovery.DiscoveryClient    : Client configured to neither register nor query for data.
2018-03-03 17:28:39.285  INFO 36724 --- [           main] com.netflix.discovery.DiscoveryClient    : Discovery Client initialized at timestamp 1520069319285 with initial instances count: 0
2018-03-03 17:28:39.333  INFO 36724 --- [           main] c.n.eureka.DefaultEurekaServerContext    : Initializing ...
2018-03-03 17:28:39.335  WARN 36724 --- [           main] c.n.eureka.cluster.PeerEurekaNodes       : The replica size seems to be empty. Check the route 53 DNS Registry
2018-03-03 17:28:39.344  INFO 36724 --- [           main] c.n.e.registry.AbstractInstanceRegistry  : Finished initializing remote region registries. All known remote regions: []
2018-03-03 17:28:39.344  INFO 36724 --- [           main] c.n.eureka.DefaultEurekaServerContext    : Initialized
2018-03-03 17:28:39.355  WARN 36724 --- [           main] arterDeprecationWarningAutoConfiguration : spring-cloud-starter-eureka-server is deprecated as of Spring Cloud Netflix 1.4.0, please migrate to spring-cloud-starter-netflix-eureka-server
2018-03-03 17:28:39.407  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-03-03 17:28:39.418  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'configurationPropertiesRebinder' has been autodetected for JMX exposure
2018-03-03 17:28:39.418  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'refreshEndpoint' has been autodetected for JMX exposure
2018-03-03 17:28:39.419  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'environmentManager' has been autodetected for JMX exposure
2018-03-03 17:28:39.420  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'serviceRegistryEndpoint' has been autodetected for JMX exposure
2018-03-03 17:28:39.421  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'refreshScope' has been autodetected for JMX exposure
2018-03-03 17:28:39.424  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'environmentManager': registering with JMX server as MBean [org.springframework.cloud.context.environment:name=environmentManager,type=EnvironmentManager]
2018-03-03 17:28:39.439  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'serviceRegistryEndpoint': registering with JMX server as MBean [org.springframework.cloud.client.serviceregistry.endpoint:name=serviceRegistryEndpoint,type=ServiceRegistryEndpoint]
2018-03-03 17:28:39.446  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'refreshScope': registering with JMX server as MBean [org.springframework.cloud.context.scope.refresh:name=refreshScope,type=RefreshScope]
2018-03-03 17:28:39.454  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'configurationPropertiesRebinder': registering with JMX server as MBean [org.springframework.cloud.context.properties:name=configurationPropertiesRebinder,context=517d4a0d,type=ConfigurationPropertiesRebinder]
2018-03-03 17:28:39.460  INFO 36724 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located managed bean 'refreshEndpoint': registering with JMX server as MBean [org.springframework.cloud.endpoint:name=refreshEndpoint,type=RefreshEndpoint]
2018-03-03 17:28:39.472  INFO 36724 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2018-03-03 17:28:39.473  INFO 36724 --- [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application unknown with eureka with status UP
2018-03-03 17:28:39.527  INFO 36724 --- [      Thread-11] o.s.c.n.e.server.EurekaServerBootstrap   : Setting the eureka configuration..
2018-03-03 17:28:39.527  INFO 36724 --- [      Thread-11] o.s.c.n.e.server.EurekaServerBootstrap   : Eureka data center value eureka.datacenter is not set, defaulting to default
2018-03-03 17:28:39.527  INFO 36724 --- [      Thread-11] o.s.c.n.e.server.EurekaServerBootstrap   : Eureka environment value eureka.environment is not set, defaulting to test
2018-03-03 17:28:39.536  INFO 36724 --- [      Thread-11] o.s.c.n.e.server.EurekaServerBootstrap   : isAws returned false
2018-03-03 17:28:39.537  INFO 36724 --- [      Thread-11] o.s.c.n.e.server.EurekaServerBootstrap   : Initialized server context
2018-03-03 17:28:39.537  INFO 36724 --- [      Thread-11] c.n.e.r.PeerAwareInstanceRegistryImpl    : Got 1 instances from neighboring DS node
2018-03-03 17:28:39.537  INFO 36724 --- [      Thread-11] c.n.e.r.PeerAwareInstanceRegistryImpl    : Renew threshold is: 1
2018-03-03 17:28:39.537  INFO 36724 --- [      Thread-11] c.n.e.r.PeerAwareInstanceRegistryImpl    : Changing status to UP
2018-03-03 17:28:39.543  INFO 36724 --- [      Thread-11] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2018-03-03 17:28:39.583  INFO 36724 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8761 (http)
2018-03-03 17:28:39.584  INFO 36724 --- [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761
2018-03-03 17:28:39.588  INFO 36724 --- [           main] c.happybks.demo.EurekaServerApplication  : Started EurekaServerApplication in 10.552 seconds (JVM running for 11.327)

然后我们访问http://localhost:8761/,访问Eureka服务器的控制台。

这里比较需要关注的是当中的服务列表。是的,Eureka所谓服务管理者,作为服务注册的中心,所有客户端注册信息都将在这个列表中出现。

建立服务提供者

接下来,我们再建立一个项目,作为服务提供者客户端。

这次构建的是客户端,所以不选EurekaServer,选Eureka Discovery

构建生成的项目目录如下:(Eureka的客户端——服务提供者)

它的pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.happybks</groupId>
	<artifactId>serviceProvider_1</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>serviceProvider-1</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.10.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Edgware.SR2</spring-cloud.version>
	</properties>

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

客户端的启动类的构建方式,我们依然官文:

11. Service Discovery: Eureka Clients

Service Discovery is one of the key tenets of a microservice based architecture. Trying to hand configure each client or some form of convention can be very difficult to do and can be very brittle. Eureka is the Netflix Service Discovery Server and Client. The server can be configured and deployed to be highly available, with each server replicating state about the registered services to the others.

11.1 How to Include Eureka Client

To include Eureka Client in your project use the starter with group org.springframework.cloud and artifact id spring-cloud-starter-eureka. See the Spring Cloud Project page for details on setting up your build system with the current Spring Cloud Release Train.

11.2 Registering with Eureka

When a client registers with Eureka, it provides meta-data about itself such as host and port, health indicator URL, home page etc. Eureka receives heartbeat messages from each instance belonging to a service. If the heartbeat fails over a configurable timetable, the instance is normally removed from the registry.

Example eureka client:

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableEurekaClient
@RestController
public class Application {

    @RequestMapping("/")
    public String home() {
        return "Hello world";
    }

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

(i.e. utterly normal Spring Boot app). In this example we use @EnableEurekaClient explicitly, but with only Eureka available you could also use @EnableDiscoveryClient. Configuration is required to locate the Eureka server. Example:

application.yml. 

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

where "defaultZone" is a magic string fallback value that provides the service URL for any client that doesn’t express a preference (i.e. it’s a useful default).

The default application name (service ID), virtual host and non-secure port, taken from the Environment, are ${spring.application.name},${spring.application.name} and ${server.port} respectively.

@EnableEurekaClient makes the app into both a Eureka "instance" (i.e. it registers itself) and a "client" (i.e. it can query the registry to locate other services). The instance behaviour is driven by eureka.instance.* configuration keys, but the defaults will be fine if you ensure that your application has aspring.application.name (this is the default for the Eureka service ID, or VIP).

See EurekaInstanceConfigBean and EurekaClientConfigBean for more details of the configurable options.

可以看到文档中说道Eureka客户端需要依赖spring-cloud-starter-eureka

POM文件中已经为我们建立好了。

之后我们将Eureka客户端的启动类也按照文档中说的修改:

注意:springboot项目设置component-scan目录,需要用@ComponentScan注解在springboot启动类上,并将basePackages属性赋给一个字符串数组,每个元素都是扫描的包名。如果不写这个注解,basePackages默认值就是启动类所在包,那么springboot只会扫描与启动类同一个包下的注解了@Component@Service@Repository@Controller的注解,其他包下的无效;而注解了该特定包名后,启动类所在包则不再扫描,除非也显示的配置到basePackages中去

package com.happybks.providers;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;


@SpringBootApplication
@EnableEurekaClient
@ComponentScan(basePackages = { "com.happybks.controllers" })
public class ServiceProvider1Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(ServiceProvider1Application.class).web(true).run(args);
    }
}

然后我们按照文档配置一下application.yml:

spring:
  application:
    name: first-service-app
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

配置项eureka.client.service-url是一个map,可以往里面添加其他配置键值对。(不知道是不是版本原因,文档中说的serviceUrl,在IDE中给出的是service-url提示)

另外请注意,我们给本服务提供者的服务应用命名为first-service-app,这个服务信息会被注册到Eureka上。 一会儿我们会再构建一个服务调用者客户端程序,它会通过Eureka获取first-service-app服务列表,然后通过first-service-app来请求调用服务。

这里我们再给first-service-app创建一个控制器,提供一个rest服务。

package com.happybks.controllers;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import com.happybks.beans.PetBean;

@Controller
public class PetInfoController {
	
	@GetMapping(value="/petinfo/findpet",produces=MediaType.APPLICATION_JSON_VALUE)
	public PetBean findPet() {
		PetBean petBean = new PetBean();
		petBean.setBreedType("聪明机灵小柴犬");
		petBean.setAge(1);
		petBean.setPrice(50000);
		return petBean;
	}

}

我们在启动类同级目录下也写个controller:

package com.happybks.providers;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.happybks.beans.PetBean;

@RestController
public class CatInfoController {
	
	@GetMapping(value="/petinfo/findcat",produces=MediaType.APPLICATION_JSON_VALUE)
	public PetBean findPet() {
		PetBean petBean = new PetBean();
		petBean.setBreedType("聪明机灵布偶喵");
		petBean.setAge(2);
		petBean.setPrice(20000);
		return petBean;
	}

}

对应的PetBean:

package com.happybks.beans;

public class PetBean {
	private String breedType;
	private int age;
	private double price;
	public String getBreedType() {
		return breedType;
	}
	public void setBreedType(String breedType) {
		this.breedType = breedType;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	
	public PetBean() {
		
	}
}

好整个服务提供者客户端项目构建好了

启动运行,当然,刚刚的eureka服务一直开着呢,否则找不到eureka会报错的。默认访问本地8761端口。

之后,我们在刷新一下:http://localhost:8761/

已经可以看到我们刚刚启动的first-service-app已经将自己的服务信息注册到了eureka服务器上,请看下面的列表。first-service-app的端口没设置,所以默认是8080,没显示在status上。

如果你去点击localhost:first-service-app,你会发现它请求的是8080端口

你可以访问以下:http://localhost:8080/petinfo/findpet/

访问:http://localhost:8080/petinfo/findcat

如果示例中没有配置那个@ComponentScan,则默认的扫描路径是启动类所在包名。则运行的结果将是,猫能正常显示,狗不能。)

建立服务调用者

最后我们还要建立一个服务调用者客户端

(本文出自oschina博主happyBKs的博文:https://my.oschina.net/happyBKs/blog/edit/1629043)

既然也是Eureka的客户端,自然也要勾选Eureka Discovery的依赖。并且,我们还需要引入一个Ribbon框架的依赖,用于负载均衡。

后面这一步什么都不用改,但是你可以看到,我们刚才的配置全部作为一个url的get请求参数去下载springboot的项目模板了,STS会根据这些参数来构建好项目。

服务调用者使用的依赖,与服务提供者相同。但在此基础上,还要追加一个Ribbon的依赖。Ribbon是Netflix oss中负载均衡框架。

加入Ribbon的原因是服务调用者不关心服务提供者有多少个服务实例,它只关注它要调用这个服务ID,因而可能需要一定的负载均衡。

启动类如下:

package com.happybks.callers;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = { "com.happybks.controllers" })
@EnableEurekaClient
public class ServiceCaller1Application {

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

然后我们写一个controller,内容时提供一个接口,实现对特定服务的请求调用。

package com.happybks.controllers;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Configuration
public class ShopperBehaviorController {

	@Bean
	@LoadBalanced
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}
	
	@GetMapping("/shopper/routine")
	public String routine() {
		RestTemplate restTemplate = getRestTemplate();
		String json = restTemplate.getForObject("http://first-service-app/petinfo/findpet", String.class);
		return json;
	}
}

如果我们需要调用之前发布的服务,我们需要一个rest客户端。

在过去没有使用Eureka之前,我们调用rest服务时,一般用的是一些rest客户端,例如CXF。调用的时候使用服务器端的IP、端口、服务的url,就可以调用了。

但是现在我们使用Eureka之后,它的调用就不一样了。我们现在需要使用Ribbon这个负载均衡框架。然后在调用的时候需要做一些处理。

注意:

(1)这里的@Bean注解需要与@Configuration配合使用。@Bean注解在方法上,@Configuration注解在类上。

(2)restTemplate.getForObject方法请求特定的服务url,注意里面使用的first-service-app正是我们刚才在serviceProvider-1服务提供者客户端项目中配置的服务名称,它已经被注册到Eureka服务上。

(3)RestTemplate 还有其他请求调用服务的方法,如restTemplate.getForEntity,返回的是一个ResponseEntity<>。

最后我们看看配置文件application.yml

server:
  port: 8090
spring:
  application:
    name: first-caller-app
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

我们启动该项目:

也能够在Eureka的管理页面localhost:8761上看到这个客户端实例被注册了。

然后我们在浏览器中请求Eureka客户端服务调用者应用的url:

http://localhost:8090/shopper/routine

显示了如下内容

因为服务调用者客户端从Eureka服务器获得了服务列表,知道了first-service-app服务提供者客户端的服务信息,然后自己通过rest方式请求调用first-service-app的服务,并将结果返回了。

本文示例项目的架构总结

过程不再多说,示例的整个搭建过程已经很详细了,最后的总结,请着重关注文中的注意事项。

猜你喜欢

转载自my.oschina.net/happyBKs/blog/1629043