zoukankan      html  css  js  c++  java
  • 在 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. 根据 StackID 找到某一个组件,如果没有找到则返回 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
    

    参考资料

  • 相关阅读:
    html中的背景图片不能充满整个浏览器 .
    linux系统安装Memcache
    linux下如何查看某软件是否已安装
    Redis监控之redisstat安装与详解
    memcached出现:Fatal error: Call to undefined method Memcached::connect()
    PHP多进程开发与Redis结合实践
    关于大型asp.net应用系统的架构架构的选择(转载)
    浅谈https\ssl\数字证书
    利用Lucene.net对附件做搜索(转载)
    Mic's blog iis下ISAPI_Rewrite配置及 iis rewrite 规则书写
  • 原文地址:https://www.cnblogs.com/lvfeilong/p/234rsfsdf.html
Copyright © 2011-2022 走看看