Spring Cloud学习02-服务注册与发现

Spring Cloud学习02-服务注册与发现

Spring Cloud简介

Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。

微服务架构

“微服务架构”在这几年非常的火热,以至于关于微服务架构相关的产品社区也变得越来越活跃(比如:netflix、dubbo),Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。

那么什么是“微服务架构”呢?简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如RESTful API的方式互相调用。

对于“微服务架构”,大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的《Microservices》,本文不做更多的介绍和描述。

服务注册与发现

在简单介绍了Spring Cloud和微服务架构之后,下面回归本文的主旨内容,如何使用Spring Cloud搭建服务注册与发现模块。

这里我们会用到Spring Cloud Netflix,该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路有(Zuul),客户端负载均衡(Ribbon)等。

所以,我们这里的核心内容就是服务发现模块:Eureka。

Eureka简介

EurekaNetflix开发的,一个基于REST服务的,服务注册与发现的组件

它主要包括两个组件:Eureka Server和Eureka Client

Eureka Client:一个Java客户端,用于简化与Eureka Server的交互(通常就是微服务中的客户端和服务端)

Eureka Server:提供服务注册和发现的能力(通常就是微服务中的注册中心)

各个微服务启动时,会通过Eureka Client向Eureka Server注册自己,Eureka Server会存储该服务的信息

也就是说,每个微服务的客户端和服务端,都会注册到Eureka Server,这就衍生出了微服务相互识别的话题

同步:每个Eureka Server同时也是Eureka Client(逻辑上的)

多个Eureka Server之间通过复制的方式完成服务注册表的同步,形成Eureka的高可用

识别:Eureka Client会缓存Eureka Server中的信息

即使所有Eureka Server节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者(笔者已亲测)

续约:微服务会周期性(默认30s)地向Eureka Server发送心跳以Renew(续约)自己的信息(类似于heartbeat)

续期:Eureka Server会定期(默认60s)执行一次失效服务检测功能

它会检查超过一定时间(默认90s)没有Renew的微服务,发现则会注销该微服务节点

Spring Cloud已经把Eureka集成在其子项目Spring Cloud Netflix里面

关于Eureka配置的最佳实践,可参考:https://github.com/spring-cloud/spring-cloud-netflix/issues/203

更多介绍,可参考:http://cloud.spring.io/spring-cloud-static/Camden.SR4/#spring-cloud-eureka-server

下面我们动手来做一些尝试。

创建服务注册中心

创建一个基础的Spring Boot工程,工程目录如下:


并在pom.xml中引入需要的依赖内容:

```

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.5.3.RELEASE</version>

<relativePath/></parent>

<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>1.8</java.version>

</properties>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-dependencies</artifactId>

<version>Dalston.RELEASE</version>

<type>pom</type>

<scope>import</scope>

</dependency>

</dependencies>

</dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-config</artifactId>

</dependency>

org.springframework.cloud

spring-cloud-starter-eureka

--><dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-eureka-server</artifactId>

</dependency>

</dependencies>

```

通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:

@SpringBootApplication

@EnableEurekaServer

public classApplicaiton {

public static voidmain(String[] args) {

//SpringApplication.run(Applicaiton.class,args);

//new SpringApplicationBuilder(Applicaiton.class).web(true).run(args);

newSpringApplicationBuilder(Applicaiton.class).run(args);

}

}

在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.properties中问增加如下配置:

server.port=1111

eureka.client.register-with-eureka=false#设置是否从注册中心获取注册信息(缺省true),因为这是一个单点的EurekaServer

#不需要同步其他EurekaServer节点的数据,故设为falseeureka.client.fetch-registry=false#设置是否将自己作为客户端注册到注册中心(缺省true#这里为不需要(查看@EnableEurekaServer注解的源码,会发现它间接用到了@EnableDiscoveryClient#在未设置defaultZone的情况下,注册中心在本例中的默认地址就是http://127.0.0.1:1100/eureka/

#但奇怪的是,启动注册中心时,控制台还是会打印这个地址的节点:http://localhost:8761/eureka/

#而实际服务端注册时,要使用1100端口的才能注册成功,8761端口的会注册失败并报告异常--说法不正确

#打印信息如下:2017-05-24 11:16:49.148INFO 14800 --- [main] c.n.eureka.DefaultEurekaServerContext: Initializing ...

2017-05-24 11:16:49.156INFO 14800 --- [main] c.n.eureka.cluster.PeerEurekaNodes: Adding new peer nodes [http://localhost:1111/eureka/]eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/#实际测试:若修改尾部的eureka为其他的,比如/myeureka,注册中心启动没有问题,但服务端在注册时会失败#报告异常:com.netflix.discovery.shared.transport.TransportException:cannot execute request on any known servereureka.server.enable-self-preservation=falseeureka.server.eviction.interval-timer-in-ms=4000

为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port属性设置为1111。

启动工程后,访问:http://localhost:1111/

可以看到下面的页面,其中还没有发现任何服务


创建服务提供方

下面我们创建提供服务的客户端,并向服务注册中心注册自己。

假设我们有一个提供计算功能的微服务模块,我们实现一个RESTful API,通过传入两个参数a和b,最后返回a + b的结果。

首先,创建一个基本的Spring Boot应用,在pom.xml中,加入如下配置:

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.5.3.RELEASE</version>

<relativePath/></parent>

<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>1.8</java.version>

</properties>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-dependencies</artifactId>

<version>Dalston.RELEASE</version>

<type>pom</type>

<scope>import</scope>

</dependency>

</dependencies>

</dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-config</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-eureka</artifactId>

</dependency>

org.springframework.cloud

spring-cloud-starter-eureka-server

-->

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>

其次,实现/add请求处理接口,通过DiscoveryClient对象,在日志中打印出服务实例的相关内容。

@RestController

public classComputerController {

private finalLoggerlogger= Logger.getLogger(getClass());

@Autowired

privateDiscoveryClientdiscoveryclient;

@Autowired

privateEurekaClienteurekaClient;

@RequestMapping(value="/add",method= RequestMethod.GET)

publicInteger add(@RequestParamInteger a,@RequestParamInteger b) {

//ServiceInstance instance = client.getLocalServiceInstance();

InstanceInfo instance =eurekaClient.getNextServerFromEureka("compute-service",false);

Integer r = a + b;

logger.info("/add, host:"+instance.getHostName() +",serviceId:"+ instance.getInstanceId() +",result:"+ r);

returnr;

}

}

最后在主类中通过加上@EnableEurekaClient注解,该注解能激活Eureka中的DiscoveryClient实现,才能实现Controller中对服务信息的输出。

@SpringBootApplication

@EnableEurekaClient

public classComputeServiceApplication {

public static voidmain(String[] args) {

//new SpringApplicationBuilder(ComputeServiceApplication.class).web(true).run(args);

newSpringApplicationBuilder(ComputeServiceApplication.class).run(args);

}

}

我们在完成了服务内容的实现之后,再继续对application.properties做一些配置工作,具体如下:

server.port=2222

spring.application.name=compute-serviceeureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

eureka.instance.lease-renewal-interval-in-seconds=10eureka.instance.lease-expirtion-duration-in-seconds=30eureka.client.healthcheck.enabled=true

通过spring.application.name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。

eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。

为了在本机上测试区分服务提供方和服务注册中心,使用server.port属性设置不同的端口。

启动该工程后,再次访问:http://localhost:1111/

可以看到,我们定义的服务被注册了。


验证

由于发布的微服务所暴露出去的都是HTTP的接口,所以验证的话,可以在浏览器访问下面的地址:

http:127.0.0.1:2222/add?a=3&b=13


目前为止,我们完成了Spring Cloud

Netflix Eureka搭建注册中心的基本示例,不过也只是尝尝鲜

因为它还存在着很多问题,比如

什么是自我保护模式

服务提供方关闭之后,在注册中心看到的状态还是UP

注册中心的服务提供方显示的名字,是不是可以自定义

等等吧,这些问题,请参见Eureka进阶篇

参考:http://blog.didispace.com/springcloud1/

http://jadyer.cn/2017/01/16/springcloud-eureka/

https://springcloud.cc/spring-cloud-dalston.html#_spring_cloud_netflix

eureka-server的配置文件:

server.port=1111

#设置是否从注册中心获取注册信息(缺省true),因为这是一个单点的EurekaServer

#不需要同步其他EurekaServer节点的数据,故设为false

eureka.client.register-with-eureka=false

#设置是否将自己作为客户端注册到注册中心(缺省true)

#这里为不需要(查看@EnableEurekaServer注解的源码,会发现它间接用到了@EnableDiscoveryClient)

#在未设置defaultZone的情况下,注册中心在本例中的默认地址就是http://127.0.0.1:1100/eureka/

#但奇怪的是,启动注册中心时,控制台还是会打印这个地址的节点:http://localhost:8761/eureka/

#而实际服务端注册时,要使用1100端口的才能注册成功,8761端口的会注册失败并报告异常--说法不正确

eureka.client.fetch-registry=false

#实际测试:若修改尾部的eureka为其他的,比如/myeureka,注册中心启动没有问题,但服务端在注册时会失败

#报告异常:com.netflix.discovery.shared.transport.TransportException:cannot execute request on any known server

eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

#关闭自我保护模式(缺省为打开)

eureka.server.enable-self-preservation=false

#续期时间,即扫描失败服务的间隔时间(缺省为:60*1000ms)

eureka.server.eviction.interval-timer-in-ms=4000


<parent>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-parentartifactId>

<version>1.5.3.RELEASEversion>

<relativePath/>parent>

<properties>

<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>

<java.version>1.8java.version>

properties>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloudgroupId>

<artifactId>spring-cloud-dependenciesartifactId>

<version>Dalston.RELEASEversion>

<type>pomtype>

<scope>importscope>

dependency>

dependencies>

dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starterartifactId>

dependency>

<dependency>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-testartifactId>

<scope>testscope>

dependency>

<dependency>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-actuatorartifactId>

dependency>

<dependency>

<groupId>org.springframework.cloudgroupId>

<artifactId>spring-cloud-starter-configartifactId>

dependency>

org.springframework.cloud

spring-cloud-starter-eureka

--><dependency>

<groupId>org.springframework.cloudgroupId>

<artifactId>spring-cloud-starter-eureka-serverartifactId>

dependency>

dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-maven-pluginartifactId>

plugin>

plugins>

build>


eureka-client配置文件:

server.port=2222

#指定发布的微服务名(以后调用时,只需该名称即可访问该服务)

spring.application.name=compute-service

#指定服务注册中心的地址

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

#修改显示的微服务名为IP:端口

eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port}

#eureka.instance-id:${spring.application.name}

#设置微服务调用地址为IP优先(缺省为false)

eureka.instance.prefer-ip-address=true

#eureka.instance.ip-address=192.168.6.16

#心跳时间,即服务续约间隔时间(缺省为30s)

eureka.instance.lease-renewal-interval-in-seconds=10

#发呆时间,即服务续约到期时间(缺省为90s)

eureka.instance.lease-expirtion-duration-in-seconds=30

#开启健康检查(依赖spring-boot-starter-actuator)

eureka.client.healthcheck.enabled=true


pom.xml文件:

<parent>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-parentartifactId>

<version>1.5.3.RELEASEversion>

<relativePath/>parent>

<properties>

<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>

<java.version>1.8java.version>

properties>

<dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.cloudgroupId>

<artifactId>spring-cloud-dependenciesartifactId>

<version>Dalston.RELEASEversion>

<type>pomtype>

<scope>importscope>

dependency>

dependencies>

dependencyManagement>

<dependencies>

<dependency>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starterartifactId>

dependency>

<dependency>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-testartifactId>

<scope>testscope>

dependency>

<dependency>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-starter-actuatorartifactId>

dependency>

<dependency>

<groupId>org.springframework.cloudgroupId>

<artifactId>spring-cloud-starter-configartifactId>

dependency>

<dependency>

<groupId>org.springframework.cloudgroupId>

<artifactId>spring-cloud-starter-eurekaartifactId>

dependency>

org.springframework.cloud

spring-cloud-starter-eureka-server

-->

dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.bootgroupId>

<artifactId>spring-boot-maven-pluginartifactId>

plugin>

plugins>

build>

推荐阅读更多精彩内容