tags: Docker Microservice RESTful etcdjava
Author: Andy Ai
Weibo: NinetyH
GitHub: https://github.com/aiyanbo/docker-restful-demonode
在 上一节 中,咱们学习了如何在 Docker 上构建一个 RESTful 风格的微服务。接下来,咱们将学习如何把运行在 Docker 上的微服务暴露在服务中心上,以便客户端的调用。git
etcd 是一个分布式,一致性的 k-v 存储系统,用于共享配置和服务发现。其特性是:github
etcd 使用 go 语言实现,并经过 Raft 一致性算法处理日志复制以保证强一致性。golang
在这个章节中,咱们使用 etcd 做为服务注册中心。算法
注册 & 服务发现
1. etcd registry 作为服务中心,提供注册与服务发现。
2. 资源服务在准备完毕以后将服务实例注册到服务中心。
3. 客户端到服务注册中心根据服务名称获取资源服务的地址。
4. 客户端获取资源服务的地址后,调用资源服务。
5. 资源服务在关闭时须要将服务实例在服务中心进行注销操做。docker
etcd 提供了 Docker Image, 咱们将使用 Docker 运行一个 etcd:json
bashdocker run -d -p 4001:4001 coreos/etcd:v0.4.6 -name myetcd
测试 etcd 服务:segmentfault
bash$ boot2docker ip 192.168.59.103 $ curl http://192.168.59.103:4001/v2/keys/ {"action":"get","node":{"key":"/","dir":true}}
xml<properties> <etcd4j.version>2.7.0</etcd4j.version> </properties> <dependency> <groupId>org.mousio</groupId> <artifactId>etcd4j</artifactId> <version>${etcd4j.version}</version> </dependency>
在这里,咱们将使用到 etcd 的 put 接口:api
bashcurl http://192.168.59.103:4001/v2/keys/registry/stacks/v1/$instance_id -XPUT -d value="$instance_address"
你能够在 etc api docs 里面查看更多的接口使用。
调用 mousio.etcd4j.EtcdClient
向 etcd 注册服务:
javafinal String instanceId = UUID.randomUUID().toString(); final String serviceInstanceKey = "registry/stacks/v1/" + instanceId; final EtcdClient etcd = new EtcdClient(UriBuilder.fromUri("http://192.168.59.103/").port(4001).build()); etcd.put(serviceInstanceKey, "http://" + host + ":9998/").send();
Note:
注册到服务中的key有如下几个部分组成:
1. registry: 服务中心 Root
2. stacks: 服务名称
3. v1: 服务的版本
4. instanceId: 服务的实例 ID
bash$ curl http://192.168.59.103:4001/v2/keys/registry/stacks/v1
若是服务成功注册,你能够看到像下面这样的结果:
json{ "action": "get", "node": { "key": "/registry/stacks/v1", "dir": true, "nodes": [ { "key": "/registry/stacks/v1/ee07bf3e-e9d3-4d04-8bb9-48fc38f16384", "value": "http://172.17.0.5:9998/", "modifiedIndex": 26, "createdIndex": 26 } ], "modifiedIndex": 26, "createdIndex": 26 } }
资源服务实例在关闭时应该在服务中心进行注销操做,不然客户端就会拿到一个不可用的服务。
有一种简单的方式是在程序的 Runtime.shutdownHook
里面添加一个注销线程,像下面这样:
javaRuntime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { etcd.delete(serviceInstanceKey).send(); } catch (IOException e) { e.printStackTrace(); } } });
上面咱们已经成功地使用 etcd 为咱们的服务进行了注册跟暴露,可是咱们实例在暴露本身的位置的时候倒是 "value": "http://172.17.0.5:9998/"
。它暴露的是 docker 内部的一个 ip,不是公共 ip 会仍是会致使客户端不能访问服务。这时咱们可使用 docker 的环境变量来解决这个问题。
ENV
来定义一个环境变量。# host 的默认值为 0.0.0.0 ENV host 0.0.0.0
System.getenv("host")
来读取 docker 的环境变量。-e
来覆盖 Dockerfile
中 host
的环境变量值。shdocker run -d -p 9998:9998 -e "host=192.168.59.103" docker-restful-demo
在 JVM 中,调用 shutdown hooks
有如下几种状况:
1. 程序正常终止,最后的 non-daemon
线程退出。
2. 调用 System.exit
方法退出。
3. 响应用户的终止。例如,输入 Ctrl + C
;使用 kill
命令杀死 JVM 进程(kill -9
不会);系统的全局事件:用户的注销,操做系统的 shutdown。
最底层的 java.lang.Shutdown
只运行 10 个 shutdown hook
,可是使用 java.lang.Runtime.addShutdownHook
添加的的 shutdown hook
通过 java.lang.ApplicationShutdownHooks
包装后并无限制。java.lang.ApplicationShutdownHooks
是 java.lang.Shutdown
的一个子 shutdown hook
。
在程序被上面的几种方法正常关闭下,JVM 会顺利的执行 shutdown hooks
。可是在非正常状况下,例如:kill -9
;系统忽然断电并不会调用 shutdown hooks
。那么这样就会致使资源服务已经不能提供服务了,可是因为这些缘由没有在服务中心注销,一样地会让客户端拿到一个不可用的地址列表。资源服务器的网络中断也会有一样的问题。
咱们可使用一种很通用的方法解决这个问题:发送心跳包。具体的步骤为:
bashcurl http://192.168.59.103:4001/v2/keys/registry/stacks/v1/$instance_id -XPUT -d value="$instance_address" -d ttl=5 { "action": "get", "node": { "key": "/registry/stacks/v1/72f9a7ba-f100-4638-a502-1541fc7d08f1", "value": "http://192.168.113.86:9998/", "expiration": "2015-06-30T07:20:47.485980615Z", "ttl": 5, "modifiedIndex": 45, "createdIndex": 45 } }
javaetcd.put(serviceInstanceKey, "http://" + host + ":9998/").ttl(5).send();
Note
5s 视本身系统的状况而定。
https://github.com/coreos/etcd
http://java.dzone.com/articles/know-jvm-series-2-shutdown
http://www.infoq.com/cn/news/2014/07/etcd-cluster-discovery
https://coreos.com/docs/cluster-management/setup/cluster-discovery/
http://blog.gopheracademy.com/advent-2013/day-06-service-discovery-wit...
http://stackoverflow.com/questions/2541597/how-to-gracefully-handle-th...
未经赞成不可转载, 转载需保留原文连接与做者署名。