在 Docker 上运行一个 RESTful 风格的微服务
tags: Microservice Restful Docker
实现构思
1. 使用 Maven 进行项目构建
2. 使用 Jersey 实现一个 RESTful 风格的微服务
3. 在 Docker 里面执行 mvn package
对项目打包
4. 在 Docker 容器里运行这个微服务
实现一个微服务
场景 & 需求
在 Maven 仓库里面有许多的组件,我们现在暂且称之为 Stack
。在我们模拟的系统里面有下面2个需求:
1. 列出仓库里的所有 Stack
2. 根据 Stack
的 ID
找到某一个组件,如果没有找到则返回 Not Found
现在,我们就根据这个需求一起踏入 Jersey 打造微服务的奇幻之旅。
Step0. 准备
使用 mvn
命令创建一个简单工程
mvn
mvn 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 自动生成,无需手动编写。
java
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 创建一个 Restlet
刚刚我们已经把 Model 做好了,现在我们就开始使用 Jersey 实现一个 Service,在 JAX-RS 中这样的一个服务称为 Resource。在这里,这个 Service(Resource) 提供了一个 RESTful 风格的接口访问。所以我们称之为 restlet
。 restlet
在 JAX-RS 或 Jersey 中并没有这个概念,这是我们附加上去的用法。
java
import 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 容器运行。
java
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. 测试
获取 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
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。在这个文件里面你可能会使用到一些特定的配置,例如:maven 仓库的代理镜像。代理镜像可以加快你的 Docker Build。
2. EXPOSE 9998
Docker 对外暴露的端口需要跟服务的端口是一致的。
Step2. Build Image
bash
cd docker-restful-demo
docker build -t docker-restful-demo .
上面代码中, -t
是在 Docker Build 的时候指定 Image Tag。
Step3. 运行 Image
bash
docker
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 容器:
bash
docker 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 容器
bash
exit
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 就可以,像这样:
java
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 地址的方法:
bash
boot2docker ssh docker inspect --format '{{.NetworkSettings.IPAddress}}' $container_id
使用 Java 接口获取本机的非回路IP地址:
java
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;
}
使用上面的任何一种方法,服务都能正常调用:
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 镜像仓库:
bash
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/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...
在 Docker 上运行一个 RESTful 风格的微服务的更多相关文章
- .NET Core 3.0 部署在docker上运行
自从.NET Core3.0发布之后,写了几篇关于.NET Core 3.0的文章,有助于你快速入门.NET Core3.0. 本篇文章主要讲解如何一步步创建一个mvc项目,然后发布并部署在Docke ...
- linux安装docker,并在docker上运行springboot项目
docker架构示例图 仓库---> 镜像 ---> 容器 一.安装docker 1.通过 uname -r 命令查看你当前的内核版本 uname -r 2使用 root 权限登录 Ce ...
- 试试将.NET7编译为WASM并在Docker上运行
之前有听到说Docker支持Wasmtime了,刚好.NET7也支持WASM,就带大家来了解一下这个东西,顺便试试它怎么样. 因为WASM(WebAssembly) 一开始是一个给浏览器的技术,比起J ...
- 在OSX和Windows版本Docker上运行GUI程序
看到很多人在Docker问题区讨论:如何在OS X和Windows的Docker上运行GUI程序, 随手记录几个参考资料: https://github.com/docker/docker/issue ...
- 通过beego快速创建一个Restful风格API项目及API文档自动化
通过beego快速创建一个Restful风格API项目及API文档自动化 本文演示如何快速(一分钟内,不写一行代码)的根据数据库及表创建一个Restful风格的API项目,及提供便于在线测试API的界 ...
- ELK 性能(3) — 在 Docker 上运行高性能容错的 Elasticsearch 集群
ELK 性能(3) - 在 Docker 上运行高性能容错的 Elasticsearch 集群 介绍 在 Docker 上运行高性能容错的 Elasticsearch 集群 内容 通常熟悉的开发流程是 ...
- 通过beego快速创建一个Restful风格API项目及API文档自动化(转)
通过beego快速创建一个Restful风格API项目及API文档自动化 本文演示如何快速(一分钟内,不写一行代码)的根据数据库及表创建一个Restful风格的API项目,及提供便于在线测试API的界 ...
- 构建RESTful风格的WCF服务
构建RESTful风格的WCF服务 RESTful Wcf是一种基于Http协议的服务架构风格. 相较 WCF.WebService 使用 SOAP.WSDL.WS-* 而言,几乎所有的语言和网络平台 ...
- RESTful风格的Web服务框架:Swagger
Swagger与SpringMVC项目整合 为了方便的管理项目中API接口,在网上找了好多关于API接口管理的资料,感觉目前最流行的莫过于Swagger了,功能强大,UI界面漂亮,并且支持在线测试等等 ...
随机推荐
- Oracle Database PSU/CPU
1. 什么是PSU/CPU?CPU: Critical Patch UpdateOracle对于其产品每个季度发行一次的安全补丁包,通常是为了修复产品中的安全隐患. PSU: Patch Set Up ...
- [Python] numpy.Matrix
import numpy as np np.matrix('1, 2; 3, 4') #1, 2 #3, 4 np.matrix([[1,2],[3,4]]) #1, 2 #3, 4
- canvas合成和裁剪
canvas合成和裁剪 属性 globalCompositeOperation=type 设置覆盖类型 source-over 源覆盖在目标上 source-in 源覆盖在目标上的公共部分(只取源图形 ...
- 使用Fiddler对Android手机的应用数据进行抓包分析
文章源自: http://blog.csdn.net/zshq280017423/article/details/8928616/ 对于Android开发的同事最头疼的事情莫过于真机抓包,然后Fidd ...
- 建设银行网上银行MD5withRSA php版
1. 首先通过java程序将建设银行的公钥串转成pem格式并写入文件 SignTest.java是运行程序, RSASig.java是建设银行签名算法类, bcprov-jdk15-145.jar是P ...
- autocomplete.js的使用(2):自动输入时,出现下拉选择框
<!--自动输入文本值所需的jquery文件--><script src="/js/jquery-1.8.3.min.js" type="text/Ja ...
- axure学习点
动态模板内部框架动态弹出框
- IT项目经理岗位职责(转)
一. 项目经理岗位职责 1. 项目经理为整个项目的第一责任人. 2. 项目经理对<质量检查报告>中的所有细则负首要责任. 3. 项目经理必须有效掌控项目开发的各个环节,协助.指导项 ...
- RabbitMQ学习在windows下安装配置
RabbitMQ学习一. 在windows下安装配置 1.下载并安装erlang,http://www.erlang.org/download.html,最新版是R15B01(5.9.1).由于我机器 ...
- [OS] 远程启动计划任务时以管理员身份运行
在Jenkins建了一个task自动启动Selenium的Grid,命令行是这样写的: schtasks /end /tn RestartGrid /s SZTEST201606 /u szdomai ...