微服务发布与调用
- 认识Eureka框架
- 运行Eureka服务器
- 发布微服务
- 调用微服务
- 本章将讲述SpringCloud中Eureka的使用,包括在Eureka服务器上发布、调用微服务,Eureka的配置以及集群等内容
1、Eureka介绍
- 提供了Eureka服务器端与客户端
- 主要用于服务管理(维护服务列表,自动检查其状态)
- SpringCloud集成了Netflix OSS的多个项目,形成了Spring-Cloud-Netflix项目,该项目包含了多个子模块,这些子模块对集成的Netflix旗下框架进行了封装
Spring Cloud Netflix提供了一系列搭建微服务基础架构的功能组件
- Eureka(服务注册与发现框架)
- Hystrix(服务容错组件):容错管理工具,旨在通过控制服务和第三方库的节点,从而对延迟和故障提供强大的容错能力
- Zuul(服务网关):边缘服务工具,提供动态路由、监控等边缘服务
- Ribbon(客户端负载均衡器):提供客服端负载均衡算法,将Netflix的中间层服务连接起来
- Feign(声明式Http客户端):可以创建声明式、模板化的Http客户端,进行微服务调用
本小节将讲述其中一个较为重要的服务管理框架:Eureka[juˈri:kə]
1.1、关于Eureka
- 一个基于REST风格的服务组件,用于定位服务,以实现云端的负载均衡和中间层服务器的故障转移
- 提供了基于Java的客户端组件,使得与服务的交互更加方便
- 客户端还有一个内置的负载均衡器,用于执行基本的轮询负载均衡,为业务组件的集群部署创造了条件
- 使用该框架,可以将业务组件注册到Eureka容器中,进行集群部署
- Eureka提供服务调用功能,可以发布容器中的服务并进行调用
1.2、Eureka架构
- 一个简单的Eureka集群,需要一个Eureka服务器、若干个服务提供者
- 将业务组件注册到Eureka服务器中,其他客户端组件可以向服务器获取服务并且进行远程调用
- 图中有两个服务器,服务器支持集群部署,每个服务器也可以作为对方服务器的客户端进行相互注册与复制
- 3个Eureka客户端,2个用于发布服务,1个用于调用服务
- 不管是服务器还是客户端,都可以部署多个实例,因此,就很容易构建高可用的服务集群
Eureka服务器(114电话查询平台)
Eureka客户端服务提供者(医院、警察局电话放置114电话查询平台)
Eureka客户端服务调用者(查询电话的我们)
我们去114平台查询某处电话号码(服务查找),得到电话号码后(获取注册信息),拨打电话(服务调用)
- Eureka Server实例作为服务注册中心的角色,负责接收来自Eureka Client的服务注册,并加以保存
- Eureka Client内嵌于一个微服务中,会将当前实例作为服务实例注册中心(Eureka Server),注册内容包括IP、Port和服务实例名称
- 消费者同样内嵌了Eureka Client,在进行微服务调用的时候,可以通过Eureka提供的DiscoveryClient和服务实例的名称,从服务注册中心(Eureka Server)获取服务实例列表,然后得到服务实例的IP和Port,以完成服务调用
1.3、服务器端
对于注册到服务器的服务组件,Eureka服务器并没有提供后台的存储,这些注册的服务实例被保存在内存的服务注册中心,它们通过心跳来保持其最新状态,这些操作都可以在内存中完成。
客户端存在相同的机制,同样在内存中保存了注册表信息,这样的机制提升了Eureka组件性能,每次服务的请求都不用经过服务器端的注册中心。
1.4、服务提供者
作为Eureka客户端存在的服务提供者,主要进行以下工作:
第一:向服务器注册服务
第二:发送心跳给服务器
第三:向服务器端获取注册列表
当客户端注册到服务器时,它将会提供一些关于它自己的信息给服务器端,例如:IP、Port、健康链接等
1.5、服务调用者
对于发布到Eureka服务器的服务,使用调用者可对其进行服务查找和调用,服务调用者也是作为客户端存在,但是其职责主要是发现和调用服务。
在实际情况中,有可能出现本身既是服务提供者,又是服务调用者的情况,例如:传统的企业应用三层架构中,服务层(service)会调用数据访问层(dao)的接口进行数据操作,它本身也会提供服务给控制层(controller)使用。
2、Eureka应用
2.1、构建服务器
- 创建一个maven项目:atm-eureka-server
2.1.1、添加依赖
- 在pom.xml中添加Spirng Cloud的依赖
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atm.cloud</groupId>
<artifactId>atm_enreka_server</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>atm_enreka_server Maven Webapp</name>
<url>http://maven.apache.org</url>
<!-- Spring Cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--不需要再引入-->
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-->
<!--Eureka-->
<!--
加入的spring-cloud-starter-eureka-server会自动引入spring-boot-starter-web
因此只需要加入该依赖,我们的项目就具有 Web 容器的功能
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<finalName>atm_enreka_server</finalName>
</build>
</project>
2.1.2、编写启动类
package com.atm.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class MyApplicationServer {
public static void main(String[] args) {
SpringApplication.run(MyApplicationServer.class, args);
}
}
- 可以发现与SpringBoot的启动类基本没有差异,只是加入了@EnableEurekaServer,声明这是一个 Eureka 服务器。直接运行 FirstServer 即可启动 Eureka 服务器
- application.yml(Eureka默认 localhost:8761)
server:
port: 8761
- 执行main方法,启动,虽然启动成功,但是出现了异常
- 异常暂时不管,先进行访问
- 可以看到服务的实例列表,目前我们并没有注册服务,因此列表为空
2.1.3、服务器注册开关(解决异常)
- 在上面我们已经发现,启动的时候会出现异常
- 这是由于在服务器启动时,服务器会把自己当作一个客户端,去 Eureka 服务器注册,并且会到 Eureka 服务器抓取注册信息,它自己本身只是一个服务器,而不是服务的提供者(客户端),因此可以修改application.yml 文件,修改以下两个配置:
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
2.2、编写服务提供者
- 新创建一个maven项目:atm_eureka_provider
2.2.1、添加依赖
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atm.cloud</groupId>
<artifactId>atm_enreka_provider</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>atm_enreka_provider Maven Webapp</name>
<url>http://maven.apache.org</url>
<!-- Spring Cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</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>
<!-- 服务提供者 -->
</dependencies>
<build>
<finalName>atm_enreka_provider</finalName>
</build>
</project>
2.2.2、配置文件
server:
port: 8080
spring:
application:
name: first-service-provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
hostname: localhost
- defaultZone: http://localhost:8761/eureka/,让客户端知道去哪里注册
2.2.3、编写控制器
- Person类
package com.atm.cloud;
public class Person {
private Integer id;
private String name;
private Integer age;
//...省略setter/getter...
}
- MyController类
package com.atm.cloud;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping(value = "/info", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Person findPerson(){
Person person=new Person();
person.setId(1);
person.setAge(18);
person.setName("atm");
return person;
}
@RequestMapping(value = "/person/{personId}", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Person findPerson(@PathVariable("personId")Integer personId){
Person person=new Person();
person.setId(personId);
person.setAge(18);
person.setName("atm");
return person;
}
}
2.2.4、编写启动类
- @EnableEurekaClient,默认注册到8761的服务器上
package com.atm.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class MyApplicationProvider {
public static void main(String[] args) {
SpringApplication.run(MyApplicationProvider.class, args);
}
}
- 启动服务器MyApplicationServer、启动服务提供者MyApplicationProvider
2.3、编写服务调用者
- 服务被注册、发布到Euraka服务器后,就需要程序去发现它,并且进行调用
- 此处所说的调用者,是指同样注册到Eureka的客户端,来调用其他客户端发布的服务,简单来说,就是Eureka内部调用
- 由于同一个服务,可能会部署多个实例,调用过程可能涉及负载均衡,服务器查找等问题,Netflix已经帮我们解决了这个问题,并且SpringCloud已经封装了一次
- 创建新maven项目:atm_eureka_invoker
2.3.1、添加依赖
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atm.cloud</groupId>
<artifactId>atm_enreka_invoker</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>atm_enreka_invoker Maven Webapp</name>
<url>http://maven.apache.org</url>
<!-- Spring Cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</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>
<dependency><!-- 负载均衡框架 -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- 服务调用者 -->
</dependencies>
<build>
<finalName>atm_enreka_invoker</finalName>
</build>
</project>
2.3.2、配置文件
server:
port: 9000
spring:
application:
name: first-service-invoker
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
- 在配置文件中,配置了应用名称为 “first-service-invoker”,这个调用者的访问端口为9000,需要注意的是,这个调用本身也可以对外提供服务
- 与提供者一样,使用eureka的配置,将调用者注册到“atm_enreka_server”
2.3.3、编写控制器
package com.atm.cloud;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@Configuration
public class InvokerController {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@RequestMapping(value="/router",method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public String router() {
RestTemplate restTemplate = getRestTemplate();
// 根据应用名称调用服务
String json = restTemplate.getForObject(
"http://first-service-provider/person/1", String.class);
return json;
}
}
- 在控制器中,配置了RestTemplate的bean,RestTemplate本来是spring-web模块下的类,主要用调用REST服务,本身并不具备调用分布式服务的能力,但是RestTemplate的bean被@LoadBalanced注解修饰后,这个RestTemplate实例就具备访问分布式服务的能力
2.3.4、编写启动类
- 在控制器中,新建了一个router的测试方法,用来对外发布REST服务,该方法只是一个路由的作用
- 实际上,使用RestTemplate来调用“first-service-provider”(服务者提供)的服务
- 需要注意的是,仅仅是通过服务名称来进行调用
package com.atm.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class MyApplicationInvoker {
public static void main(String[] args) {
SpringApplication.run(MyApplicationInvoker.class, args);
}
}
- 启动类中,使用@EnableDiscoveryClient注解来修饰启动类
- 该注解使得服务调用者,有能力去Eureka中发现服务
- 需要注意的是@EnableEurekaClient中已经包含@EnableDiscoveryClient的功能
- 也就是说,一个Eureka客户端,本身就具有发现服务的能力
- 启动服务器(atm_eureka_server)
- 启动服务提供者(atm_eureka_provider)
- 启动服务调用者(atm_eureka_invoker)
- 在 浏 览 器 中 访 问 服 务 调 用 者 发 布 的 “ router ” 服 务 :http://localhost:9000/router,可以看到在浏览器输出如下:
- 根据输出可知,实际上调用了服务提供者的person/1
2.4、程序结构
- 本案例新建了三个项目
- Eureka 服 务 为 本 例 的 “ atm_eureka_server ”,服务发布者为“atm_eureka_provider”,服务调用者为“atm_eureka_invoker”
- 用户通过浏览器访问调用者的9000端口的router服务,router服务中查找服务器提供者的服务并进行调用