本分步攻略协助全栈开发人员运用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
中创立和构建映像很重要。