前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Docker 部署 SpringCloud 微服务(docker-compose 编排微服务高可用案例)

Docker 部署 SpringCloud 微服务(docker-compose 编排微服务高可用案例)

作者头像
子乾建建-Jeff
发布2020-06-29 15:26:33
7.3K2
发布2020-06-29 15:26:33
举报
文章被收录于专栏:iBroProiBroPro

前面的一篇文章,通过对每个项目建立单独的 Dockerfile,可以实现对单个项目生成 Docker 镜像,然后单独启动容器,可以实现简单连接,达到部署的目的。

但是微服务比较多的情况下,每次手动启动都比较麻烦;另外,还要考虑微服务的高可用(双节点或多节点)情况,通过上一篇文章(Docker 部署 SpringCloud 微服务的服务提供者和消费者(初级版))的方法解决起来有点麻烦。

针对上述情况,可采用 docker-compose 编排微服务。

说白了,就是通过 docker-compose 可以实现对多个微服务以及微服务高可用的编排,统一管理。

该篇文章涉及的项目及场景:

基于 Docker 实现 SpringCloud 微服务实例的容器化部署和运行,包含 5 个微服务实例。

  • 服务注册与发现组件(2个该组件容器,实现服务注册高可用)
  • 服务提供者(2个该容器,实现服务提供的高可用和负载均衡)
  • 服务消费者(1个该容器,通过服务注册与发现组件容器发现服务提供者容器,并实现负载均衡调用)

服务注册与发现

microservice-simple-eureka-discovery

项目的整体完整架构:

1 新建项目 microservice-simple-eureka-discovery

详细的创建过程可参考上一篇文章,(Docker 部署 SpringCloud 微服务的服务提供者和消费者(初级版))这里只贴出重点的地方。

项目的元数据部分:

选择依赖:

这里仍然只添加了基本的依赖,再需要的后续手动添加,肯定要添加 eureka-server 的啦。

最后 Next 直至 Finish,完成创建。

2 写代码,并验证正确性

2.1 修改 pom.xml

为了保证版本前后一致,这里的 SpringBoot 版本也修改为:

代码语言:javascript
复制
<version>1.5.9.RELEASE</version>

SpringCloud 版本修改为:

代码语言:javascript
复制
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>

同时,在里面添加 eureka-server 的依赖:

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

完整的 pom.xml 文件:

代码语言:javascript
复制
<?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-eureka-discovery</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-discovery</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.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</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 修改启动类 EurekaDiscoveryApplication

在启动类上面添加注解: @EnableEurekaServer

启动类代码:

代码语言:javascript
复制
package ouc.isclab.microservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaDiscoveryApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaDiscoveryApplication.class, args);
    }

}

2.3 修改 application.properties

将 application.properties 重命名为 application.yml,添加以下内容:

代码语言:javascript
复制
spring:
  application:
    name: microservice-discovery-eureka # 微服务实例的名称
# 第一个 eureka 环境
---
spring:
  profiles: peer1
server:
  port: 8761 # 开放的端口
eureka:
  instance:
    hostname: peer1
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://peer2:8762/eureka/  # 注册到另一个服务实例
    register-with-eureka: true
    fetch-registry: false
# 第二个 eureka 环境
---
spring:
  profiles: peer2
eureka:
  instance:
    hostname: peer2
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka/ # 注册到另一个服务实例
    register-with-eureka: true
    fetch-registry: false
server:
  port: 8762 # 开放的端口

这里在单机环境下模仿多台机器环境,因此配置了 peer1、peer2, 此配置需要在自己的电脑中修改操作系统的配置文件

Mac 和 Linux 电脑,路径在 /etc/hosts

通过 vim 编辑器直接添加即可,上图是我的添加完成的。

Windows系统的文件路径为 C:\Windows\System32\drivers\etc\hosts

第 5 行和第 20 行的 --- 表示一个单独的 eureka 环境配置,上面就是配置了两个 eureka 环境。

最上面不在任何一个 --- 里面的,表示多个 eureka 环境共用的环境配置。

代码语言:javascript
复制
spring:
  profiles: peer1

profiles 的值为你在 hosts 中设置的其中一个。

代码语言:javascript
复制
server:
  port: 8761 # 开放的端口

port 为该 spirng.profiles:XXX 主机下提供的端口

代码语言:javascript
复制
eureka:
  instance:
    hostname: peer1
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://peer2:8762/eureka/  # 注册到另一个服务实例
    register-with-eureka: true
    fetch-registry: false

hostname: 表示该主机的地址

prefer-ip-address: 表示微服务通过地址可达

defaultZone:表示注册的目标地址,peer1 和 peer2 之间互相注册

register-with-eureka:是否允许向 eureka server 注册信息

fetch-registry: 是否允许客户端向 eureka 注册表获取信息

测试了几次,这种配置能成功,先这么用着吧。

2.4 通过终端启动

项目打一个 jar 包,在终端命令行,输入命令:

代码语言:javascript
复制
java -jar xxx.jar --spring.profiles.active=peer1

启动报错不用慌,就像这种错误,

因为它和 peer2 相互注册,此时peer2 还没启动。只需另起一个终端,将 peer2 也启动一下:

代码语言:javascript
复制
java -jar xxx.jar --spring.profiles.active=peer2

此时,两个终端都可以正常启动,终端提示有一个 replicate = true,基本没啥毛病。

通过浏览器,输入 localhost:8761 或者 localhost:8762

有红线标注的说明服务注册发现组件没问题。可以准备打包。

停掉服务,开始打包。

3 生成镜像,准备启动

在 src/main/ 路径下新建 docker 文件夹,并新建 Dockerfile 文件、docker-compose.yml 文件。

需要你的docker 已拉取 java:8 镜像,如果还没有,打开终端执行:

代码语言:javascript
复制
docker pull java:8

3.1 项目打 jar 包

在项目根目录下执行 mvn clean package,或者在 IDEA 中,依次点击MACEN-Lifecycle-package ,在 target 目录下得到 xxx.jar。

3.2 使用 Dockerfile 制作 Docker镜像

注意这里的 jar 包位置。将 target 目录下生成的 jar 包拷贝在src/main/docker 目录下。在 Dockerfile 中写入以下内容:

代码语言:javascript
复制
# 基于哪个镜像
FROM java:8
# 将本地文件夹挂载到当前容器
VOLUME /tmp
# 拷贝文件到容器
ADD microservice-simple-eureka-discovery-0.0.1-SNAPSHOT.jar /app.jar
ENTRYPOINT  ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

在终端 cd 到src/main/docker 目录,执行命令:

代码语言:javascript
复制
docker build -t isclab/eureka-discovery:0.0.1 .

再通过 docker images 可以发现,镜像已经存在。

现在可以去书写 docker-compose.yml。

3.3 使用 docker-compose.yml 编排微服务,启动

首先要在本地安装 docker-compose,根据官方来就行。Mac 安装了 docker 后自带了 docker-compose,我就可以直接使用。

可以通过查看版本看本地是否安装了:1.25.5 是 compose 的版本

在 docker-compose.yml 里面写入:

代码语言:javascript
复制
version: '3'
services:
  peer1:
    hostname: peer1
    image: isclab/eureka-discovery:0.0.1
    ports:
      - 8761:8761
    environment:
      - spring.profiles.active=peer1

  peer2:
    hostname: peer2
    image: isclab/eureka-discovery:0.0.1
    ports:
      - 8762:8762
    environment:
      - spring.profiles.active=peer2

version:是根据 docker 版本而写的,规定compose file 的格式。

下图是一个对应关系,去官网链接可以根据自己的 docker 版本,查看自己的用哪一个。

官网:https://docs.docker.com/compose/compose-file/

我的是19.03.8,因此使用 3 是完全可以的

services:服务。这个地方的名字其实可以随便命名,当这两者相互注册的,如果随便起一个名字,会造成死循环注册。报错:

代码语言:javascript
复制
ERROR:Circular dependency between 名字1 and 名字2

在这里就用 peer1、peer2 方案来解决。

hostname: 主机地址

iamge:连接的对应的镜像名字,必须和你生成的镜像一模一样,版本号也一定要加上(默认latest,因为之前我们加了0.0.1,没有latest)

ports: 就是暴漏端口映射。保持一致,不易混淆。

environment:必须加,就是我们通过命令行启动的命令。

做完上面的工作,就可以在终端命令行 cd 到src/main/docker 目录下,执行:

代码语言:javascript
复制
docker-compose up -d

-d:仍然表示后台执行

此时,通过 docker ps,可看到:

因为我们并没有给容器起名字,所以,默认了 docker_xxx_x 的名字。

此时,去浏览器,输入 localhost:8761 或者 8762 也都是可以访问成功的。

服务提供者

microservice-simple-provider-user

项目完整结构:

1 新建项目 microservice-simple-provider-user

填写项目元数据:

选择依赖:

先选择这么多,后面手动补。

2 写代码,并验证正确性

同样注意版本问题。

2.1 修改 pom.xml

为了保证版本前后一致,这里的 SpringBoot 版本也修改为:

代码语言:javascript
复制
<version>1.5.9.RELEASE</version>

SpringCloud 版本修改为:

代码语言:javascript
复制
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>

同时,在里面添加 eureka-client 的依赖:

代码语言:javascript
复制
<!-- Eureka -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

完整的 pom.xml 文件内容:

代码语言:javascript
复制
<?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>
        <!-- Eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</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 包

在包下面新建对应的类,entity/User 类:

代码语言:javascript
复制
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;
    }
}

repository/UserRepository 类:

代码语言:javascript
复制
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> {

}

controller/UserController 类:

代码语言:javascript
复制
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); }

}

2.3 启动类 ProviderUserApplication

无需修改

2.4 修改 application.properties

重命名为 application.yml,内容如下:

代码语言:javascript
复制
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数据库的数据脚本
  application:
    name: microservice-simple-provier-user
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@

# eureka
eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka/,http://peer2:8761/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false

---
spring:
  profiles: peer1
server:
  port: 8000
---
spring:
  profiles: peer2
server:
  port: 8001

defaultZone: 表示服务注册发现组件地址,也就是前面写的项目

management:SpringBoot 1.5.X 以上默认开通了安全认证,如果不关闭会要求权限,在此把它关闭

---:表示单独配置一个实例

此配置文件中,表示 peer1 的 8000 端口;peer2 的 8001 端口。

借此实现服务提供者的高可用。

2.5 新增 sql 文件

因为使用的 H2 Database,很方便测试用。此处与MySQL等略微不同。

在 resources 下面添加 data.sql 和 schema.sql。

data.sql:

代码语言:javascript
复制
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);

schema.sql:

代码语言:javascript
复制
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));

到此项目写完,因为它需要注册到服务提供与发现端,在这里不再单独测试。

3 生成镜像,准备测试启动

3.1 项目打 jar 包

在 target 目录下找到项目的 jar 包。

3.2 使用 Dockerfile 制作 Docker镜像

在项目的根目录下新建 Dockerfile 文件,内容:

代码语言:javascript
复制
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"]

执行命令构建镜像:(不要忘记最后的 . )

代码语言:javascript
复制
docker build -t isclab/microservice-simple-provider-user:0.0.1 .

可看到已经成功制作了镜像,通过 docker run 可以启动试一下。

此时通过 --link 去链接服务注册发现组件容器,发现并不成功。

代码语言:javascript
复制
docker run -d -p 8000:8000 --name provider --link docker_peer1_1:peer1 --link docker_peer2_1:peer2 isclab/microservice-simple-provider-user:0.0.1
代码语言:javascript
复制
提示错误信息:
docker: Error response from daemon: Cannot link to /docker_peer1_1, as it does not belong to the default network.

因为不在同一网络,那这时就可以设想通过 docker-compose 来解决。

3.3 使用 docker-compose.yml 编排微服务,启动

此时,已经涉及两个项目(服务注册与发现、服务提供者),因此,我在其他文件夹新建一个 docker-compose 文件夹,在该文件夹下新建 docker-compse.yml,这是一个综合管理的 yml 文件,来管理这两个项目。

docker-compose.yml 的内容为:

代码语言:javascript
复制
version: '3'
services:
  peer1:
    hostname: peer1
    image: isclab/eureka-discovery:0.0.1
    container_name: docker_peer1_1
    ports:
      - 8761:8761
    environment:
      - spring.profiles.active=peer1

  peer2:
    hostname: peer2
    image: isclab/eureka-discovery:0.0.1
    container_name: docker_peer2_1
    ports:
      - 8762:8762
    environment:
      - spring.profiles.active=peer2
  provider-1:
    hostname: peer1
    image: isclab/microservice-simple-provider-user:0.0.1
    container_name: provider-1
    ports:
      - 8000:8000
    environment:
      - server.port=8000
  provider-2:
    hostname: peer2
    image: isclab/microservice-simple-provider-user:0.0.1
    container_name: provider-2
    ports:
      - 8001:8001
    environment:
      - server.port=8001

provider-1、provider-2: 服务提供者1、服务提供者2(高可用)

environment: 命令行启动时的格式

image:镜像名字

此时,可以先去停掉、删除之前启动的服务注册与发现的容器和镜像。

然后在该 docker-compose.yml 的根目录下执行:

代码语言:javascript
复制
docker-compose up -d

此时,会出现Creating .... done 。

通过 docker ps 可看到 4 个容器均已启动,通过浏览器访问 localhost:8761,

这儿仅显示了 8001 ,没有 8000。但是 8000 和 8001 端口都可以访问到。回看 Docker 容器,8000/tcp,8001 都对应了 8001,我猜测和这儿有关系。没有去特意解决该问题。

访问 localhost:8000/user/1 或者 localhost:8001/user/2 都可以访问到数据。

到此,服务注册与发现、服务提供者两个项目均部署完成,接下来就是服务消费者。现在累了吧,累了就歇歇,再干!

服务消费者

microservice-simple-consumer-shopping

整体项目结构:

1 新建项目 microservice-simple-consumer-shopping

设置项目元数据:

选择项目依赖:

点击Next,直至 Finish 完成。

2 写代码

2.1 修改 pom.xml

为了保证版本前后一致,这里的 SpringBoot 版本也修改为:

代码语言:javascript
复制
<version>1.5.9.RELEASE</version>

SpringCloud 版本修改为:

代码语言:javascript
复制
<spring-cloud.version>Edgware.RELEASE</spring-cloud.version>

同时,在里面添加 eureka-client 的依赖:

代码语言:javascript
复制
<!-- Eureka -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

完整的 pom.xml 内容:

代码语言:javascript
复制
<?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>
        <!-- Eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</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

entity/User 类:

代码语言:javascript
复制
package ouc.isclab.microservice.entity;

import java.math.BigDecimal;

public class User {

    private Long id;
    private String username;
    private String name;
    private Integer age;
    private BigDecimal balance;

    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;
    }
}

controller/ShoppingController 类:

代码语言:javascript
复制
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://microservice-simple-provier-user/user/"+id, User.class);
    }
}

注意:就是这儿的地址,指向的是服务提供者,写服务提供者的服务名字即可。

该名字是在每个项目的 application.yml 里面 spring.application.name 指定的。

2.3 修改启动类 EurekaDiscoveryApplication

因为 controller 里面使用了 restTemplate,需要在这里添加,并加上注解 @Bean。

另外为了负载均衡,需要在这里加 @LoadBalanced 注解。

(因为加入了 eureka-client 依赖,里面包含负载均衡的依赖,不需要单独再添加依赖)

最后,由于服务提供者做了高可用,因此服务消费者必须要通过负载均衡访问,否则,在通过消费者访问时,会报 unknowhostnameException 异常。

完整的启动类内容:

代码语言:javascript
复制
package ouc.isclab.microservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class ConsumerShoppingApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerShoppingApplication.class, args);
    }

}

2.4 修改 application.properties

重命名为 application.yml,内容为:

代码语言:javascript
复制
server:
  port: 8010
spring:
  application:
    name: microservice-consumer-shopping
# eureka
eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka/,http://peer2:8761/eureka/
  instance:
    prefer-ip-address: true
management:
  security:
    enabled: false

提供 8010 端口

名字为 microservice-consumer-shopping

注册进服务发现端 peer1/8761、peer2/8762

3 生成镜像,准备启动

3.1 项目打 jar 包

在命令行 cd 到项目根目录,执行mvn clean package,或在 IDEA 里面 通过 MAVEN-Lifecycle-package,在 target 目录下得到 jar 包。

3.2 使用 Dockerfile 制作 Docker镜像

在项目根目录新建 Dockerfile 文件:

代码语言:javascript
复制
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"]

并执行命令:

代码语言:javascript
复制
docker build -t isclab/microservice-simple-consumer-shopping:0.0.1 .

得到镜像,通过 docker ps 可以查看

此时三个镜像全都存在,没有问题。通过 docker-compose 进行启动。

3.3 使用 docker-compose.yml 编排微服务,启动

仍然是找到上次写的综合管理服务注册发现、服务提供的 docker-compose 文件,在里面加入服务消费者的启动信息。

代码语言:javascript
复制
version: '3'
services:
  peer1:
    hostname: peer1
    image: isclab/eureka-discovery:0.0.1
    container_name: docker_peer1_1
    ports:
      - 8761:8761
    environment:
      - spring.profiles.active=peer1

  peer2:
    hostname: peer2
    image: isclab/eureka-discovery:0.0.1
    container_name: docker_peer2_1
    ports:
      - 8762:8762
    environment:
      - spring.profiles.active=peer2
  provider-1:
    hostname: peer1
    image: isclab/microservice-simple-provider-user:0.0.1
    container_name: provider-1
    ports:
      - 8000:8000
    environment:
      - server.port=8000
  provider-2:
    hostname: peer2
    image: isclab/microservice-simple-provider-user:0.0.1
    container_name: provider-2
    ports:
      - 8001:8001
    environment:
      - server.port=8001
  consumer:
    image: isclab/microservice-simple-consumer-shopping:0.0.1
    ports:
      - 8010:8010

尾部的 consumer 则表示服务消费者, iamge 对应刚刚制作的镜像,暴漏端口 8010。

现在该文件就变成了一个综合管理服务的 docker-compose 配置文件。

此时在命令行 cd 到 这个综合管理服务的 docker-compose 文件夹,执行

代码语言:javascript
复制
docker-compose up -d

此时,可以看到前四个容器都是 up-to-date,因为之前已经启动了。

消费者容器显示 done,通过 docker images 可以看到容器已全部启动。

访问 localhost:8761 或 8762,

通过消费者查询信息,localhost:8010/user/1 , user/2 可查询到信息

至此,服务注册发现、服务提供者、服务消费者均通过 Docker 部署完成。完事,拜拜!

其实不难,关键在于弄清楚对应关系,记下来。

项目源码:

服务注册与发现eureka:

https://gitee.com/JeffBro/microservice-simple-eureka-discovery

服务提供者provider-user:

https://gitee.com/JeffBro/microservice-simple-provider-user

服务消费者:

https://gitee.com/JeffBro/microservice-simple-consumer-shopping


错误与技巧

  • 在 docker-compose.yml 文件里面,container_name 不可重复,刚开始想通过该名字使服务消费者访问服务提供者,但发现错了。哈哈..
  • 通过 docker-compose 编排,消费者调用服务者时,仍是通过提供者的spring.application.name 调用,不需要在 yml 里面写 --links;如果写了,会报错没有容器名字;错误如下:
代码语言:javascript
复制
Service 'consumer has a link to service 'xx:xx' which is undefined

这是测试中的一个例子,我的前后名字当时没写错。(别杠我

  • docker-compose.yml 里面,高可用的实例不要忘记了 environment 。
  • 注意 application.yml 写法以及 docker-compose.yml 的写法,以及对应关系;
  • 此次操作时每个项目单独生成镜像(通过 Dockerfile),然后统一管理(通过 docker-compose)。当然也可以全部通过 docker-compose 来管理,你可以试试。
  • docker-compose 文件夹下不一定必须包含 jar 包,此时只是执行启动容器,保证镜像已存在就可以了。
  • 服务提供者一旦提供了多个可访问节点,服务消费者必须配置负载均衡。否则会报 unhostnameexception.

感谢阅读,感谢陪伴,这篇文章快写吐了.....

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iBroPro 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档