Spring Cloud 微服务和 Docker 容器化技术,随便拿出来一个,都够你玩半天喝二两的。那么当它俩交叉在一起时,确实让新手烧脑。
开始本篇操作前,你要先在本地先成功安装 Docker,并能进行拉取镜像,启动、删除容器操作。我用的是 Mac 端的 Docker,用起来很方便。
好了,接下来才是重点。
书写一个微服务服务提供者
microservice-simple-provider-user,
一个微服务服务消费者
microservice-simple-consumer-shopping。
采用 Dockerfile 方式将其部署在 Docker 容器,后面做了高并发集群案例再考虑用 docker-compose,别着急一步步来。
SpringBoot 和 SpringCloud 版本要求挺严格,项目之间务必保持一致。
要点:
SpringCloud 微服务代码书写
采用 Dockerfile 方式部署微服务
一
微服务服务提供者
microservice-simple-provider-user
1 创建项目
1.1 IDEA new project,选择 Spring Initializr ,里面内容就默认了,
点击右下角 Next;
1.2 来到这个页面,完善你的项目元数据:
Group、Artifact、Name、Pacgage 我都做了简单修改;
Name 里面的值就是你的启动类的名字,自动驼峰命名去掉了横线;
其他的名字就不需要多解释了吧,再点击 Next。
1.3 选择基础的依赖
可以先忽略不管中间上面 SpringBoot 的版本,因为后面有手动修改;
一共选择了右侧的四个依赖。然后 Next;
这里选择了 DataJPA 和 H2 Database,是为了方便,直接就可用,没有其他配置,你也可以选择使用 Mybatis ...
1.4 选择项目位置,点击 Finish,项目创建完成。
2 写代码
2.1 pom.xml
首先,pom.xml 里已填满依赖信息。我做了两处修改:
一是第 8 行,修改 SpringBoot 版本:
<version>1.5.9.RELEASE</version>
二是第 19 行,修改 SpringCloud 的版本:
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
版本虽然老,但是比较可靠,就这么来吧伙计。
完整的 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ouc.isclab</groupId>
<artifactId>microservice-simple-provider-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>provider-user</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
2.2 填充项目基本模型
增加 controller,entity,repository, 以及 resources 资源:
2.2.1 新建 User 类
package ouc.isclab.microservice.entity;
import javax.persistence.*;
import java.math.BigDecimal;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String username;
@Column
private String name;
@Column
private Integer age;
@Column
private BigDecimal balance;
public User(){}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
2.2.2 新建 UserReposity 类,在 repository 包下
package ouc.isclab.microservice.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ouc.isclab.microservice.entity.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
函数里面啥也不需要写,因为简单嘛,就是这样简单的。
2.2.3 新建 UserController 类,在 controller 包下
package ouc.isclab.microservice.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import ouc.isclab.microservice.entity.User;
import ouc.isclab.microservice.repository.UserRepository;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class UserController {
@Autowired
public UserRepository userRepository;
// 地址栏直接接参数
@GetMapping("/user/{id}")
public User findUserById(@PathVariable Long id){ return userRepository.findOne(id); }
}
关于 RestController 与 Controller 注解的区别,可以看下这篇文章:
谈谈 @RestController 和 @Controller
此时它提供的访问路径就是 IP:端口号/user/具体的一个id。
2.2.4 新增 data.sql 和 schema.sql
别问为啥,就是 H2 Database 用着方便。
data.sql 的内容:
insert into user (id, username, name, age, balance) values (1, 'account1', '张三', 20, 100.00);
insert into user (id, username, name, age, balance) values (2, 'account2', '李四', 28, 180.00);
insert into user (id, username, name, age, balance) values (3, 'account3', '王五', 32, 280.00);
insert into user (id, username, name, age, balance) values (4, 'account3', '赵六', 33, 300.00);
schemal.sql 内容:
drop table user if exists;
create table user (id bigint generated by default as identity, username varchar(40), name varchar(20), age int(3), balance decimal(10,2), primary key (id));
2.2.5 修改 application.properties
将 application.properties 改名为 application.yml,加入内容:
server:
port: 8000
spring:
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: none
datasource: # 指定数据源
platform: h2 # 指定数据源类型
schema: classpath:schema.sql # 指定h2数据库的建表脚本
data: classpath:data.sql # 指定h2数据库的数据脚本
logging: # 配置日志级别,让hibernate打印出执行的SQL
level:
root: INFO
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
## INFO
info:
app:
name: @project.artifactId@
decoding: @project.build.sourceEncoding@
java:
source: @java.version@
target: @java.version@
2.2.6 关于启动类 ProviderUserApplication
无需改变。
此时通过启动类启动,可以成功启动。若提示 Test 类有错误,是测试类问题,可以先将其删除。
通过 localhost:8000/user/1 是可以查看到返回的数据结果的。
user/1,user/2 都是可以的。
此时说明项目是没问题的,将其停掉,可以准备部署。
3 准备部署
3.1 新建 Dockerfile
在项目的根目录,新建 Dockerfile,内容为:
FROM java:8
VOLUME /tmp
ADD target/microservice-simple-provider-user-0.0.1-SNAPSHOT.jar /app.jar
EXPOSE 8000
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
FROM:指定基础镜像。java:8 ,表示基于 jdk1.8 版本
VOLUME:授权访问从容器内到主机上的目录。如果不太清楚怎么写,就先写上 /tmp
ADD 《src》《destination》复制文件指令。src 是本地文件,destination 是容器内文件名字。上面的意思就是将本地 targer 目录下的 xxx.jar 包复制到容器内,命名为 /app.jar
本地文件目录尽量不要出现 ../ (上一级)这种形式,可能会出现找不到文件。好的方法是 jar 包和 Dockerfile 放在同一级目录
EXPOSE:暴漏的端口。和微服务的保持一致,防止混淆弄乱
ENTRYPOINT:你仔细品,有 java、-jar、app.jar ,是不是很像从终端命令行运行一个 jar 包的命令。没错就是了。中间那一串是一个随机数,加速启动。
3.2 制作 docker 镜像
有两种方法,一种是在 pom.xml 里面引入插件,打包和制作镜像一步完成,生成的镜像名字是通过 pom.xml 里面的插件格式规定的。另一种就是手动操作镜像名字版本。这儿我选择的手动操作设置,咱自己一步步来。
3.2.1 打 jar 包
在 IDEA 里面,通过 MAVEN-Lifecycle-pacgage,快速打包;或者使用命令行 mvn clean package(注意要在该项目的根目录下执行)。
此时在项目的 target 目录下就出现了该 jar 包。
成功后,在控制台会提示 "BUILD SUCCESS" 字样。
3.2.2 生成 docker 镜像
该项目基于 Java jdk1.8,所以要保证你的 Docker 有 java:8 这个镜像。没有的话先拉取一下:
docker pull java:8
然后生成该项目的镜像,因为 Dockerfile 在项目的根目录,因此在终端命令行 cd 到该根目录,执行:
docker built -t isclab/microservice-simple-provider-user:0.0.1 .
docker built -t 是基础的命令,isclab/microservice-simple-provider-user 是镜像名字,0.0.1 是版本号,最后的 . 不要忘记,表示容器的当前目录。
如果忘记了点,会在执行的时候提示参数问题,因为你漏掉了参数嘛。或者提示 unknown shorthand flag: 't' in -t ,也可能是这个问题。
执行命令正确的话,通过查看镜像 docker images ,可以看到该镜像已经存在了。如上图。
3.2.3 启动容器,访问
在终端输入
docker run -d -p 8000:8000 --name provider isclab/microservice-simple-provider-user:0.0.1
-d 后台运行
-p 指定映射端口
-- name 此时并用不着,是为了服务消费者而准备的
启动成功返回一个 ID 号。
此时输入 docker ps 查看已经启动的容器。
会发现该容器 NAMES 一栏的名字正好是我们的命名 provider。
此时再通过浏览器访问 localhost:8000/user/1 ,可以查找到数据。
微服务服务提供者到此就部署完成。
二
服务消费者
microservice-simple-consumer-shopping
1 创建项目
和服务提供者基本一样,项目元数据设置如下:
它并不需要直接调用数据库,因此在选择依赖时,可以只选择 Spring Web 和 Cloud Bootstrap。
直至点击 Finish 完成。
2 写代码
2.1 修改 pom.xml
和服务提供者一样,保持 SpringBoot 和 SpringCloud 版本的一致。
完整的 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ouc.isclab</groupId>
<artifactId>microservice-simple-consumer-shopping</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consumer-shopping</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
2.2 新增 controller 包、entity 包
2.2.1 新建 User 类,在 entity 包下
内容和服务提供者的 User 类一模一样。
2.2.2 新建 ShoppingController 类,在 controller 包下
提供访问的入口
package ouc.isclab.microservice.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import ouc.isclab.microservice.entity.User;
@RestController
public class ShoppingController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return this.restTemplate.getForObject("http://provider:8000/user/"+id, User.class);
}
}
此时,消费者调用提供者的路径已经不再是 localhost:8000/xxx,因为在不同的容器里面,localhost 是不通的。这时就用到了我们上面启动提供者容器时,命名的 --name provider。用 provider 代替 localhost:8000 即可。
2.2.3 修改启动类 ConsumerShopping
因为在 ShoppingController 中,通过 restTemplate 进行调用,因此需要在启动类用注解加以启用。
package ouc.isclab.microservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class ConsumerShoppingApplication {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerShoppingApplication.class, args);
}
}
2.2.4 修改 application.properties
将其重命名为 application.yml
server:
port: 8010
提供一个访问接口。
3 制作 docker 镜像
3.1 新建 Dockerfile
在项目根目录下新建 Dockerfile 文件,内容为:
FROM java:8
VOLUME /tmp
ADD target/microservice-simple-consumer-shopping-0.0.1-SNAPSHOT.jar /app.jar
EXPOSE 8010
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
和服务提供者很相似,不再赘述。
3.2 打 jar 包
和服务提供者一样。
当 Test 类有错误(或有其他错误)而没修改时,可能出现下面的错误:(惨败)最后的 BUILD FAILURE!就代表你已经输了。
3.3 生成 docker 镜像,启动容器
3.3.1 生成镜像
在终端,cd 该项目根目录,执行
docker build -t isclab/microservice-simple-consumer-shopping:0.0.1 .
生成镜像,可查看验证一下
3.3.2 启动容器,并查看
输入命令
docker run -d -p 8010:8010 --name consumer --link provider:provider isclab/microservice-simple-consumer-shopping:0.0.1
--link containerNAMES:name ,和其他容器进行通信。
containerNAMES : 被链接的容器的名字
name:在该项目中使用的名字
此时,在浏览器输入 localhost:8010/user/1 也可以查询到数据
这样,简单的微服务服务提供者、服务消费者的 Docker 部署就结束了。
整体的项目结构:
重点:两个容器的通信通过 --link 连接,关键还是要名字的相互对应,服务提供者容器的名字 和 服务消费者访问服务提供者的路径名字保持一致。
可以想象一下,如果再加一个微服务注册与发现,--name、--link 或许可以满足要求,但是微服务的注册与发现如果需要高集群该怎么做呢?
这个问题下篇文章通过 docker-compose 编排微服务来解决,这篇就这样了,拜拜各位。
感谢阅读,感谢陪伴!
项目的源代码地址:https://gitee.com/JeffBro/microservice-simple-provider-user
2020-06-05 该版代码是此次项目对应的代码