本分步指南帮助全栈开发人员使用Spring Boot在微服务中构建组件,并将其用于Docker。
在本教程中,我将向您展示如何使用Spring Boot及其不同组件构建微服务,在最后一节中,我将向您展示使用Docker容器的部署。
我们将了解:
- 实现微服务的不同组件。
- 通过集装箱化部署服务。
微服务架构的组件
1.配置服务器
为了使所有微服务集中和共享属性文件,我们将创建一个配置服务器,该服务器本身就是微服务,并管理所有微服务属性文件,这些文件受版本控制;属性的任何更改都将自动发布到所有微服务,而无需重新启动服务。需要记住的一件事是,每个微服务都与配置服务器通信以获取属性值,因此配置服务器必须是一个高度可用的组件;如果它失败,那么所有微服务都会失败,因为它无法确定属性值!因此,我们应该处理这个场景——配置服务器不应该是SPF(单个故障点),因此我们将为配置服务器启动多个容器。
2.Eureka Discovery 服务器
微服务的主要目标是基于业务特性去中心化不同的组件,这样每个组件(也就是微服务)都可以根据需要扩展,对于一个特定的微服务,有多个实例,我们可以根据需要添加和删除实例。因此,在微服务模式中,单一庞大的系统做负载平衡的方式是行不通的。当它动态生成容器时,容器有动态IP地址,为了跟踪服务的所有实例,需要一个管理器服务,所以当容器生成时,它将自己注册到管理器,管理器跟踪实例;如果删除了服务,则管理器将其从管理器的服务注册表中删除。如果其他服务需要相互通信,它会联系一个发现服务以获得另一个服务的实例。同样,这是一个高可用性组件;如果发现服务宕机,微服务就不能相互通信,因此发现服务必须有多个实例。
3.组件,又名服务
组件是[微服务架构的关键成分]。按组件,我指的是可以独立管理或更新的实用程序或业务功能。它有一个预定义的边界,并公开了一个API,通过该API,其他组件可以与该服务通信。微服务的想法是将完整的业务功能分解为几个独立的小功能,这些功能将相互通信,以产生完整的业务功能。如果将来功能的任何部分发生变化,我们可以更新或删除该组件,并将新组件添加到架构中。因此,微服务架构产生了具有适当封装和正确定义边界的适当模块化架构。
4.网关服务
微服务是集体产生业务功能的独立服务的集合。每个微服务都会发布一个API,通常是REST API,因此作为客户端,管理这么多要通信的端点URL很麻烦。此外,考虑另一个角度:如果一些应用程序想要构建身份验证框架或安全检查,他们必须在所有服务中实现它,这样就可以对DRY重复。如果我们有一个面向互联网的网关服务,客户端将只调用一个端点,并将调用委托给实际的微服务,所有身份验证或安全检查都将在网关服务中完成。
现在,我们对微服务的不同部分如何协同工作有了基本的了解。在本教程中,我将创建一个将返回员工信息的员工搜索服务,一个将调用搜索服务并显示结果的EmployeeDashBoard
服务,一个Eureka
服务器,以便这些服务可以自行注册,以及一个从外部联系这些服务的网关服务。然后,我们将在Docker
容器中部署我们的服务,并使用DockerCompose
生成Docker
容器。我将使用Spring Boot
进行本教程。
让我们开始构建我们的微服务项目,因为我们必须创建五个单独的服务:
-
Config Server
-
Eureka server
-
Employee service
-
Employee Dashboard service
-
Zuul Proxy
最好的起点是去Spring Initializr,生成所需的模块,然后点击“生成项目”。
在本教程中,我们将使用Spring Boot 1.5.4。
创建配置服务器
要创建配置服务器,首先我们需要从start.spring.io
检查配置服务器模块,并检查执行器以查看端点。然后,下载zip文件并在Eclipse中打开它。
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.example</groupId>
<artifactId>MicroserviceConfigServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>MicroserviceConfigServer</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.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>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-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>
它下载spring-cloud-config-server工件。
接下来,我们必须创建一个bootstrap.properties
文件,其中我们提到配置服务器从哪个位置读取属性。在生产模式下,它应该是Git存储库的URL,但由于这是一个演示,我将使用我的本地磁盘。所有属性文件都将放置在那里,配置服务器读取这些属性文件。
让我们看看bootstrap.properties文件:
server.port=9090
spring.cloud.config.server.native.searchLocations=file://${user.home}/MicroService/centralProperties/
SPRING_PROFILES_ACTIVE=native
在这里,我指示Spring Boot
在端口9090
中生成嵌入式服务器,并使用centralProperties
文件夹作为文件夹来搜索所有属性文件。请注意,在我们的Docker
容器中,您必须创建一个中央属性文件夹,并将所有属性文件放置在那里。
现在让我们看看Java部分:
package com.example.MicroserviceConfigServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@EnableConfigServer
@SpringBootApplication
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
在这里,我使用@EnableConfigserver
注释,我们指示Spring Boot
将此服务视为配置服务器应用程序。
现在,在 CentralProperties
文件夹中放置一些测试属性文件。
我们现在都为配置服务器准备好了。如果我们运行此服务并点击http://localhost:9090/config/default URL,我们会看到以下响应:
{
"name": "config",
"profiles": [
"default"
],
"label": null,
"version": null,
"state": null,
"propertySources": [
{
"name": "file:///home/shamik/MicroService/centralProperties/config.properties",
"source": {
"application.message": "Hello Shamik"
}
},
{
"name": "file:///home/shamik/MicroService/centralProperties/application.properties",
"source": {
"welcome.message": "Hello Spring Cloud"
}
}
]
}
它显示了我放置在centralProperties文件夹中的所有文件名、密钥和值。
实施服务发现
下一步是创建一个Eureka
服务器来发现服务。我们将使用Netflix
的Eureka
服务器进行服务发现。为此,我正在从start.spring.io
中选择Eureka
服务器模块并下载该项目。
pom.xml如下所示:
<?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.example</groupId>
<artifactId>EmployeeEurekaServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>EmployeeEurekaServer</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.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>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<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-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>
现在创建bootstrap.properties:
spring.application.name=EmployeeEurekaServer
eureka.client.serviceUrl.defaultZone:http://localhost:9091/
server.port=9091
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
在这里,我给这个应用程序起了一个逻辑名称EmployeeEurekaServer
,Eureka
服务器的位置是http://localhost:9091 嵌入式服务器将从端口9091
开始。请注意,Eureka
服务器本身可以是Eureka
客户端;由于Eureka
服务器可能有多个实例,因此需要与其他服务器同步。使用此eureka.client.register-with-eureka=false
,我明确指示Spring Boot
不要将Eureka
服务器视为客户端,因为我只创建了一台Eureka
服务器,因此它不需要将自己注册为客户端。
现在,我将创建Java文件:
package com.example.EmployeeEurekaServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EmployeeEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeEurekaServerApplication.class, args);
}
}
使用@EnableEurekaServer
注释,Spring Boot
将此服务生成为Eureka
服务器。我们现在都准备好了;如果我运行服务并在浏览器中点击 http://localhost:9091/ 我们将看到以下屏幕:
创建员工搜索服务
现在,我们将创建一个小型微服务,根据传递的ID实际返回员工信息。此外,它还可以返回所有员工信息。我将公开REST API
,并在Eureka
服务器上注册此微服务,以便其他微服务可以找到它。
我们从start.spring.io
选择EurekaClient
。
让我们看看pom.xml:
<?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.example</groupId>
<artifactId>EmployeeSearchService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>EmployeeSearchService</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.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>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<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.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</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>
让我们看看bootstrap.properties:
spring.application.name=EmployeeSearch
spring.cloud.config.uri=http://localhost:9090
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8080
security.basic.enable: false
management.security.enabled: false
在这里,我给出了Service EmployeeSearch
的逻辑名称,该服务的所有实例在Eureka
服务器中以此名称注册,这是所有EmployeeSerach
服务实例的通用逻辑名称。此外,我提供了配置服务器的URL
(请注意,当我们在docker
中部署它时,我们应该将本地主机更改为配置服务器的Docker
容器IP,以找到配置服务器)。
此外,我提到了Eureka
服务器位置(请注意,当我们将其部署到docker
时,我们应该将本地主机更改为尤里卡的Docker
容器IP,以找到Eureka
服务器)。
现在创建控制器和服务文件。
package com.example.EmployeeSearchService.service;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.stereotype.Service;
import com.example.EmployeeSearchService.domain.model.Employee;
@Service
public class EmployeeSearchService {
private static Map < Long, Employee > EmployeeRepsitory = null;
static {
Stream < String > employeeStream = Stream.of("1,Shamik Mitra,Java,Architect", "2,Samir Mitra,C++,Manager",
"3,Swastika Mitra,AI,Sr.Architect");
EmployeeRepsitory = employeeStream.map(employeeStr -> {
String[] info = employeeStr.split(",");
return createEmployee(new Long(info[0]), info[1], info[2], info[3]);
}).collect(Collectors.toMap(Employee::getEmployeeId, emp -> emp));
}
private static Employee createEmployee(Long id, String name, String practiceArea, String designation) {
Employee emp = new Employee();
emp.setEmployeeId(id);
emp.setName(name);
emp.setPracticeArea(practiceArea);
emp.setDesignation(designation);
emp.setCompanyInfo("Cognizant");
return emp;
}
public Employee findById(Long id) {
return EmployeeRepsitory.get(id);
}
public Collection < Employee > findAll() {
return EmployeeRepsitory.values();
}
}
控制器文件:
package com.example.EmployeeSearchService.controller;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.EmployeeSearchService.domain.model.Employee;
import com.example.EmployeeSearchService.service.EmployeeSearchService;
@RefreshScope
@RestController
public class EmployeeSearchController {
@Autowired
EmployeeSearchService employeeSearchService;
@RequestMapping("/employee/find/{id}")
public Employee findById(@PathVariable Long id) {
return employeeSearchService.findById(id);
}
@RequestMapping("/employee/findall")
public Collection < Employee > findAll() {
return employeeSearchService.findAll();
}
}
package com.example.EmployeeSearchService.domain.model;
public class Employee {
private Long employeeId;
private String name;
private String practiceArea;
private String designation;
private String companyInfo;
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPracticeArea() {
return practiceArea;
}
public void setPracticeArea(String practiceArea) {
this.practiceArea = practiceArea;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public String getCompanyInfo() {
return companyInfo;
}
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public String toString() {
return "Employee [employeeId=" + employeeId + ", name=" + name + ", practiceArea=" + practiceArea + ", designation=" + designation + ", companyInfo=" + companyInfo + "]";
}
}
这里没什么花哨的;我只是创建了几个员工,并将他们映射到休息URL。
现在让我们看看Spring Boot
文件:
package com.example.EmployeeSearchService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableDiscoveryClient
@SpringBootApplication
public class EmployeeSearchServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeSearchServiceApplication.class, args);
}
}
在这里,我使用@EnableDiscoveryClient
将此服务注册为Eureka
客户端。
现在,如果我点击这个http://localhost:8080/employee/find/1 我可以看到以下输出:
{
"employeeId":1,
"name":"Shamik Mitra",
"practiceArea":"Java",
"designation":"Architect",
"companyInfo":"Cognizant"
}
创建员工服务
现在,我将创建另一个使用员工搜索服务获取员工信息与员工搜索服务通信的服务。我将使用Feign
客户端,并使用Hystrix
作为断路器,因此如果员工搜索服务停机,它可以提供默认数据。
Pom.xml:
<?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.example</groupId>
<artifactId>EmployeeDashBoardService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>EmployeeDashBoardService</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.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>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</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>
bootstrap.properties:
spring.application.name=EmployeeDashBoard
spring.cloud.config.uri=http://localhost:9090
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8081
security.basic.enable: false
management.security.enabled: false
Feign client:
package com.example.EmployeeDashBoardService.controller;
import java.util.Collection;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.EmployeeDashBoardService.domain.model.EmployeeInfo;
@FeignClient(name = "EmployeeSearch")
@RibbonClient(name = "EmployeeSearch")
public interface EmployeeServiceProxy {
@RequestMapping("/employee/find/{id}")
public EmployeeInfo findById(@PathVariable(value = "id") Long id);
@RequestMapping("/employee/findall")
public Collection < EmployeeInfo > findAll();
}
控制器:
package com.example.EmployeeDashBoardService.controller;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.EmployeeDashBoardService.domain.model.EmployeeInfo;
@RefreshScope
@RestController
public class FeignEmployeeInfoController {
@Autowired
EmployeeServiceProxy proxyService;
@RequestMapping("/dashboard/feign/{myself}")
public EmployeeInfo findme(@PathVariable Long myself) {
return proxyService.findById(myself);
}
@RequestMapping("/dashboard/feign/peers")
public Collection < EmployeeInfo > findPeers() {
return proxyService.findAll();
}
}
Spring boot 启动服务:
package com.example.EmployeeDashBoardService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class EmployeeDashBoardService {
public static void main(String[] args) {
SpringApplication.run(EmployeeDashBoardService.class, args);
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
我们现在准备好了。如果我点击网址http://localhost:8081/dashboard/feign/1 我会看到以下回复:
{
"employeeId":1,
"name":"Shamik Mitra",
"practiceArea":"Java",
"designation":"Architect",
"companyInfo":"Cognizant"
}
创建网关服务
pom.xml:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<name>Spring Boot Starter Parent</name>
<description>Parent pom providing dependency and plugin management for applications
built with Maven</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<java.version>1.6</java.version>
<resource.delimiter>@</resource.delimiter>
<!-- delimiter that doesn't clash with Spring ${} placeholders -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<!-- Turn on filtering by default for application properties -->
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/application*.yaml</exclude>
<exclude>**/application*.properties</exclude>
</excludes>
</resource>
</resources>
<pluginManagement>
<plugins>
<!-- Apply more sensible defaults for user projects -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>${start-class}</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<archive>
<manifest>
<mainClass>${start-class}</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<delimiters>
<delimiter>${resource.delimiter}</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
</configuration>
</plugin>
<!-- Support our own plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>
<!-- Support shade packaging (if the user does not want to use our plugin) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.6.RELEASE</version>
</dependency>
</dependencies>
<configuration>
<keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${start-class}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
bootstrap.properties:
spring.application.name=EmployeeAPIGateway
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8084
security.basic.enable: false
management.security.enabled: false
zuul.routes.employeeUI.serviceId=EmployeeDashBoard
zuul.host.socket-timeout-millis=30000
在这里,请注意属性zuul.routes.employeeUI.serviceId=EmployeeDashBoard
。通过这一点,我们指示Zuul
,任何包含 employeeUI
的URL
都应重定向到EmployeeDashboard
服务。
启动文件:
package com.example.EmployeeZuulService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class EmployeeZuulServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeZuulServiceApplication.class, args);
}
}
现在,如果我运行该服务并点击http://localhost:8084/employeeUI/dashboard/feign/1 它会给我们以下回复:
{
"employeeId":1,
"name":"Shamik Mitra",
"practiceArea":"Java",
"designation":"Architect",
"companyInfo":"Cognizant"
}
在Docker容器中的部署
现在编码够了。让我们看看我们的应用程序是如何运行的。到这个时候,我们所有的服务都已准备就绪,并在本地机器上完美运行。但是,我们不希望我们的代码最终只在您的本地设置中运行。相反,我们希望看到它在生产中以优异的成绩运行。(我们像婴儿一样热爱我们的代码,我们希望看到它一直成功。)但是,当我们送孩子上学或引导他们走上正确的成功之路时,我们也需要指导我们的申请。因此,让我们搭车前往DevOps
世界,并尝试为我们的源代码提供正确的生产路径。
欢迎来到Docker世界
我假设您的机器上安装了Docker CE
。我们将在这里用于部署的概念如下:
- Dockerfile: 这是一个文本文档,包含构建
Docker
映像所需的所有说明。使用Dockerfile
的指令集,我们可以编写复制文件、进行安装等步骤。有关更多参考,请访问此链接。 - Docker Compose: 这是一个可以创建和生成多个容器的工具。它有助于使用单个命令构建所需的环境。
如微服务架构图所示,我们将为每项服务创建一个单独的容器。以下是我们示例的容器列表:
- Config Server
- EmployeeService
- Employee Board Service
- Employee Dashboard Service
- Gateway Service
配置服务器的Docker配置
容器应包含配置服务器jar文件。在这里,我们将从本地机器中选择jar文件。在现实生活中,我们应该将jar文件推送到Nexus
或Artifactory
等工件存储库管理器系统,容器应该从储存库管理器下载文件。
根据bootrap.properties
,配置服务器应该在端口8888上可用。
如上所述,我们将让配置服务器从文件位置读取配置,因此我们将确保即使容器出现故障,也可以检索这些属性文件。
创建一个名为config-repo
的文件夹,该文件夹将包含所需的属性文件。我们将确保Config Server
容器的以下内容。
# mkdir config-repo
# cd config-repo
# echo "service.employyesearch.serviceId=EmployeeSearch" > EmployeeDashBoard.properties
# echo "user.role=Dev" > EmployeeSearch.properties
回到父文件夹,创建一个名为Dockerfile
的Docker
文件。此Dockerfile
将创建我们的基础映像,其中包含Java
。
# cd ../
# vi Dockerfile
放入以下内容:
FROM alpine:edge
MAINTAINER javaonfly
RUN apk add --no-cache openjdk8
FROM: 此关键字告诉Docker使用带有标签的给定图像作为构建基础。
MAINTAINER: 维护者是图像的作者
RUN: 此命令将在系统中安装openjdk8。
执行以下命令以创建基本Docker映像:
docker build --tag=alpine-jdk:base --rm=true
成功构建基本映像后,是时候为配置服务器创建Docker映像了。
创建一个名为files
的文件夹,并将配置服务器jar文件放在目录中。然后,创建一个名为Dockerfile-configserver
的文件,其中包含以下内容:
FROM alpine-jdk:base
MAINTAINER javaonfly
COPY files/MicroserviceConfigServer.jar /opt/lib/
RUN mkdir /var/lib/config-repo
COPY config-repo /var/lib/config-repo
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/MicroserviceConfigServer.jar"]
VOLUME /var/lib/config-repo
EXPOSE 9090
在这里,我们提到了从之前创建的alpine-jdk
图像中构建图像。我们将在/opt/lib
位置复制名为 employeeconfigserver.jar
的jar
文件,并将config-repo
复制到/root
目录。当容器启动时,我们希望配置服务器开始运行,因此ENTRYPOINT
和CMD
设置为运行Java
命令。我们需要挂载一个卷来从容器外共享配置文件;VOLUME
命令帮助我们实现这一点。配置服务器应该可以通过端口9090
向外部世界访问;这就是为什么我们有EXPOSE 9090
。
现在让我们构建Docker image
,并将其标记为config-server
:
# docker build --file=Dockerfile-configserver --tag=config-server:latest --rm=true .
现在让我们创建一个Docker volume
:
# docker volume create --name=config-repo
# docker run --name=config-server --publish=9090:9090 --volume=config-repo:/var/lib/config-repo config-server:latest
一旦我们运行了上述命令,我们应该能够看到Docker容器并正在运行。如果我们转到浏览器并点击URL http://localhost:9090/config/default/ 我们也应该能够访问这些属性。
Eureka服务器
同样,我们需要为EurekaServer
创建一个Dockerfile
,该文件将在端口9091
上运行。Eureka Server
的Dockerfile
应如下:
FROM alpine-jdk:base
MAINTAINER javaonfly
COPY files/MicroserviceEurekaServer.jar /opt/lib/
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/MicroserviceEurekaServer.jar"]
EXPOSE 9091
要构建image
,请使用以下命令:
docker build --file=Dockerfile-EurekaServer --tag=eureka-server:latest --rm=true .
docker run --name=eureka-server --publish=9091:9091 eureka-server:latest
微服务
现在是时候部署我们实际的微服务了。步骤应该相似;我们唯一需要记住的是,我们的微服务依赖于ConfigServer
和EurekaServer
,因此我们总是需要确保在启动微服务之前,上述两个已经启动并运行。[容器之间存在依赖关系],所以是时候探索Docker Compose
了。这是一种很好的方法,可以确保生成的容器保持一定的顺序。
为此,我们应该为其余容器编写一个Dockerfile
。以下是Dockerfile
:
Dockerfile-EmployeeSearch.
================================
FROM alpine-jdk:base
MAINTAINER javaonfly
RUN apk --no-cache add netcat-openbsd
COPY files/EmployeeSearchService.jar /opt/lib/
COPY EmployeeSearch-entrypoint.sh /opt/bin/EmployeeSearch-entrypoint.sh
RUN chmod 755 /opt/bin/EmployeeSearch-entrypoint.sh
EXPOSE 8080
Dockerfile-EmployeeDashboard
====================================
FROM alpine-jdk:base
MAINTAINER javaonfly
RUN apk --no-cache add netcat-openbsd
COPY files/EmployeeDashBoardService.jar /opt/lib/
COPY EmployeeDashBoard-entrypoint.sh /opt/bin/EmployeeDashBoard-entrypoint.sh
RUN chmod 755 /opt/bin/EmployeeDashBoard-entrypoint.sh
EXPOSE 8080
Dockerfile-ZuulServer
=========================================
FROM alpine-jdk:base
MAINTAINER javaonfly
COPY files/EmployeeZuulService.jar /opt/lib/
ENTRYPOINT ["/usr/bin/java"]
CMD ["-jar", "/opt/lib/EmployeeZuulService.jar"]
EXPOSE 8084
这里需要注意的一件事是,我已经为Employee
和Employee Dashboard services
创建了两个shell
脚本。它指示Dockercompose
在配置服务器和Eureka
服务器启动之前不要启动Employee
和Employee Dashboard services
。
Employee dashBoard Script
#!/bin/sh
while ! nc -z config-server 9090 ; do
echo "Waiting for the Config Server"
sleep 3
done
while ! nc -z eureka-server 9091 ; do
echo "Waiting for the Eureka Server"
sleep 3
done
java -jar /opt/lib/EmployeeDashBoardService.jar
Employee service Script
#!/bin/sh
while ! nc -z config-server 9090 ; do
echo "Waiting for the Config Server"
sleep 3
done
while ! nc -z eureka-server 9091 ; do
echo "Waiting for the Eureka Server"
sleep 3
done
java -jar /opt/lib/EmployeeSearchService.jar
现在让我们创建一个名为docker-compose
的文件。它将使用所有这些dockerfile
生成我们所需的环境。它还将确保生成的所需容器保持正确的顺序,并且它们是相互链接的。
version: '2.2'
services:
config-server:
container_name: config-server
build:
context: .
dockerfile: Dockerfile-configserver
image: config-server:latest
expose:
- 9090
ports:
- 9090:9090
networks:
- emp-network
volumes:
- config-repo:/var/lib/config-repo
eureka-server:
container_name: eureka-server
build:
context: .
dockerfile: Dockerfile-EurekaServer
image: eureka-server:latest
expose:
- 9091
ports:
- 9091:9091
networks:
- emp-network
EmployeeSearchService:
container_name: EmployeeSearch
build:
context: .
dockerfile: Dockerfile-EmployeeSearch
image: employeesearch:latest
environment:
SPRING_APPLICATION_JSON: '{"spring": {"cloud": {"config": {"uri": "http://config-server:9090"}}}}'
entrypoint: /opt/bin/EmployeeSearch-entrypoint.sh
expose:
- 8080
ports:
- 8080:8080
networks:
- emp-network
links:
- config-server:config-server
- eureka-server:eureka-server
depends_on:
- config-server
- eureka-server
logging:
driver: json-file
EmployeeDashboardService:
container_name: EmployeeDashboard
build:
context: .
dockerfile: Dockerfile-EmployeeDashboard
image: employeedashboard:latest
environment:
SPRING_APPLICATION_JSON: '{"spring": {"cloud": {"config": {"uri": "http://config-server:9090"}}}}'
entrypoint: /opt/bin/EmployeeDashBoard-entrypoint.sh
expose:
- 8081
ports:
- 8081:8081
networks:
- emp-network
links:
- config-server:config-server
- eureka-server:eureka-server
depends_on:
- config-server
- eureka-server
logging:
driver: json-file
ZuulServer:
container_name: ZuulServer
build:
context: .
dockerfile: Dockerfile-ZuulServer
image: zuulserver:latest
expose:
- 8084
ports:
- 8084:8084
networks:
- emp-network
links:
- eureka-server:eureka-server
depends_on:
- eureka-server
logging:
driver: json-file
networks:
emp-network:
driver: bridge
volumes:
config-repo:
external: true
在下面的Docker
编写文件中,有几个重要的条目:
-
版本: 我们需要维护
Docker Compose
格式版本的必填字段。 -
服务: 每个条目都定义了我们需要生成的容器。
- build: 如果提到,那么
Docker Compose
应该从给定的Dockerfile
构建一个映像。 - image:将要创建的
image
的名称。 - networks: 要使用的网络名称。此名称应显示在网络部分。
- links : 这将在服务和上述服务之间创建内部链接。在这里,
EmployeeSearch
服务需要访问配置和Eureka
服务器。 - depents: 这是维持订单所必需的。
EmployeeSearch
容器依赖于Eureka
和Config Server
。因此,Docker
确保Eureka
和Config Server
容器在生成EmployeeSearch
容器之前生成。
- build: 如果提到,那么
创建文件后,让我们[构建image],创建所需的容器,并从单个命令开始:
docker-compose up --build
要停止完整的环境,我们可以使用此命令:
docker-compose down
Docker Compose
的完整文档可在此链接中找到。
总之,编写Dockerfile
和Docker Compose
文件是一次性活动,但它允许您随时按需生成完整的环境。
结论
这是关于如何在微服务中构建不同组件并将其部署到Docker
中的完整指南。在生产中,应该涉及CI/CD
,因此您不需要知道构建映像的所有Docker
命令,但作为全堆栈开发人员,了解如何在Docker
中创建和构建映像很重要。