運行在 Docker 上的微服務 - 服務發現與註冊

運行在 Docker 上的微服務 - 服務發現與註冊

tags: Docker Microservice RESTful etcdjava

Author: Andy Ai
Weibo: NinetyH
GitHub: https://github.com/aiyanbo/docker-restful-demonode


上一節 中,咱們學習瞭如何在 Docker 上構建一個 RESTful 風格的微服務。接下來,咱們將學習如何把運行在 Docker 上的微服務暴露在服務中心上,以便客戶端的調用。git

etcd

etcd 是一個分佈式,一致性的 k-v 存儲系統,用於共享配置和服務發現。其特性是:github

  • 簡單: 可以使用 curl 訪問的用戶 API(HTTP+JSON)
  • 安全: 可選的SSL客戶端證書認證
  • 快速: 單實例每秒 1000 次寫操做
  • 可靠: 使用Raft保證一致性

etcd 使用 go 語言實現,並經過 Raft 一致性算法處理日誌複製以保證強一致性。golang

在這個章節中,咱們使用 etcd 做爲服務註冊中心。算法

架構

註冊 & 服務發現
1. etcd registry 作爲服務中心,提供註冊與服務發現。
2. 資源服務在準備完畢以後將服務實例註冊到服務中心。
3. 客戶端到服務註冊中心根據服務名稱獲取資源服務的地址。
4. 客戶端獲取資源服務的地址後,調用資源服務。
5. 資源服務在關閉時須要將服務實例在服務中心進行註銷操做。docker

服務註冊 & 發現

Step0. 啓動 etcd

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

Step1. 在 pom.xml 加入 etcd 客戶端

xml<properties>
    <etcd4j.version>2.7.0</etcd4j.version>
</properties>

<dependency>
    <groupId>org.mousio</groupId>
    <artifactId>etcd4j</artifactId>
    <version>${etcd4j.version}</version>
</dependency>

Step2. 微服務啓動完成時註冊到服務中心

在這裏,咱們將使用到 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

Step3. 查看服務中內心的服務

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

Step4. 微服務關閉時註銷實例

資源服務實例在關閉時應該在服務中心進行註銷操做,不然客戶端就會拿到一個不可用的服務。

有一種簡單的方式是在程序的 Runtime.shutdownHook 裏面添加一個註銷線程,像下面這樣:

javaRuntime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
        try {
            etcd.delete(serviceInstanceKey).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});

暴露公共 IP

上面咱們已經成功地使用 etcd 爲咱們的服務進行了註冊跟暴露,可是咱們實例在暴露本身的位置的時候倒是 "value": "http://172.17.0.5:9998/"。它暴露的是 docker 內部的一個 ip,不是公共 ip 會仍是會致使客戶端不能訪問服務。這時咱們可使用 docker 的環境變量來解決這個問題。

  • 在 docker 中,咱們須要使用 ENV 來定義一個環境變量。
# host 的默認值爲 0.0.0.0
ENV host 0.0.0.0
  • 在程序中,咱們要使用 System.getenv("host") 來讀取 docker 的環境變量。
  • 在 docker 運行中,咱們使用 -e 來覆蓋 Dockerfilehost 的環境變量值。
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.ApplicationShutdownHooksjava.lang.Shutdown 的一個子 shutdown hook

在程序被上面的幾種方法正常關閉下,JVM 會順利的執行 shutdown hooks。可是在非正常狀況下,例如:kill -9;系統忽然斷電並不會調用 shutdown hooks。那麼這樣就會致使資源服務已經不能提供服務了,可是因爲這些緣由沒有在服務中心註銷,一樣地會讓客戶端拿到一個不可用的地址列表。資源服務器的網絡中斷也會有一樣的問題。

咱們可使用一種很通用的方法解決這個問題:發送心跳包。具體的步驟爲:

  • 使用 etcd 的 TTL 接口設置key 的存活時間爲 5s。
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
  }
}
  • 每 5s 將服務實例的信息從新註冊到 etcd。
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...

未經贊成不可轉載, 轉載需保留原文連接與做者署名。

相關文章
相關標籤/搜索