摘要:在這里,這個(gè)提供了一個(gè)風(fēng)格的接口訪問(wèn)。準(zhǔn)備剛剛我們已經(jīng)成功地在中運(yùn)行了我們的微服務(wù)。對(duì)外暴露的端口需要跟服務(wù)的端口是一致的。運(yùn)行是發(fā)布一個(gè)容器的端口到運(yùn)行的主機(jī)上。
tags: Microservice Restful Docker
Author: Andy Ai
Weibo: NinetyH
GitHub: https://github.com/aiyanbo/do...
實(shí)現(xiàn)構(gòu)思
使用 Maven 進(jìn)行項(xiàng)目構(gòu)建
使用 Jersey 實(shí)現(xiàn)一個(gè) RESTful 風(fēng)格的微服務(wù)
在 Docker 里面執(zhí)行 mvn package 對(duì)項(xiàng)目打包
在 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)在暫且稱(chēng)之為 Stack。在我們模擬的系統(tǒng)里面有下面2個(gè)需求:
列出倉(cāng)庫(kù)里的所有 Stack
根據(jù) Stack 的 ID 找到某一個(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 等依賴(lài)
Step1. 構(gòu)建 ModelUTF-8 UTF-8 4.12 2.18 3.1.0 junit junit ${junit.version} test org.glassfish.jersey.containers jersey-container-grizzly2-http ${jersey.version} org.glassfish.jersey.media jersey-media-json-jackson ${jersey.version}
Stack 包含了以下幾個(gè)屬性: id, groupId, artifactId, version。同時(shí),Stack 類(lèi)里面包含了一個(gè) Builder 用來(lái)比較方便地創(chuàng)建一個(gè) Stack 對(duì)象。這些都可以使用 IDE 自動(dòng)生成,無(wú)需手動(dòng)編寫(xiě)。
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ù)稱(chēng)為 Resource。在這里,這個(gè) Service(Resource) 提供了一個(gè) RESTful 風(fēng)格的接口訪問(wèn)。所以我們稱(chēng)之為 restlet。restlet 在 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 Liststacks() { 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 類(lèi)型的數(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); } }
在上面的示例中:
@Path("{id}") 表示 id 是一個(gè) url 上的動(dòng)態(tài)參數(shù),因?yàn)?id 是變化的,所以我們要做成一個(gè) url 變量。然后在方法里面使用 @PathParam("id") 來(lái)獲得這個(gè)參數(shù)。JAX-RS 可以有許多類(lèi)型的參數(shù),例如:QueryParam 用來(lái)獲取 url 問(wèn)號(hào)? 后面的查詢(xún)參數(shù)。你可以在 javax.ws.rs 這個(gè)包中找到其他的參數(shù)!
使用 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)的依賴(lài)復(fù)制到一個(gè)特地的地方就可以了。在下面的代碼中,我們把依賴(lài)放到了 ${project.build.directory}/lib 下。
在 pom.xml 加入下面的代碼:
Step1. Dockerfileorg.apache.maven.plugins maven-dependency-plugin copy-dependencies package copy-dependencies provided ${project.build.directory}/lib org.apache.maven.plugins maven-compiler-plugin 1.7
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。在這個(gè)文件里面你可能會(huì)使用到一些特定的配置,例如:maven 倉(cāng)庫(kù)的代理鏡像。代理鏡像可以加快你的 Docker Build。
EXPOSE 9998 Docker 對(duì)外暴露的端口需要跟服務(wù)的端口是一致的。
Step2. Build Imagecd docker-restful-demo docker build -t docker-restful-demo .
上面代碼中,-t 是在 Docker Build 的時(shí)候指定 Image Tag。
Step3. 運(yùn)行 Imagedocker run -d -p 9998:9998 docker-restful-demo
Step4. Docker 容器測(cè)試Tips
-p 是發(fā)布一個(gè) Docker 容器的端口到 Docker 運(yùn)行的主機(jī)上。
檢查 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 容器
exitStep5. 遠(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 { EnumerationnetworkInterfaces = NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { NetworkInterface networkInterface = networkInterfaces.nextElement(); Enumeration 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"}]加速器
在撰寫(xiě)此文的時(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.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...
未經(jīng)同意不可轉(zhuǎn)載, 轉(zhuǎn)載需保留原文鏈接與作者署名。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/26422.html
摘要:運(yùn)行在上的微服務(wù)服務(wù)發(fā)現(xiàn)與注冊(cè)在上一節(jié)中,我們學(xué)習(xí)了如何在上構(gòu)建一個(gè)風(fēng)格的微服務(wù)。接下來(lái),我們將學(xué)習(xí)如何把運(yùn)行在上的微服務(wù)暴露在服務(wù)中心上,以便客戶(hù)端的調(diào)用。資源服務(wù)在關(guān)閉時(shí)需要將服務(wù)實(shí)例在服務(wù)中心進(jìn)行注銷(xiāo)操作。響應(yīng)用戶(hù)的終止。 運(yùn)行在 Docker 上的微服務(wù) - 服務(wù)發(fā)現(xiàn)與注冊(cè) tags: Docker Microservice RESTful etcd Author: And...
摘要:微服務(wù)架構(gòu)模式使得每個(gè)微服務(wù)獨(dú)立部署,且每個(gè)服務(wù)獨(dú)立擴(kuò)展,開(kāi)發(fā)者不再需要協(xié)調(diào)其它服務(wù)部署對(duì)本服務(wù)的影響。微服務(wù)架構(gòu)模式使得持續(xù)化部署成為可能。所以使用微服務(wù)不是必須的,而是在適當(dāng)?shù)膶?shí)際,架構(gòu)適應(yīng)應(yīng)用場(chǎng)景的一種改變。 近段時(shí)間離職,跟同事們講解我之前所做的微服務(wù)相關(guān)產(chǎn)品,對(duì)于同事們提出的問(wèn)題,做了如下整理出來(lái),加上自己的理解,分享出來(lái)跟大家一起探討下: 問(wèn)題預(yù)覽 我為什么要換微服務(wù)?能...
摘要:此刻的后手指依舊飛速地敲打鍵盤(pán),絲毫沒(méi)有要停不下來(lái)意思。閱讀本期技術(shù)周刊,你不光能弄明白什么是,使用的意義何在,還將被傳授秘籍,以達(dá)的境界。周刊篩選的每篇內(nèi)容,是作者的獨(dú)到見(jiàn)解,踩坑總結(jié)和經(jīng)驗(yàn)分享。 showImg(https://segmentfault.com/img/bVC5qJ?w=900&h=385); 啪嗒啪嗒,啪嗒啪嗒,聽(tīng)到后排動(dòng)感十足的清脆鍵盤(pán)響,我就能猜到公司程序員定...
摘要:納尼隔壁少林派表示自家金剛技?jí)喝盒墼谧魑欢际恰!!<{尼你覺(jué)得寫(xiě)太繁瑣了你不喜歡我們還有或者等等一大堆工具呢。納尼沒(méi)有你還是覺(jué)得無(wú)法接受好吧那么筆者推薦類(lèi)似這類(lèi)更友好的工具你可以導(dǎo)入導(dǎo)出其他格式也可以使用其來(lái)撰寫(xiě)。 說(shuō)起微服務(wù), 想必現(xiàn)在的技術(shù)圈內(nèi)人士個(gè)個(gè)都能談笑風(fēng)云, 娓娓道來(lái)。的確, 技術(shù)變革日新月異, 各種工具框架雨后春筍般涌現(xiàn), 現(xiàn)在我們可以輕巧便捷地根據(jù)自己的業(yè)務(wù)需求, 構(gòu)建...
摘要:是一個(gè)相對(duì)比較新的微服務(wù)框架,年才推出的版本雖然時(shí)間最短但是相比等框架提供的全套的分布式系統(tǒng)解決方案。提供線程池不同的服務(wù)走不同的線程池,實(shí)現(xiàn)了不同服務(wù)調(diào)用的隔離,避免了服務(wù)器雪崩的問(wèn)題。通過(guò)互相注冊(cè)的方式來(lái)進(jìn)行消息同步和保證高可用。 Spring Cloud 是一個(gè)相對(duì)比較新的微服務(wù)框架,...
閱讀 3486·2021-11-18 10:07
閱讀 1586·2021-11-04 16:08
閱讀 1502·2021-11-02 14:43
閱讀 1083·2021-10-09 09:59
閱讀 837·2021-09-08 10:43
閱讀 1073·2021-09-07 09:59
閱讀 954·2019-12-27 11:56
閱讀 994·2019-08-30 15:56