一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

在 Docker 上運(yùn)行一個(gè) RESTful 風(fēng)格的微服務(wù)

 陳永正的圖書館 2017-09-16



實(shí)現(xiàn)構(gòu)思

  1. 使用 Maven 進(jìn)行項(xiàng)目構(gòu)建

  2. 使用 Jersey 實(shí)現(xiàn)一個(gè) RESTful 風(fēng)格的微服務(wù)

  3. 在 Docker 里面執(zhí)行 mvn package 對(duì)項(xiàng)目打包

  4. 在 Docker 容器里運(yùn)行這個(gè)微服務(wù)

實(shí)現(xiàn)一個(gè) RESTful 風(fēng)格的微服務(wù)

如果你對(duì) RESTful 風(fēng)格的 API 設(shè)計(jì)有疑惑,可以參考我的文章 RESTful Best Practices。

場(chǎng)景 & 需求

在 Maven 倉(cāng)庫(kù)里面有許多的組件,我們現(xiàn)在暫且稱之為 Stack。在我們模擬的系統(tǒng)里面有下面2個(gè)需求:

  1. 列出倉(cāng)庫(kù)里的所有 Stack

  2. 根據(jù) StackID 找到某一個(gè)組件,如果沒(méi)有找到則返回 Not Found

現(xiàn)在,我們就根據(jù)這個(gè)需求一起踏入 Jersey 打造微服務(wù)的奇幻之旅。

Step0. 準(zhǔn)備

使用 mvn 命令創(chuàng)建一個(gè)簡(jiǎn)單工程

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

pom.xml 加入 Jersey 等依賴

<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. 構(gòu)建 Model

Stack 包含了以下幾個(gè)屬性: id, groupId, artifactId, version。同時(shí),Stack 類里面包含了一個(gè) Builder 用來(lái)比較方便地創(chuàng)建一個(gè) Stack 對(duì)象。這些都可以使用 IDE 自動(dòng)生成,無(wú)需手動(dòng)編寫。

package 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 創(chuàng)建一個(gè) Restlet

剛剛我們已經(jīng)把 Model 做好了,現(xiàn)在我們就開(kāi)始使用 Jersey 實(shí)現(xiàn)一個(gè) Service,在 JAX-RS 中這樣的一個(gè)服務(wù)稱為 Resource。在這里,這個(gè) Service(Resource) 提供了一個(gè) RESTful 風(fēng)格的接口訪問(wèn)。所以我們稱之為 restletrestlet 在 JAX-RS 或 Jersey 中并沒(méi)有這個(gè)概念,這是我們附加上去的用法。

import javax.ws.rs.Path;

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

我們需要使用 javax.ws.rs.Path 這個(gè)注解來(lái)申明 Restlet 的根路徑是什么。在上面的代碼中, Restlet 的跟路徑是 /v1/stacks。

Step3. 實(shí)現(xiàn)服務(wù)接口

在 Jersey 里實(shí)現(xiàn)一個(gè)服務(wù)接口非常簡(jiǎn)單,你只需要?jiǎng)?chuàng)建一個(gè) public 的方法就可以了。

接口1:

@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 這個(gè)注解來(lái)申明接口接受的是 HTTP 請(qǐng)求的 GET 方法。javax.ws.rs.Produces("application/json") 用來(lái)表示我們這個(gè)接口返回的是 application/json 類型的數(shù)據(jù)。

接口2:

@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 是一個(gè) url 上的動(dòng)態(tài)參數(shù),因?yàn)?id 是變化的,所以我們要做成一個(gè) url 變量。然后在方法里面使用 @PathParam("id") 來(lái)獲得這個(gè)參數(shù)。JAX-RS 可以有許多類型的參數(shù),例如:QueryParam 用來(lái)獲取 url 問(wèn)號(hào)? 后面的查詢參數(shù)。你可以在 javax.ws.rs 這個(gè)包中找到其他的參數(shù)!

  2. 使用 WebApplicationException 拋一個(gè)任何狀態(tài)的異常,例如: 404 或 500。

Step4. 運(yùn)行微服務(wù)

這里,我們使用 Jersey 的內(nèi)置的 Grizzly 容器運(yùn)行。

final 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. 測(cè)試

獲取 Stacks 列表

$ 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"}]

根據(jù) ID 獲取 Stack

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

找不到的 Stack

$ 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 中運(yùn)行

首先,我們需要安裝一個(gè) Docker 環(huán)境,你可以從 Docker Docs 上找到如何安裝它。

安裝完成后,我們需要把我們的微服務(wù)打包成一個(gè) Docker Image。下面,我們就使用 Dockerfile 來(lái)構(gòu)建我們的 Docker Image。

Step0. 準(zhǔn)備

剛剛我們已經(jīng)成功地在 IDE 中運(yùn)行了我們的微服務(wù)。但是如果需要讓它能獨(dú)立運(yùn)行,我們需要把我們的工程通過(guò) mvn 做成一個(gè)可以運(yùn)行的包。但是因?yàn)槲覀冊(cè)?Docker 中運(yùn)行,所以我們只需要把相關(guān)的依賴復(fù)制到一個(gè)特地的地方就可以了。在下面的代碼中,我們把依賴放到了 ${project.build.directory}/lib 下。

pom.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

FROM 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。在這個(gè)文件里面你可能會(huì)使用到一些特定的配置,例如:maven 倉(cāng)庫(kù)的代理鏡像。代理鏡像可以加快你的 Docker Build。

  2. EXPOSE 9998 Docker 對(duì)外暴露的端口需要跟服務(wù)的端口是一致的。

Step2. Build Image

cd docker-restful-demo
docker build -t docker-restful-demo .

上面代碼中,-t 是在 Docker Build 的時(shí)候指定 Image Tag。

Step3. 運(yùn)行 Image

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

Tips
-p 是發(fā)布一個(gè) Docker 容器的端口到 Docker 運(yùn)行的主機(jī)上。

Step4. Docker 容器測(cè)試

檢查 Docker 容器是否在運(yùn)行

$ 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 容器內(nèi)的服務(wù)是否已經(jīng)啟動(dòng):

  • 登錄到 Docker 容器:

docker exec -i -t bdda2408484a bash
  • 查看服務(wù)端口信息

$ 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                                     :::*
  • 測(cè)試接口

$ 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 容器

exit

Step5. 遠(yuǎn)程調(diào)用測(cè)試

  • 如果你使用的是 boot2docker, 需要拿到 boot2docker 虛擬機(jī)的IP

$ boot2docker ip
192.168.59.103
  • 調(diào)用遠(yuǎn)程接口

$ curl http://192.168.59.103:9998/v1/stacks
curl: (7) Failed to connect to 192.168.59.103 port 9998: Connection refused

如果遇到上面的錯(cuò)誤,我們可以通過(guò)2種方式去解決它:
方法1: 將程序綁定全零IP的端口

檢查 Docker 容器綁定的端口:

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

我們看到的是 9998 這個(gè)端口綁定在 0.0.0.0 上,這時(shí)需要把 Jersey 容器的 URI 改成 0.0.0.0 就可以,像這樣:

final 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 地址的方法:

boot2docker ssh

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

使用 Java 接口獲取本機(jī)的非回路IP地址:

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

使用上面的任何一種方法,服務(wù)都能正常調(diào)用:

$ 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"}]

加速器

在撰寫此文的時(shí)候,我使用的是 DaoColud 的鏡像在做加速。 具體步驟請(qǐng)查看 DaoColud Mirror 文檔。

如果你在 Windows 上使用 Boot2Docker, 可以按照下列步驟設(shè)置你的 Docker 鏡像倉(cāng)庫(kù):

boot2docker ssh

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

boot2docker restart

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    一本久道久久综合中文字幕| 超碰在线播放国产精品| 亚洲国产成人精品一区刚刚| 我想看亚洲一级黄色录像| 国产精品午夜福利免费在线| 九九热在线视频精品免费| 午夜国产成人福利视频| 中文字幕日韩欧美一区| 欧美日韩一区二区综合| 国产精品欧美在线观看| 中文字幕在线五月婷婷| 偷自拍亚洲欧美一区二页| 九九视频通过这里有精品| 色婷婷丁香激情五月天| 爱草草在线观看免费视频| 亚洲中文字幕在线观看黑人| 久久热中文字幕在线视频| 国产精品免费自拍视频| 欧美人妻少妇精品久久性色| 国产熟女一区二区精品视频| 国产精品日本女优在线观看| 91人妻人人做人碰人人九色| 免费亚洲黄色在线观看| 国产精品欧美在线观看| 欧洲自拍偷拍一区二区| 深夜日本福利在线观看| 亚洲一区二区精品免费| 国产综合欧美日韩在线精品 | 免费亚洲黄色在线观看| 五月天丁香婷婷狠狠爱| 国产日韩欧美一区二区| 精品国产日韩一区三区| 这里只有九九热精品视频| 亚洲中文字幕免费人妻| 最近的中文字幕一区二区| 日本在线视频播放91| 黄片免费观看一区二区| 国产又粗又长又大的视频| 久久精品国产99国产免费| 青青操视频在线观看国产| 中文字幕熟女人妻视频|