在 Docker 上运行一个 RESTful 风格的微服务

tags: Microservice Restful Docker

Author: Andy Ai

Weibo:NinetyH

GitHub: https://github.com/aiyanbo/docker-restful-demo

实现构思

1. 使用 Maven 进行项目构建

2. 使用 Jersey 实现一个 RESTful 风格的微服务

3. 在 Docker 里面执行 mvn package 对项目打包

4. 在 Docker 容器里运行这个微服务

实现一个微服务

场景 & 需求

在 Maven 仓库里面有许多的组件,我们现在暂且称之为 Stack 。在我们模拟的系统里面有下面2个需求:

1. 列出仓库里的所有 Stack

2. 根据 Stack
ID
找到某一个组件,如果没有找到则返回 Not Found

现在,我们就根据这个需求一起踏入 Jersey 打造微服务的奇幻之旅。

Step0. 准备

使用 mvn
命令创建一个简单工程

mvnmvn archetype:generate -DgroupId=org.jmotor -DartifactId=docker-restful-demo -DinteractiveMode=false

pom.xml 加入 Jersey 等依赖

xml<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <junit.version>4.12</junit.version>
  <jersey.version>2.18</jersey.version>
  <javax.servlet.version>3.1.0</javax.servlet.version>
</properties>
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-grizzly2-http</artifactId>
    <version>${jersey.version}</version>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey.version}</version>
  </dependency>
</dependencies>

Step1. 构建 Model

Stack 包含了以下几个属性: id , groupId , artifactId , version 。同时,Stack 类里面包含了一个 Builder 用来比较方便地创建一个 Stack 对象。这些都可以使用 IDE 自动生成,无需手动编写。

javapackage org.jmotor.model;
/**
 * Component:
 * Description:
 * Date: 2015/6/18
 *
 * @author Andy Ai
 */
public class Stack {
  private Integer id;
  private String groupId;
  private String artifactId;
  private String version;
  ...getter and setter...
  public static class Builder {
    private Integer id;
    private String groupId;
    private String artifactId;
    private String version;
    public Builder id(Integer id) {
      this.id = id;
      return this;
    }
    ...
    public Stack build() {
      Stack stack = new Stack();
      stack.setId(id);
      ...
      return stack;
    }
    public static Builder newBuilder() {
      return new Builder();
    }
  }
}

Step2 创建一个 Restlet

刚刚我们已经把 Model 做好了,现在我们就开始使用 Jersey 实现一个 Service,在 JAX-RS 中这样的一个服务称为 Resource。在这里,这个 Service(Resource) 提供了一个 RESTful 风格的接口访问。所以我们称之为 restletrestlet 在 JAX-RS 或 Jersey 中并没有这个概念,这是我们附加上去的用法。

javaimport javax.ws.rs.Path;

@Path("/v1/stacks")
public class StacksRestlet {}

我们需要使用 javax.ws.rs.Path 这个注解来申明 Restlet 的根路径是什么。在上面的代码中, Restlet 的跟路径是 /v1/stacks

Step3. 实现服务接口

在 Jersey 里实现一个服务接口非常简单,你只需要创建一个 public 的方法就可以了。

接口1:

java@GET
@Produces("application/json")
public List<Stack> stacks() {
    return Arrays.asList(
            Stack.Builder.newBuilder().id(1).groupId("javax.servlet").artifactId("javax.servlet-api").version("3.1.0").build(),
            Stack.Builder.newBuilder().id(2).groupId("com.google.guava").artifactId("guava").version("18.0").build()
    );
}

我们使用 javax.ws.rs.GET 这个注解来申明接口接受的是 HTTP 请求的 GET 方法。 javax.ws.rs.Produces("application/json") 用来表示我们这个接口返回的是 application/json 类型的数据。

接口2:

java@GET
@Path("{id}")
@Produces("application/json")
public Stack filterByArtifactId(@NotNull @PathParam("id") Integer id) {
  switch (id) {
    case 1:
      return Stack.Builder.newBuilder().id(1).groupId("javax.servlet").artifactId("javax.servlet-api").version("3.1.0").build();
    case 2:
      return Stack.Builder.newBuilder().id(2).groupId("com.google.guava").artifactId("guava").version("18.0").build();
    default:
      throw new WebApplicationException("Stack not found, id: " + id, 404);
  }
}

在上面的示例中:

1. @Path("{id}") 表示 id 是一个 url 上的动态参数,因为 id 是变化的,所以我们要做成一个 url 变量。然后在方法里面使用 @PathParam("id") 来获得这个参数。JAX-RS 可以有许多类型的参数,例如: QueryParam 用来获取 url 问号 ? 后面的查询参数。你可以在 javax.ws.rs 这个包中找到其他的参数!

2. 使用 WebApplicationException 抛一个任何状态的异常,例如: 404 或 500。

Step4. 运行微服务

这里,我们使用 Jersey 的内置的 Grizzly 容器运行。

javafinal URI uri = UriBuilder.fromUri("http://localhost/").port(9998).build();
final ResourceConfig config = new ResourceConfig();
config.packages("org.jmotor.restlet");
final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri, config);
Runtime.getRuntime().addShutdownHook(new Thread() {
  @Override
  public void run() {
    server.shutdown();
  }
});
try {
  server.start();
} catch (IOException e) {
  e.printStackTrace();
  System.exit(1);
}

Step5. 测试

获取 Stacks 列表

bash$ curl http://localhost:9998/v1/stacks
[{"id":1,"groupId":"javax.servlet","artifactId":"javax.servlet-api","version":"3.1.0"},{"id":2,"groupId":"com.google.gua
va","artifactId":"guava","version":"18.0"}]

根据 ID 获取 Stack

bash$ curl http://localhost:9998/v1/stacks/1
{"id":1,"groupId":"javax.servlet","artifactId":"javax.servlet-api","version":"3.1.0"}

找不到的 Stack

bash$ curl -I http://localhost:9998/v1/stacks/5
HTTP/1.1 404 Not Found
Content-Length: 0
Date: Tue, 23 Jun 2015 06:04:19 GMT

在 Docker 中运行

首先,我们需要安装一个 Docker 环境,你可以从 Docker Docs 上找到如何安装它。

安装完成后,我们需要把我们的微服务打包成一个 Docker Image 。下面,我们就使用 Dockerfile 来构建我们的 Docker Image。

Step0. 准备

刚刚我们已经成功地在 IDE 中运行了我们的微服务。但是如果需要让它能独立运行,我们需要把我们的工程通过 mvn 做成一个可以运行的包。但是因为我们在 Docker 中运行,所以我们只需要把相关的依赖复制到一个特地的地方就可以了。在下面的代码中,我们把依赖放到了 ${project.build.directory}/lib 下。

pom.xml 加入下面的代码:

xml<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-dependencies</id>
          <phase>package</phase>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <configuration>
            <excludeScope>provided</excludeScope>
            <outputDirectory>${project.build.directory}/lib</outputDirectory>
          </configuration>
        </execution>
      </executions>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>1.7</source>
        <target>1.7</target>
      </configuration>
    </plugin>
  </plugins>
</build>

Step1. Dockerfile

DockerfileFROM jamesdbloom/docker-java8-maven

MAINTAINER Andy Ai "yanbo.ai@gmail.com"

WORKDIR /code

ADD pom.xml /code/pom.xml
ADD src /code/src
ADD settings.xml /root/.m2/settings.xml

RUN ["mvn", "package"]

CMD ["java", "-cp", "target/lib/*:target/docker-restful-demo-1.0-SNAPSHOT.jar", "org.jmotor.StackMicroServices"]

EXPOSE 9998

Tips

1. ADD settings.xml /root/.m2/settings.xml ,这是加入了本地的 maven settings。在这个文件里面你可能会使用到一些特定的配置,例如:maven 仓库的代理镜像。代理镜像可以加快你的 Docker Build。

2. EXPOSE 9998 Docker 对外暴露的端口需要跟服务的端口是一致的。

Step2. Build Image

bashcd docker-restful-demo
docker build -t docker-restful-demo .

上面代码中, -t 是在 Docker Build 的时候指定 Image Tag。

Step3. 运行 Image

bashdocker run -d -p 9998:9998 docker-restful-demo

Tips

-p 是发布一个 Docker 容器的端口到 Docker 运行的主机上。

Step4. Docker 容器测试

检查 Docker 容器是否在运行

bash$ docker ps
CONTAINER ID        IMAGE                       COMMAND                CREATED             STATUS              PORTS
                NAMES
bdda2408484a        docker-restful-demo:latest   "java -cp target/lib   31 seconds ago      Up 29 seconds       0.0.0.0:9
998->9998/tcp   fervent_swartz

检查 Docker 容器内的服务是否已经启动:

  • 登录到 Docker 容器:
bashdocker exec -i -t bdda2408484a bash
  • 查看服务端口信息
bash$ ss -a
Netid  State      Recv-Q Send-Q                       Local Address:Port                           Peer Address:Port
nl     UNCONN     0      0                                     rtnl:kernel                                     *
nl     UNCONN     4352   0                                  tcpdiag:ss/92                                      *
nl     UNCONN     768    0                                  tcpdiag:kernel                                     *
nl     UNCONN     0      0                                        6:kernel                                     *
nl     UNCONN     0      0                                       10:kernel                                     *
nl     UNCONN     0      0                                       12:kernel                                     *
nl     UNCONN     0      0                                       15:kernel                                     *
nl     UNCONN     0      0                                       16:kernel                                     *
u_str  ESTAB      0      0                                        * 9590                                      * 0
tcp    LISTEN     0      128                       ::ffff:127.0.0.1:9998                                     :::*
  • 测试接口
bash$ curl -i http://localhost:9998/v1/stacks
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 23 Jun 2015 07:51:15 GMT
Content-Length: 163

[{"id":1,"groupId":"javax.servlet","artifactId":"javax.servlet-api","version":"3.1.0"},{"id":2,"groupId":"com.google.gua
va","artifactId":"guava","version":"18.0"}]
  • 退出 Docker 容器
bashexit

Step5. 远程调用测试

  • 如果你使用的是 boot2docker, 需要拿到 boot2docker 虚拟机的IP
bash$ boot2docker ip
192.168.59.103
  • 调用远程接口
bash$ curl http://192.168.59.103:9998/v1/stacks
curl: (7) Failed to connect to 192.168.59.103 port 9998: Connection refused

如果遇到上面的错误,我们可以通过2种方式去解决它:

方法1: 将程序绑定全零IP的端口

检查 Docker 容器绑定的端口:

bash$ docker port bdda2408484a
9998/tcp -> 0.0.0.0:9998

我们看到的是 9998 这个端口绑定在 0.0.0.0 上,这时需要把 Jersey 容器的 URI 改成 0.0.0.0 就可以,像这样:

javafinal URI uri = UriBuilder.fromUri("http://localhost/").port(9998).build();

--->

final URI uri = UriBuilder.fromUri("http://0.0.0.0/").port(9998).build();

方法2: 将程序绑定到非回路的IP端口上

查看 Docker 容器 IP 地址的方法:

bashboot2docker ssh

docker inspect --format ‘{{.NetworkSettings.IPAddress}}‘ $container_id

使用 Java 接口获取本机的非回路IP地址:

javafinal URI uri = UriBuilder.fromUri("http://localhost/").port(9998).build();
--->
InetAddress inetAddress = localInet4Address();
String host = "0.0.0.0";
if (inetAddress != null) {
  host = inetAddress.getHostAddress();
}
final URI uri = UriBuilder.fromUri("http://" + host + "/").port(9998).build();
private static InetAddress localInet4Address() throws SocketException {
  Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
  while (networkInterfaces.hasMoreElements()) {
    NetworkInterface networkInterface = networkInterfaces.nextElement();
    Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
    while (inetAddresses.hasMoreElements()) {
      InetAddress inetAddress = inetAddresses.nextElement();
      if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
        return inetAddress;
      }
    }
  }
  return null;
}

使用上面的任何一种方法,服务都能正常调用:

bash$ curl -i http://192.168.59.103:9998/v1/stacks
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 23 Jun 2015 07:53:24 GMT
Content-Length: 163

[{"id":1,"groupId":"javax.servlet","artifactId":"javax.servlet-api","version":"3.1.0"},{"id":2,"groupId":"com.google.gua
va","artifactId":"guava","version":"18.0"}]

加速器

在撰写此文的时候,我使用的是 DaoColud 的镜像在做加速。 具体步骤请查看 DaoColud Mirror 文档。

如果你在 Windows 上使用 Boot2Docker, 可以按照下列步骤设置你的 Docker 镜像仓库:

bashboot2docker ssh

sudo su
echo "EXTRA_ARGS=\"--registry-mirror=http://98bc3dca.m.daocloud.io\"" >> /var/lib/boot2docker/profile
exit

boot2docker restart

参考资料

https://dashboard.daocloud.io/mirror

http://martinfowler.com/articles/microservices.html

https://jersey.java.net/documentation/latest/index.html

https://blog.giantswarm.io/getting-started-with-java-development-on-do...

时间: 2024-10-08 06:52:20

在 Docker 上运行一个 RESTful 风格的微服务的相关文章

spring boot 快速搭建 基于 Restful 风格的微服务

使用 spring boot 快速搭建 基于  Restful 风格的微服务, 无spring 配置文件,纯java 工程,可以快速发布,调试项目 1.创建一个maven 工程 2. 导入如下配置 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="htt

搭建一个RESTFUL风格的Web Service (Maven版本)

[该教程翻译自Spring官方,并进行适当删减.] 你将搭建的 你将搭建一个可以接受Http Get 请求的web service, http://localhost:8080/greeting 并将以JSON字符串的形式返回问候, {"id":1,"content":"Hello, World!"} 工具 一个文本编辑器,JDK1.6及以上,Maven 3.0+或者Gradle 1.11+.(本文将使用Maven) 下面是pom.xml文件的清

通过beego快速创建一个Restful风格API项目及API文档自动化(转)

通过beego快速创建一个Restful风格API项目及API文档自动化 本文演示如何快速(一分钟内,不写一行代码)的根据数据库及表创建一个Restful风格的API项目,及提供便于在线测试API的界面. 一.创建数据库及数据表(MySQL) #db--jeedev -- ---------------------------- -- Table structure for `app` -- ---------------------------- DROP TABLE IF EXISTS `a

在Windows版本Docker上运行GUI程序

看到很多人在Docker问题区讨论:如何在OS X和Windows的Docker上运行GUI程序, 随手记录几个参考资料: https://github.com/docker/docker/issues/8710 http://sourceforge.net/projects/xming/ https://www.gitbook.com/book/yeasy/docker_practice/details

DOCKER上运行DOTNET CORE

原文:DOCKER上运行DOTNET CORE 下载microsoft/dotnet镜像 运行命令: docker pull microsoft/dotnet 如果没有使用阿里镜像加速的,参照这篇先配置好再跑上面命令: http://www.cnblogs.com/windchen/p/6231009.html 启动持久化容器 docker run -itd -p 5000:5000 microsoft/dotnet -p用来添加Host跟Container的端口映射 创建.NET Core M

K8S 之使用DaemonSet在每个节点上运行一个POD

一.DaemonSet作用 Relicationcontroller和ReplicaSet都用于在Kubernetes集群上运行部署特定数量的pod.但是,当你希望pod在集群中的每个节点上运行时,就可使用DaemonSet. apiVersion: apps/v1beta2 kind: DaemonSet metadata: name: ssd-monitor namespace: test spec: selector: matchLabels: app: ssd-monitor templ

RESTful风格的Web服务框架:Swagger

Swagger与SpringMVC项目整合 为了方便的管理项目中API接口,在网上找了好多关于API接口管理的资料,感觉目前最流行的莫过于Swagger了,功能强大,UI界面漂亮,并且支持在线测试等等,所以本人仔细研究了下Swagger的使用,下面就如何将Swagger与个人的SpringMVC项目进行整合做详细说明: 最终API管理界面:  详细步骤: Step1:项目中引入相关jar包: <properties> <project.build.sourceEncoding>UT

15年资深架构师详解:一个大型互联网公司的微服务转型实践

微服务是一个比较大的话题,基于我的过往经历,本文将以 Netflix 为例,分享一个大型互联网公司如何从一个 Monolithic 的 APP 成功转型到微服务.文章主要涉及微服务的产生历史,应用场景,与单片服务区别,微服务带来的技术.企业组织结构等方面挑战,以及如何合理地选择单片服务构架和微服务构架等内容. 微服务的产生历史 如下图,是微服务在 Google 的搜索结果: 自 2014 年以来,微服务开始被关注,搜索的人越来越多,并在 2016 年左右达到顶峰.从地域来看,很多国家都在关注,如

一个最简单的微服务架构

前言 微服务架构一般会有一个开放网关作为总入口,负责分发流量到实际的应用服务上.下面看图. 架构图 项目结构 这个架构分别由反向代理nginx,注册中心zookeeper,开放网关gateway,和两个服务goodservice,priceservice组件而成.为了方便测试,我把建了两个一样的gateway和goodservice.而common作为公共的二方包存在,也是为了简单起见,gateway和service引用同一个二方包. nginx nginx除了作为反向代理,也具有负载均衡的功能