tags: Microservice Restful Dockerhtml
Author: Andy Ai
Weibo: NinetyH
GitHub: https://github.com/aiyanbo/do...java
實現構思git
使用 Maven 進行項目構建github
使用 Jersey 實現一個 RESTful 風格的微服務docker
在 Docker 裏面執行 mvn package
對項目打包apache
在 Docker 容器裏運行這個微服務json
若是你對 RESTful 風格的 API 設計有疑惑,能夠參考個人文章 RESTful Best Practices。segmentfault
在 Maven 倉庫裏面有許多的組件,咱們如今暫且稱之爲 Stack
。在咱們模擬的系統裏面有下面2個需求:api
列出倉庫裏的全部 Stack
bash
根據 Stack
的 ID
找到某一個組件,若是沒有找到則返回 Not Found
如今,咱們就根據這個需求一塊兒踏入 Jersey 打造微服務的奇幻之旅。
使用 mvn
命令建立一個簡單工程
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>
Stack
包含了如下幾個屬性: id
, groupId
, artifactId
, version
。同時,Stack 類裏面包含了一個 Builder 用來比較方便地建立一個 Stack 對象。這些均可以使用 IDE 自動生成,無需手動編寫。
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(); } } }
剛剛咱們已經把 Model 作好了,如今咱們就開始使用 Jersey 實現一個 Service,在 JAX-RS 中這樣的一個服務稱爲 Resource。在這裏,這個 Service(Resource) 提供了一個 RESTful 風格的接口訪問。因此咱們稱之爲 restlet
。restlet
在 JAX-RS 或 Jersey 中並無這個概念,這是咱們附加上去的用法。
import javax.ws.rs.Path; @Path("/v1/stacks") public class StacksRestlet {}
咱們須要使用 javax.ws.rs.Path
這個註解來申明 Restlet 的根路徑是什麼。在上面的代碼中, Restlet 的跟路徑是 /v1/stacks
。
在 Jersey 裏實現一個服務接口很是簡單,你只須要建立一個 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
這個註解來申明接口接受的是 HTTP 請求的 GET 方法。javax.ws.rs.Produces("application/json")
用來表示咱們這個接口返回的是 application/json
類型的數據。
接口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); } }
在上面的示例中:
@Path("{id}")
表示 id 是一個 url 上的動態參數,由於 id 是變化的,因此咱們要作成一個 url 變量。而後在方法裏面使用 @PathParam("id")
來得到這個參數。JAX-RS 能夠有許多類型的參數,例如:QueryParam
用來獲取 url 問號?
後面的查詢參數。你能夠在 javax.ws.rs
這個包中找到其餘的參數!
使用 WebApplicationException
拋一個任何狀態的異常,例如: 404 或 500。
這裏,咱們使用 Jersey 的內置的 Grizzly 容器運行。
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); }
獲取 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"}]
根據 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 環境,你能夠從 Docker Docs 上找到如何安裝它。
安裝完成後,咱們須要把咱們的微服務打包成一個 Docker Image
。下面,咱們就使用 Dockerfile 來構建咱們的 Docker Image。
剛剛咱們已經成功地在 IDE 中運行了咱們的微服務。可是若是須要讓它能獨立運行,咱們須要把咱們的工程經過 mvn 作成一個能夠運行的包。可是由於咱們在 Docker 中運行,因此咱們只須要把相關的依賴複製到一個特意的地方就能夠了。在下面的代碼中,咱們把依賴放到了 ${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>
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
ADD settings.xml /root/.m2/settings.xml
,這是加入了本地的 maven settings。在這個文件裏面你可能會使用到一些特定的配置,例如:maven 倉庫的代理鏡像。代理鏡像能夠加快你的 Docker Build。
EXPOSE 9998
Docker 對外暴露的端口須要跟服務的端口是一致的。
cd docker-restful-demo docker build -t docker-restful-demo .
上面代碼中,-t
是在 Docker Build 的時候指定 Image Tag。
docker run -d -p 9998:9998 docker-restful-demo
Tips
-p
是發佈一個 Docker 容器的端口到 Docker 運行的主機上。
檢查 Docker 容器是否在運行
$ 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 容器:
docker exec -i -t bdda2408484a 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 :::*
測試接口
$ 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
若是你使用的是 boot2docker, 須要拿到 boot2docker 虛擬機的IP
$ boot2docker ip 192.168.59.103
調用遠程接口
$ 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 容器綁定的端口:
$ docker port bdda2408484a 9998/tcp -> 0.0.0.0:9998
咱們看到的是 9998 這個端口綁定在 0.0.0.0 上,這時須要把 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 接口獲取本機的非迴路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; }
使用上面的任何一種方法,服務都能正常調用:
$ 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 鏡像倉庫:
boot2docker ssh sudo su echo "EXTRA_ARGS=\"--registry-mirror=http://98bc3dca.m.daocloud.io\"" >> /var/lib/boot2docker/profile exit boot2docker restart
參考資料
https://dashboard.daocloud.io...
http://martinfowler.com/artic...
https://jersey.java.net/docum...
https://blog.giantswarm.io/ge...
未經贊成不可轉載, 轉載需保留原文連接與做者署名。