1. 程式人生 > >小白入門微服務(5) - 服務註冊發現實戰(docker+registrator+consul+nodejs+python)

小白入門微服務(5) - 服務註冊發現實戰(docker+registrator+consul+nodejs+python)

概述

  • 前言
  • 原始碼
  • registrator
  • API gateway
  • web 服務
  • 執行
  • 後記

前言

這篇文章真是等了挺久才寫,讓小夥伴們久等了。這篇文章旨在帶你走一下微服務的流程,真實的微服務遠不止這些東西。詳細的介紹敬請關注我後面的文章。**如果我的文章對你有幫助,歡迎關注、點贊、轉發,這樣我會更有動力做原創分享。**OK,進入正題!

原始碼

首先甩出原始碼吧,因為這對於剛接觸的小夥伴來說這可能還是個龐然大物。先大致瀏覽一下程式碼,對後面理解會好一些。關注微信公眾號【zone7】後臺回覆【微服務】獲取原始碼。
docker-compose

version: '2'
services:
  # consul server,對外暴露的ui介面為8500,只有在2臺consul伺服器的情況下叢集才起作用
  consulserver:
    image: progrium/consul:latest
    hostname: consulserver
    ports:
      - "8300"
      - "8400"
      - "8500:8500"
      - "53"
    command: -server -ui-dir /ui -data-dir /tmp/consul --bootstrap-expect=2
    networks:
      - app

  # consul server1在consul server服務起來後,加入叢集中
  consulserver1:
    image: progrium/consul:latest
    hostname: consulserver1
    depends_on:
      - "consulserver"
    ports:
      - "8300"
      - "8400"
      - "8500"
      - "53"
    command: -server -data-dir /tmp/consul -join consulserver
    networks:
      - app
  # consul server2在consul server服務起來後,加入叢集中
  consulserver2:
    image: progrium/consul:latest
    hostname: consulserver2
    depends_on:
      - "consulserver"
    ports:
      - "8300"
      - "8400"
      - "8500"
      - "53"
    command: -server -data-dir /tmp/consul -join consulserver
    networks:
      - app
  # 監聽容器中暴露的埠,一有新的埠,註冊到註冊中心
  registrator:
    image: gliderlabs/registrator:master
    hostname: registrator
    depends_on:
      - "consulserver"
    volumes:
      - "/var/run/docker.sock:/tmp/docker.sock"
    command: -internal consul://consulserver:8500
    networks:
      - app

# web 服務
  web-nodejs:
    build: ./webNodejs
    image: webapp:latest
    depends_on:
      - "consulserver"
    ports:
      - "3000"
    environment:
      SERVICE_3000_NAME: service-web
    networks:
      - app

# web 服務
  web-py:
    build: ./webPy
    image: webpy:latest
    ports:
      - "5000"
    environment:
      SERVICE_5000_NAME: service-web-py
    volumes:
      - ./webPy:/usr/local/work
    networks:
      - app
    command: bash -c "pip install -r requirements.txt && python app.py"

# api gateway
  gateway-py:
    build: ./gateWayPy
    image: gatewaypy:latest
    ports:
      - "5000:5000"
    environment:
      SERVICE_5000_NAME: service-gateway-py
    volumes:
      - ./gateWayPy:/usr/local/work
    networks:
      - app
    command: bash -c "pip install -r requirements.txt && python app.py"

# api gateway
  gateway-nodejs:
    build: ./gateWayJs
    image: gatewayjs:latest
    ports:
      - "3000:3000"
    environment:
      SERVICE_3000_NAME: service-gateway-js

    networks:
      - app

networks:
  app:
    driver: bridge

registrator

關於 registrator ,上一篇文章已經有介紹,這裡就不再贅述。這裡找到一箇中文文件,如下:

http://www.mamicode.com/info-detail-2383285.html#%E6%B3%A8%E5%86%8C%E5%90%8E%E7%AB%AF

下面簡單介紹一下,docker-compose 中的命令:

command: -internal consul://consulserver:8500
連線到 consul 
environment:
      SERVICE_5000_NAME: service-web
設定服務發現中的名。

服務名是你在服務發現查詢中使用的。預設情況下,服務名按下面的格式確定:

<base(container-image)>[- if >1 ports]

使用容器映象的基礎,如果映象是 gliderlabs/footbar,服務名就是footbar。如果映象是 redis,服務名就是簡單的 redis。

而且如果一個容器有多個暴露埠,它將各自追加內部暴露埠以區別。例如,一個映象 nginx 有兩個暴露埠,80 和 443,將產生兩個服務,分別命名 nginx-80 和 nginx-443。

你可以使用標籤或者環境變數,SERVICE_NAME 或者 SERVICE_x_NAME,其中 x 是內部暴露埠,覆蓋這些預設名字。注意如果一個容器有多個暴露埠,設定SERVICE_NAME 會導致多個服務命名為 SERVICE_NAME-。

API gateway

關於 API gateway,這裡使用 nodejs 實現了一個簡易版的 API gateway。說他簡易,那真的事很簡易,就只有服務發現和服務註冊的功能。至於 python,也實現了一套,但是值得一提的事 python 的 consul 庫不提供 服務監控功能,就是說當你新新增一個服務,程式是不會自動監控的。python 程式碼就不在貼出來了,詳情請檢視原始碼。

const Consul = require('consul');
const utils = require('./utils');
const serviceLocalStorage = require('./serviceLocalStorage.js');
class Discovery {
    connect(...args) {
        if (!this.consul) {
            console.log("與consul server連線中...")
            //建立連線,
            //需要注意的時,由於需要動態獲取docker內的consul server的地址,
            //所以host需要配置為consulserver(來自docker-compose配置的consulserver)
            //發起請求時會經過docker內建的dns server,即可把consulserver替換為具體的consul 伺服器 ip
            this.consul =new Consul({
                host:'consulserver',
                ...args,
                promisify: utils.fromCallback //轉化為promise型別
            });
        }
        return this;


    }
    /**
     * 根據名稱獲取服務
     * @param {*} opts
     */
    async getService(opts) {
        if (!this.consul) {
            throw new Error('請先用connect方法進行連線');
        }
        const {service} = opts;
        // 從快取中獲取列表
        const services = serviceLocalStorage.getItem(service);
        if (services.length > 0) {
            console.log(`命中快取,key:${service},value:${JSON.stringify(services)}`)
            return services;
        }
        //如果快取不存在,則獲取遠端資料
        let result = await this
            .consul
            .catalog
            .service
            .nodes(opts);
        console.log(`獲取服務端資料,key:${service}:value:${JSON.stringify(result[0])}  result: ${JSON.stringify(result)}`);
        serviceLocalStorage.setItem(service, result[0])
        return result[0];
    }
}

module.exports = new Discovery();

web 服務

這裡的 web 服務的主要功能就是獲取當前主機的 ip 地址。我分別用 pyhon 和 nodejs 兩種語言實現了一遍,其程式碼如下:
python 實現

app = Flask(__name__)
@app.route('/')
def a():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
    finally:
        s.close()
    return "hello zone !ip address ==> " + ip


@app.route('/index')
def index():
    c = consul.Consul(host='consulserver')
    (index, data) = c.catalog.service("service-web")
    host = str(data[0]["ServiceAddress"]) + ":" + str(data[0]["ServicePort"])
    print("==========="+host)
    r = requests.get('http://'+host)
    return r.text


if __name__ == '__main__':
    app.run(host='0.0.0.0')
#    app.run(host='0.0.0.0', debug=True)

nodejs 實現

const Application = require('koa');
const app = new Application();
const Router = require('koa-router');
const router = new Router();
const ip = require('ip');
router.get('/', async(ctx) => {
    ctx.body = {
    ip: ip.address()
}
})
//監聽3000埠
app.listen(3000, () => {
    console.log('listen on port 3000')
});
app
    .use(router.routes())
    .use(router.allowedMethods);

執行

網頁端訪問:

控制檯 log,不斷重新整理網頁邊出現如下 bug:
image.png
現在關閉一個微服務:createnewinstance_web-py_1
服務發現監控資訊如下:

[{
	"Node": "consulserver",
	"Address": "172.22.0.3",
	"ServiceID": "registrator:dockermicroservices_web-py_2:5000",
	"ServiceName": "service-web",
	"ServiceTags": null,
	"ServiceAddress": "172.22.0.4",
	"ServicePortNode": "consulserver",
	"Address": "172.22.0.3",
	"ServiceID": "registrator:dockermicroservices_web-py_4:5000",
	"ServiceName": "service-web",
	"ServiceTags": null,
	"ServiceAddress": "172.22.0.6",
	"ServicePort": 5000
}, {
	"Node": "consulserver",
	"Address": "172.22.0.3",
	"ServiceID": "registrator:dockermicroservices_web-py_3:5000",
	"ServiceName": "service-web",
	"ServiceTags": null,
	"ServiceAddress": "172.22.0.5",
	"ServicePort": 5000
}]

新啟動一個微服務:

version: '2'
services:
  web-py:
    build: ../webPy
    image: webpy:latest
#    depends_on:
#      - "consulserver"
    ports:
      - "5000"
    environment:
      SERVICE_5000_NAME: service-web
    volumes:
      - ../webPy:/usr/local/work
    networks:
      - app
    command: bash -c "pip install -r requirements.txt && python app.py"

networks:
  app:
    driver: bridge

執行命令 docker-compose up
監控服務:

[{
	"Node": "consulserver",
	"Address": "172.22.0.3",
	"ServiceID": "registrator:dockermicroservices_web-py_2:5000",
	"ServiceName": "service-web",
	"ServiceTags": null,
	"ServiceAddress": "172.22.0.4",
	"ServicePortNode": "consulserver",
	"Address": "172.22.0.3",
	"ServiceID": "registrator:dockermicroservices_web-py_4:5000",
	"ServiceName": "service-web",
	"ServiceTags": null,
	"ServiceAddress": "172.22.0.6",
	"ServicePort": 5000
}, {
	"Node": "consulserver",
	"Address": "172.22.0.3",
	"ServiceID": "registrator:dockermicroservices_web-py_3:5000",
	"ServiceName": "service-web",
	"ServiceTags": null,
	"ServiceAddress": "172.22.0.5",
	"ServicePort": 5000
}, {
	"Node": "consulserver",
	"Address": "172.22.0.3",
	"ServiceID": "registrator:createnewinstance_web-py_1:5000",
	"ServiceName": "service-web",
	"ServiceTags": null,
	"ServiceAddress": "172.25.0.2",
	"ServicePort": 5000
}]

多了一個微服務,說明服務發現成功。

後記

最重要的還是要自己去操作與實踐,不然看懂了也沒用。

參考文獻:
基於Docker實現服務治理(一)
基於Docker實現服務治理(二)
本文首發於公眾號【zone7】,關注獲取最新推文。