作者: paul

在docker安裝pyodbc以連線到MSSQL的步驟

在docker安裝pyodbc以連線到MSSQL的步驟

在windows上,python要連線到mssql,只需要透過pyodbc,幾乎不用什麼設定,就可以輕鬆連線上mssql

但是在linux上,遇到的坑與血淚,相信前人遇到的已經太多了!

以下記錄一下步驟與眉角:

首先我們先假設已經有一個存在的docker container在運作了,裡面有基本python 3.6的環境(或其他版本,這邊以3.x為主,自行上docker hub找吧…)

連進去container後,有3大工程要施作…

1.安裝freetds

wget  http://ibiblio.org/pub/Linux/ALPHA/freetds/stable/freetds-stable.tgz

tar zxvf freetds-stable.tgz

cd freetds-0.91/

./configure --with-tdsver=7.1 --prefix=/usr/local/freetds0.91 --build=x86_64-pc-linux-gnu --host=x86_64-pc-linux-gnu --mandir=/usr/share/man --infodir=/usr/share/info --datadir=/usr/share --sysconfdir=/etc --localstatedir=/var/lib --libdir=/usr/lib64 --without-ldap --without-tcl --enable-pkinit --enable-thread-support --without-hesiod --enable-shared --with-system-et --with-system-ss --enable-dns-for-realm --enable-kdc-lookaside-cache --with-system-verto --disable-rpath --with-pkinit-crypto-impl=openssl --with-openssl

make

make install

 cat >> /usr/local/freetds0.91/etc/freetds.conf
加入
[TestDB]
host = mesotest.database.windows.net
port = 1433
tds version = 7.0

註:freetds.conf 的dump file = /tmp/freetds.log反註解,global的tds版本也要改成7.0一致的版本,有dump log的話,後續連線失敗的話,可以看的到錯誤原因,事半功倍

例: severity:9, dberr:20002[Adaptive Server connection failed], oserr:0[Success] –>tds版本問題,要調整,若8.0不行,就7.2->7.1->7.0往回裝

2.測試freetds連線

/usr/local/freetds0.91/bin/tsql -S TestDB -U [email protected] -P {password} -D test1

若freetds可以連線,也可以查詢的話,應該會像這樣:

可以下sql指令,也回傳的了資料集

2.設定ODBCInit

apt-get install unixodbc-dev
apt-get install python-pip

pip install pyodbc
#yum install gcc-c++

#關鍵中的關鍵
find /usr -name "*\.so" |egrep "libtdsodbc|libtdsS"
 #/usr/lib/libtdsS.so 
 #/usr/local/freetds0.91/lib/libtdsodbc.so

# cp /etc/odbcinst.ini /etc/odbcinst.ini.20160102

# cat >> /etc/odbcinst.ini

[SQL Server]
Description = FreeTDS ODBC driver for MSSQL
Driver = /usr/local/freetds0.91/lib/libtdsodbc.so
Setup = /usr/lib/libtdsS.so
FileUsage = 1

# 檢查一下驅動
# odbcinst -d -q
[SQL Server]

cat >> /etc/odbc.ini
[TESTDSN]
Driver          = SQL Server
Server          = xxx.xxx.xxx.xxx
User            = xxxx
TDS_Version     = 7.0
Port            = 1433

3.執行簡單的python連mssql程式

import pyodbc

conn =  pyodbc.connect("driver={SQL Server};server=mesotest.database.windows.net;PORT=1433 database=test1;[email protected];PWD=%s;TDS_Version=7.0;" % "{yourpassword}" )
cursor = conn.cursor()

query = "select getdate()"

print(query)
cursor.execute(query)
row = cursor.fetchone()
while row:
    print(str(row[0]))
    row = cursor.fetchone()

執行成功,我要哭了…凌晨3點了!!

根據網友們的分享,這裡還有一個很大的坑就是連線字串要包含TDS_Version的資訊,版本要跟freetds內配置的版本一樣…

否則就會陷入無限的…08001輪迴,而不知其所以然…

Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
pyodbc.Error: (‘08001’, ‘[08001] [unixODBC][FreeTDS][SQL Server]Unable to connect to data source (0) (SQLDriverConnect)’)

 

關鍵2篇REF

https://blog.csdn.net/samed/article/details/50449808

http://www.voidcn.com/article/p-vaxmczdi-dc.html

locust + nginx + tornado web server 壓測客戶端與服務端 自動化腳本與實測結果

locust + nginx + tornado web server 壓測客戶端與服務端 自動化腳本與實測結果

壓測是考驗整體架構吞吐量與穩定性的最直接方式

繼上一篇 找到了一個壓測新朋友Locust 傳送門

這一篇進一步透過自動化shell,可以加速建置你的壓測Client端集群與Server端集群

首先我們要租2台主機,讓其物理cpu、ram網卡獨立,然後可以預設在相同的DataCenter,以降低跨DataCenter的網路影響因素

整體壓測概念如下:

 

Initail #number of web server Shell Script

#!/bin/bash

read -p "Enter your webserver number to remove: " p_clear_count

for (( i=1 ; ((i < ($p_clear_count+1))) ; i=(($i+1)) ))
do
  sudo docker rm -f webtest$(printf 0%02d $i)
done;
sudo docker rm -f web-test-nginx

read -p "Enter your webserver number: " p_count

for (( i=1 ; ((i < ($p_count+1))) ; i=(($i+1)) ))
do
  sudo docker run -d --name webtest$(printf 0%02d $i) --network="webtest" -p $(printf 100%02d $i):6969 tornado-web-test
done;

sudo docker run --name web-test-nginx --network="webtest" -p 10000:10000 -v /mnt/nginx/conf.d/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx nginx-debug -g 'daemon off;'

Initail #number of locust client Shell Script

#!/bin/bash

Test_Url="http://{{hostname}}:10000"

read -p "Enter your locust client number to remove: " p_clear_count

for (( i=1 ; ((i < ($p_clear_count+1))) ; i=(($i+1)) ))
do
  sudo docker rm -f locust-slave$i
done;
sudo docker rm -f locust-master

read -p "Enter your locust client number: " p_count

sudo docker run -d --name locust-master --hostname locust-master \
 --network="webtest" \
 -p 8089:8089 -p 5557:5557 -p 5558:5558 \
 -v /mnt/webtest/locust-master:/locust \
 -e LOCUST_MODE=master \
 -e ATTACKED_HOST="$Test_Url" \
 grubykarol/locust

for (( i=1 ; ((i < ($p_count+1))) ; i=(($i+1)) ))
do
  sudo docker run -d --name locust-slave$i \
	 --network="webtest" \
	 --env NO_PROXY=locust-master \
	 -e ATTACKED_HOST="$Test_Url" \
	 -v /mnt/webtest/locust-slave:/locust \
	 -e LOCUST_MODE=slave \
	 -e LOCUST_MASTER=locust-master \
	 --rm grubykarol/locust
done;

 

實測下來,8g開10個container(共享)的情況下,估計10000 concurrent user應該已經是很極限了,其實我們會發現極限應該是會在client發出請求端

web server多少會因為os、網卡、容器網路的限制,導致同時connection數無法無限上崗,而web server的運作還不包含更複雜的運算情境(只考慮in ram處理與非同步mongodb log)

因此以此為基準來當作未來擴充的計算基礎參考,應該還ok,若要模擬更複雜的商業邏輯運作,那麼可以仿照此作法去刻

10k

20k (RPS反而降了,看來還是有其極限存在)

美中不足的就是怎麼動態改Nginx的Nginx.Conf,把ReversProxy動態換掉,這個以後再研究吧…

 

100k on AWS
Reference:
1.https://aws.amazon.com/tw/blogs/devops/using-locust-on-aws-elastic-beanstalk-for-distributed-load-generation-and-testing/
2.https://www.slideshare.net/AmazonWebServices/aws-reinvent-2016-how-to-launch-a-100kuser-corporate-back-office-with-microsoft-servers-and-aws-win303?from_action=save

壓測工具的新選擇!! Locust load test framework

壓測工具的新選擇!! Locust load test framework

繼上一篇,自建nginx分流後(傳送門),接著的問題就是要如何驗證分流效果與實際上這樣架構的負載吞吐量到底能到多少了。傳統想到壓測,我們就會想到jmeter,但是jmeter的參數配置繁多,而且若要做到可程式化的話其路可能相當曲折;再者,若在我們實體主機上測試的話,極有可能受限於其物理極限,包含頻寬與網卡的能力,因此我搜尋了一下python有沒有load test framework,又可以同時支援分散式的load test

 

結果發現了這套名叫:locust的套件!!其安裝方式相當的簡單:

首先,整份框架都是python寫的,透過pip就可以安裝,因此先來試著打包成映像檔,變成隨時可以使用的容器吧:

測試Script寫起來相當簡潔,我們直接存成locustfile.py,實作HttpLocust、TaskSet後,屆時會在locust的壓測ui介面,讀取到接口

from locust import HttpLocust, TaskSet, task


class WebTest(TaskSet):

    @task
    def get_uuid(self):
        self.client.get("/webtest?action=get_uuid")


class WebsiteTest(HttpLocust):
    task_set = WebTest

Dockerfile的內容:

FROM python:3.6-alpine

COPY docker-entrypoint.sh /

RUN    apk --no-cache add --virtual=.build-dep build-base \
    && apk --no-cache add libzmq \
    && pip install --no-cache-dir locustio==0.8.1 \
    && apk del .build-dep \
    && chmod +x /docker-entrypoint.sh

RUN  mkdir /locust
WORKDIR /locust
EXPOSE 8089 5557 5558

ENTRYPOINT ["/docker-entrypoint.sh"]

docker-entrypoint.sh的內容:

#!/bin/sh
set -e
LOCUST_MODE=${LOCUST_MODE:-standalone}
LOCUST_MASTER_BIND_PORT=${LOCUST_MASTER_BIND_PORT:-5557}
LOCUST_FILE=${LOCUST_FILE:-locustfile.py}

if [ -z ${ATTACKED_HOST+x} ] ; then
    echo "You need to set the URL of the host to be tested (ATTACKED_HOST)."
    exit 1
fi

LOCUST_OPTS="-f ${LOCUST_FILE} --host=${ATTACKED_HOST} --no-reset-stats $LOCUST_OPTS"

case `echo ${LOCUST_MODE} | tr 'a-z' 'A-Z'` in
"MASTER")
    LOCUST_OPTS="--master --master-bind-port=${LOCUST_MASTER_BIND_PORT} $LOCUST_OPTS"
    ;;

"SLAVE")
    LOCUST_OPTS="--slave --master-host=${LOCUST_MASTER} --master-port=${LOCUST_MASTER_BIND_PORT} $LOCUST_OPTS"
    if [ -z ${LOCUST_MASTER+x} ] ; then
        echo "You need to set LOCUST_MASTER."
        exit 1
    fi
    ;;
esac

cd /locust
locust ${LOCUST_OPTS}

單一台load test測試端 啟動指令 (驗證ok):

sudo docker run --name locust-master --hostname locust-master \
--network="webtest" \
-p 8089:8089 -p 5557:5557 -p 5558:5558 \
-v /home/paul/webtest/locust:/locust \
-e ATTACKED_HOST='http://web-test-nginx:10000' \
grubykarol/locust

分散式的測試端配置(待驗證):

啟動指令-master測試端:

docker run --name master --hostname master `
 -p 8089:8089 -p 5557:5557 -p 5558:5558 `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST='http://master:8089' `
 -e LOCUST_MODE=master `
 --rm -d grubykarol/locust

啟動指令-slave測試端:

docker run --name slave0 `
 --link master --env NO_PROXY=master `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST=http://master:8089 `
 -e LOCUST_MODE=slave `
 -e LOCUST_MASTER=master `
 --rm -d grubykarol/locust
docker run --name slave1 `
 --link master --env NO_PROXY=master `
 -v c:\locust-scripts:/locust `
 -e ATTACKED_HOST=http://master:8089 `
 -e LOCUST_MODE=slave `
 -e LOCUST_MASTER=master `
 --rm -d grubykarol/locust

 

容器執行後,打入http://xxx.xxx.xxx.xxx:8090,就可以連到對應的監控網址(這個是最棒的),一開始就會問你模擬的u數,以及你希望多久時間內要衝到該u數

摘要總表:

即時圖表:

錯誤分析:

 

同時,我去驗證了我們mongodb的log,是符合他load test的請求數量

 

以上是這次接觸到新的壓測工具實作的小小記錄,也推薦給大伙

 

參考:

https://locust.io/

https://docs.locust.io/en/stable/

https://medium.com/locust-io-experiments/locust-io-experiments-running-in-docker-cae3c7f9386e

 

 

 

Nginx Proxy建立Load Balance分流機制

Nginx Proxy建立Load Balance分流機制

傳統在雲端平台上,通常都會有現成的Load Balance服務,提供彈性負載分流到自己的應用程式集群

假如我們希望在私有雲下,或是在自己家裡,希望也可以建置Load Balancer的話,透過硬體的F5機制成本高昂

這個時候,就可以依賴Nginx的套件了

其特點是可以大量處理併發連線

Nginx在官方測試的結果中,能夠支援五萬個並列連接,而在實際的運作中,可以支援二萬至四萬個並列連結。

 

假設以下情境,我們希望建立一個WebTest的測試環境,統一1個domain的port為進入點,但背後可能有很多台Api或子web站台來支持不同的服務與運算

因此這個時候,我們需要nginx來做為API.Domain的代理服務器,將實際的請求轉導到對應的內部伺服器,再把結果回傳回去

我們以Docker為測試環境,方便模擬多台伺服器的情況,而Docker在容器間的網路連線上,提供許多Api可以方便我們建置集群

我透過Python寫一隻輕量運算的api server(這也可以是其他案例,例如取得天氣、股市、時間…等)作為範例

當api層級深度太高的話,瓶頸識別會愈來愈不單純,因此我先在這邊假定問題點就是單一台吞吐量有上限,因此我們透過多台+load balance來支持同時併發的連線請求

小型的python get uuid tornado web server

import datetime
import socket
import json
import os
import sys
import uuid
from collections import OrderedDict
from multiprocessing.pool import Pool

import asyncio
import tornado
from tornado import web, gen
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from mongodb_helper import MongoDBHelper

sys.path.append( os.path.abspath( os.path.join( os.path.abspath( __file__ ), os.pardir, os.pardir ) ) )
sys.path.append( "/usr/src/app" )

def get_server_ip():
    return  (([ip for ip in socket.gethostbyname_ex( socket.gethostname() )[2] if not ip.startswith( "127." )] or [[(s.connect( ("8.8.8.8", 53) ), s.getsockname()[0], s.close()) for s in [socket.socket( socket.AF_INET, socket.SOCK_DGRAM )]][0][1]]) + ["no IP found"])[0]

def log(from_ip, action, data):
    service = MongoDBHelper( host="mongodb-dev", port=27017 )
    service.change_db( "tornado_web_test" )
    service.insert_data( collection_name="requestLog_"+get_server_ip() , data=dict( from_ip=from_ip, action=action, data=data ) )

class WebTestEntryHandler( tornado.web.RequestHandler ):
    def initialize(self, pool=None):
        self.local_pool = pool

    def set_default_headers(self):
        self.set_header( "Access-Control-Allow-Origin", "*" )
        self.set_header( "Access-Control-Allow-Headers", "x-requested-with" )
        self.set_header( 'Access-Control-Allow-Methods', 'POST, GET, OPTIONS' )
        self.set_header( "Access-Control-Allow-Headers", "Content-Type" )
        self.set_header( 'Content-Type', 'application/json' )

    def options(self, *args, **kwargs):
        # no body
        self.set_status( 200 )
        self.finish()

    def get_uuid(self):
        requestTime = datetime.datetime.today().strftime( '%Y-%m-%d %H:%M:%S.%f' )[:-3]

        result = str(uuid.uuid4())

        responseTime = datetime.datetime.today().strftime( '%Y-%m-%d %H:%M:%S.%f' )[:-3]

        resultObj = OrderedDict([("IsSuccess", True), ("Data", result), ("RequestTime",requestTime), ("ResponseTime", responseTime)])

        return resultObj

    def common(self, action):
        try:

            if action == "get_uuid":
                resultObj = self.get_uuid()
            else:
                resultObj=dict(IsSuccess=False, Message="action not found")

            self.local_pool.apply_async(log, (self.request.remote_ip, action, resultObj,) )

        except Exception as err:
            resultObj = dict( IsSuccess=False, Message=str(err) )

        if resultObj != None:
            self.write( json.dumps( resultObj ) )

    @gen.coroutine
    def get(self):
        action = None
        if self.get_argument('action', default=None) != None:
            action = self.get_argument('action')
        self.common(action=action)


    @gen.coroutine
    def post(self):
        action = None
        if self.get_argument( 'action', default=None ) != None:
            action = self.get_argument( 'action' )
        self.common( action=action )

def serve(host, port, pool):
    import socket
    if host in ["", None]:
        ip_address = socket.gethostbyname( socket.gethostname() )
    else:
        ip_address = host

    # tornado.options.parse_command_line() not work for websocket
    app = tornado.web.Application( default_host=ip_address, handlers=[
        (r"/webtest", WebTestEntryHandler, dict( pool=pool )),
    ] )
    http_server = HTTPServer( app, max_body_size=1500 * 1024 * 1024 * 1024 )
    http_server.listen( port )  # 1.5M
    io_loop = tornado.ioloop.IOLoop.current()
    print("rest server ready to start!")
    io_loop.start()


def app(pool):
    rest_host_str = "0.0.0.0"
    rest_port_str = "6969"
    rest_port = int( rest_port_str )
    # define( "port", default=rest_port, help="run on the given port", type=int )
    serve( rest_host_str, rest_port, pool )

if __name__ == "__main__":
    pool = Pool( processes=4 )  # start 4 worker processes
    app(pool)

註:上面python的實作,為了統計server的請求處理數據,因此加入了寫入mongodb的異步流程,可以參考使用,呼叫mongodb連線時,記得也要使用container name哦,不然會連不到

將其server打包成容器

FROM python:3

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD [ "python", "./rest_server.py" ]

#建立測試api 服務容器映像檔
sudo docker build -t tornado-web-test .

#建立容器群網路,網路內的容器可直接互連
sudo docker network create webtest

#建立容器實例(共3台),分別佔用10001, 10002, 10003
sudo docker run -d –name webtest001 –network=”webtest” -p 10001:6969 tornado-web-test
sudo docker run -d –name webtest002 –network=”webtest” -p 10002:6969 tornado-web-test
sudo docker run -d –name webtest003 –network=”webtest” -p 10003:6969 tornado-web-test

分別請求10001,10002,10003的http://xxx.xxx.xxx.xxx:10001/webtest?action=get_uuid後,可以正常的回應即可

接著主要工作就是配置nginx,其設定檔,很明確的說明了我希望它扮演的角色

nginx.conf

http{
 upstream webtest.localhost {
    server webtest001:6969;
    server webtest002:6969;
    server webtest003:6969;
 }
 
 server {

   listen 10000;

   #ssl_certificate /etc/nginx/certs/demo.pem;
   #ssl_certificate_key /etc/nginx/certs/demo.key;

   gzip_types text/plain text/css application/json application/x-javascript
              text/xml application/xml application/xml+rss text/javascript;

   server_name localhost;

   location / {
       proxy_pass http://webtest.localhost;
   }
 }
}



 events {
   worker_connections  1024;  ## Default: 1024
 }

注意:這邊的webtest1~3的port號都是6969,雖然在之前docker run的時候,有expose綁到其他port號,但是在docker的容器網路內部仍是採用原本容器的設定,因此這邊一定要用容器配置,而不是容器expose的配置,這邊找了一陣子

關於upstream的標籤,官方文件如下:

範例:
upstream backend {
    server backend1.example.com weight=5;
    server 127.0.0.1:8080       max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;

    server backup1.example.com  backup;
}

標籤下,我們可以定義伺服器群組,各別伺服器可以監聽不同的port,而且可以TCP/Socket混用

預設,請求會被透過round-robin balancing方法的權重來分配到不同的伺服器,以此為例,每7個請求,會被分配5個請求到backend1.example.com,還有2個分別被轉送到2、3伺服器

假如轉送過程中,有發生error,該請求會自動pass給下一個伺服器,直到所有的伺服器都試過為止。假如沒有任何伺服器可以回傳正確的結果,那用戶端的通訊結果將會是最後一台伺服器的訊息。

 

我們啟動Nginx容器,並試著去連線對外的10000 port

#nginx docker command
sudo docker run –name web-test-nginx –network=”webtest” -p 10000:10000 -v /home/paul/webtest/nginx/conf.d/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx nginx-debug -g ‘daemon off;’

實測:http://xxx.xxx.xxx.xxx:10000/webtest?action=get_uuid

打網址,若可以出現這個畫面,那就代表可以work了,多打幾次後,我追蹤mongodb裡的log,可以看到不同的server都有接到請求

大工告成!!

透過這樣的架構,我們可以讓原本單一一台的請求量提升到n台,假如nginx的配置沒有爆的話,那只要擔心後端的每個端點是否服務正常(這關系到監控機制)

當然,docker、vm,個人電腦都有其物理極限,包含網卡、頻寬,伺服器的連線上限…etc,因此負載測試這個issue,有時因為成本過高,我們會測出單位的負載量後再加倍估算。

這個就另開討論吧…

[centos] 為Tornado開啟SSL-生成SSL證書

[centos] 為Tornado開啟SSL-生成SSL證書

首先要生成伺服器端的私密金鑰(key檔):

sudo openssl genrsa -des3 -out server.key 1024
  1. 運行時會提示輸入密碼,此密碼用於加密key檔(參數des3便是指加密演算法,當然也可以選用其他你認為安全的演算法.),以後每當需讀取此檔(通過openssl提供的命令或API)都需輸入口令.如果覺得不方便,也可以去除這個口令,但一定要採取其他的保護措施!

去除key文件口令的命令:

生成CSR檔:

sudo openssl req -new -key server.key -out server.csr -config /etc/pki/tls/openssl.cnf
  1. 生成Certificate Signing Request(CSR),生成的csr檔交給CA簽名後形成服務端自己的證書.螢幕上將有提示,依照其指示一步一步輸入要求的個人資訊即可.

對用戶端也作同樣的命令生成key及csr文件:

CSR檔必須有CA的簽名才可形成證書.可將此檔發送到verisign等地方由它驗證,要交一大筆錢,何不自己做CA呢.

  1. 在/etc/pki/CA/目錄下新建目錄certs、newcerts
  2. 建立一個空檔 index.txt
  3. 建立一個文字檔 serial, 沒有副檔名,內容是一個合法的16進制數字,例如 0000
  4. sudo openssl req -new -x509 -keyout ca.key -out ca.crt -config /etc/pki/tls/openssl.cnf
    
    sudo openssl genrsa -des3 -out client.key 1024
    
    sudo openssl req -new -key client.key -out client.csr -config openssl.cnf

用生成的CA的證書為剛才生成的server.csr,client.csr文件簽名:

sudo openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key -config /etc/pki/tls/openssl.cnf

sudo openssl ca -in client.csr -out client.crt -cert ca.crt -keyfile ca.key -config /etc/pki/tls/openssl.cnf

到了這裡應該已經創建了可以使用的證書了,如果在為檔簽名的時候有錯誤,那多半是資訊不正確,這時可以去清空一下 index.txt 裡的資訊,然後重新執行第5步裡失敗的操作。

在Tornado啟用https server

http_server = HTTPServer( app, max_body_size=1500 * 1024 * 1024 * 1024,
ssl_options={"certfile": os.path.join( os.path.abspath( "." ), "server.crt" ),
			 "keyfile": os.path.join( os.path.abspath( "." ), "server.key" )} )
[轉錄]如何設計一個小而美的秒殺系統? 作者-劉鵬

[轉錄]如何設計一個小而美的秒殺系統? 作者-劉鵬

如何設計一個小而美的秒殺系統

 作者 劉鵬 發佈於 201737

現如今,春節搶紅包的活動已經逐漸變成大家過年的新風俗。親朋好友的相互饋贈,微信、微博、支付寶等各大平臺種類繁多的紅包讓大家收到手軟。雞年春節,鏈家也想給15萬的全國員工包個大紅包,於是我們構建了一套旨在支撐10萬每秒請求峰值的搶紅包系統。經實踐證明,春節期間我們成功的為所有的小夥伴提供了高可靠的服務,紅包總發放量近百萬,搶紅包的峰值流量達到3萬/秒,最快的一輪搶紅包活動3秒鐘所有紅包全部搶完,系統運行0故障。

紅包系統,類似于電商平臺的秒殺系統,本質上都是在一個很短的時間內面對巨大的請求流量,將有限的庫存商品分發出去,並完成交易操作。比如12306搶票,庫存的火車票是有限的,但暫態的流量非常大,且都是在請求相同的資源,這裡面資料庫的併發讀寫衝突以及資源的鎖請求衝突非常嚴重。就我們實現這樣一個紅包系統本身來說,面臨著如下的一些挑戰:

首先,到活動整點時刻,我們有15萬員工在固定時間點同時湧入系統搶某輪紅包,瞬間的流量是很大的,而目前我們整個鏈路上的系統和服務基礎設施,都沒有承受過如此高的輸送量,要在短時間內實現業務需求,在技術上的風險較大。

其次,公司是第一次開展這樣的活動,我們很難預知大家參與活動的情況,極端情況下可能會出現某輪紅包沒搶完,需要合併到下輪接著發放。這就要求系統有一個動態的紅包發放策略和預算控制,其中涉及到的動態計算會是個較大的問題(這也是為系統高吞吐服務),實際的系統實現中我們採用了一些預處理機制。

最後,這個系統是為了春節的慶祝活動而研發的定制系統,且只上線運行一次,這意味著我們無法積累經驗去對服務做持續的優化。並且相關的配套環境沒有經過實際運行檢驗,缺少參考指標,系統的薄弱環節發現的難度大。所以必須要追求設計至簡,儘量減少對環境的依賴(資料路徑越長,出問題的環節越多),並且實現高可伸縮性,需要盡一切努力保證可靠性,即使有某環節失誤,系統依然能夠保障核心的用戶體驗正常。

系統設計


紅包本身的資訊通過預處理資源介面獲取。運行中使用者和紅包的映射關係動態生成。底層使用內部開發的DB中介軟體在MySQL資料庫集群上做紅包發放結果持久化,以供非同步支付紅包金額到使用者帳戶使用。整個系統的絕大部分模組都有性能和保活監控。系統架構圖如圖所示。所有的靜態資源提前部署在了協力廠商的CDN服務上,系統的核心功能主要劃分到接入層和核心邏輯系統中,各自部署為集群模式並且獨立。接入層主要是對用戶身份鑒權和結果緩存,核心系統重點關注紅包的分發,紅色實線的模組是核心邏輯,為了保障其可靠性,我們做了包括資料預處理、水準分庫、多級緩存、精簡RPC調用、超載保護等多項設計優化,並且在原生容器、MySQL等服務基礎設施上針對特殊的業務場景做了優化,後面將為讀者一一道來。

 

優化方案

優化方案中最重要的目標是保障關鍵流程在應對大量請求時穩定運行,這需要很高的系統可用性。所以,業務流程和資料流程程要儘量精簡,減少容易出錯的環節。此外,緩存、DB、網路、容器環境,任何一個部分都要假設可能會短時出現故障,要有處理預案。針對以上的目標難點,我們總結了如下的實踐經驗。

1.數據預處理

紅包本身的屬性資訊(金額,狀態,祝福語,發放策略),我們結合活動預案要求,使用一定的演算法提前生成好所有的資訊,資料總的空間不是很大。為了最大化提升性能,這些紅包資料,我們事先存儲在資料庫中,然後在容器載入服務啟動時,直接載入到本地緩存中當作唯讀資料。另外,我們的員工資訊,我們也做了一定的裁剪,最基本的資訊也和紅包資料一樣,預先生成,服務啟動時載入。

此外,我們的活動頁面,有很多視頻和圖片資源,如果這麼多的用戶從我們的閘道即時訪問,很可能我們的頻寬直接就被這些大流量的請求占滿了,用戶體驗可想而知。最後這些靜態資源,我們都部署在了CDN上,通過資料預熱的方式加速用戶端的存取速度,閘道的流量主要是來自於搶紅包期間的小資料請求。

2.精簡RPC調用

通常的服務請求流程,是在接入層訪問用戶中心進行用戶鑒權,然後轉發請求到後端服務,後端服務根據業務邏輯調用其他上游服務,並且查詢資料庫資源,再更新服務/資料庫的資料。每一次RPC調用都會有額外的開銷,所以,比如上一點所說的預載入,使得系統在運行期間每個節點都有全量的查詢資料可在本地訪問,搶紅包的核心流程就被簡化為了生成紅包和人的映射關係,以及發放紅包的後續操作。再比如,我們採用了非同步拉的方式進行紅包發放到賬,用戶搶紅包的請求不再經過發放這一步,只記錄關係,性能得到進一步提升。

實際上有些做法的可伸縮性是極強的。例如紅包資料的預生成資訊,在當時的場景下我們是能夠作為本地記憶體緩存加速訪問的。當紅包資料量很大的時候,在每個服務節點上使用本地資料庫,或者本地資料檔案,甚至是本地Redis/MC緩存服務,都是可以保證空間足夠的,並且還有額外的好處,越少的RPC,越少的服務抖動,只需要關注系統本身的健壯性即可,不需要考慮外部系統QoS。

3.搶紅包的併發請求處理

春節整點時刻,同一個紅包會被成千上萬的人同時請求,如何控制併發請求,確保紅包會且僅會被一個用戶搶到?

做法一,使用加鎖操作先佔有鎖資源,再佔有紅包。

可以使用分散式全域鎖的方式(各種分散式鎖元件或者資料庫鎖),申請lock該紅包資源成功後再做後續操作。優點是,不會出現髒資料問題,某一個時刻只有一個應用執行緒持有lock,紅包只會被至多一個使用者搶到,資料一致性有保障。缺點是,所有請求同一時刻都在搶紅包A,下一個時刻又都在搶紅包B,並且只有一個搶成功,其他都失敗,效率很低。

做法二,單獨開發請求排隊調度模組。

排隊模組接收使用者的搶紅包請求,以FIFO模式保存下來,調度模組負責FIFO佇列的動態調度,一旦有空閒資源,便從佇列頭部把使用者的訪問請求取出後交給真正提供服務的模組處理。優點是,具有中心節點的統一資源管理,對系統的可控性強,可深度定制。缺點是,所有請求流量都會有中心節點參與,效率必然會比分散式無中心系統低,並且,中心節點也很容易成為整個系統的性能瓶頸。

做法三,巧用Redis特性,使其成為分散式序號生成器。(我們最終採用的做法)。

访问请求被LB分发到每个分组,一个分组包含若干台应用容器、独立的数据库和Redis节点。Redis节点内存储的是这个分组可以分发的红包ID号段,利用Redis单进程的自减数值特性实现分布式红包ID生成器,服务通过此获取当前拆到的红包。落地数据都持久化在独立的数据库中,相当于是做了水平分库。某个分组内处理的请求,只会访问分组内部的Redis和数据库,和其他分组隔离开。
分组的方式使得整个系统实现了高内聚,低耦合的原则,能将数据流量分而治之,提升了系统的可伸缩性,当面临更大流量的需求时,通过线性扩容的方法,即可应对。并且当单个节点出现故障时,影响面能够控制在单个分组内部,系统也就具有了较好的隔离性。

4.系统容量评估,借助数据优化,过载保护

由于是首次开展活动,我们缺乏实际的运营数据,一切都是摸着石头过河。所以从项目伊始,我们便强调对系统各个层次的预估,既包括了活动参与人数、每个功能feature用户的高峰流量、后端请求的峰值、缓存系统请求峰值和数据库读写请求峰值等,还包括了整个业务流程和服务基础设施中潜在的薄弱环节。后者的难度更大因为很难量化。此前我们连超大流量的全链路性能压测工具都较缺乏,所以还是有很多实践的困难的。
在这里内心真诚的感谢开源社区的力量,在我们制定完系统的性能指标参考值后,借助如wrk等优秀的开源工具,我们在有限的资源里实现了对整个系统的端到端全链路压测。实测中,我们的核心接口在单个容器上可以达到20,000以上的QPS,整个服务集群在110,000以上的QPS压力下依然能稳定工作。
正是一次次的全链路压测参考指标,帮助我们了解了性能的基准,并以此做了代码设计层面、容器层面、JVM层面、MySQL数据库层面、缓存集群层面的种种优化,极大的提升了系统的可用性。具体做法限于篇幅不在此赘述,有兴趣的读者欢迎交流。
此外,为了确保线上有超预估流量时系统稳定,我们做了过载保护。超过性能上限阈值的流量,系统会快速返回特定的页面结果,将此部分流量清理掉,保障已经接受的有效流量可以正常处理。

5.完善监控

系统在线上运行过程中,我们很需要对其实时的运行情况获取信息,以便能够对出现的问题进行排查定位,及时采取措施。所以我们必须有一套有效的监控系统,能够帮我们观测到关键的指标。在实际的操作层面,我们主要关注了如下指标:
服务接口的性能指标
借助系统的请求日志,观测服务接口的QPS,接口总的实时响应时间。同时通过HTTP的状态码观测服务的语义层面的可用性。
系统健康度
结合总的性能指标以及各个模块应用层的性能日志,包括模块接口返回耗时,和应用层日志的逻辑错误日志等,判断系统的健康度。
整体的网络状况
尽量观测每个点到点之间的网络状态,包括应用服务器的网卡流量、Redis节点、数据库节点的流量,以及入口带宽的占用情况。如果某条线路出现过高流量,便可及时采取扩容等措施缓解。
服务基础设施
应用服务器的CPU、Memory、磁盘IO状况,缓存节点和数据库的相应的数据,以及他们的连接数、连接时间、资源消耗检测数据,及时的去发现资源不足的预警信息。
对于关键的数据指标,在超过预估时制定的阈值时,还需要监控系统能够实时的通过手机和邮件实时通知的方式让相关人员知道。另外,我们在系统中还做了若干逻辑开关,当某些资源出现问题并且自动降级和过载保护模块失去效果时,我们可以根据状况直接人工介入,在服务不停机的前提前通过手动触发逻辑开关改变系统逻辑,达到快速响应故障,让服务尽快恢复稳定的目的。

6.服务降级

当服务器压力剧增的时候,如果某些依赖的服务设施或者基础组件超出了工作负荷能力,发生了故障,这时候极其需要根据当前的业务运行情况对系统服务进行有策略的降级运行措施,使得核心的业务流程能够顺利进行,并且减轻服务器资源的压力,最好在压力减小后还能自动恢复升级到原工作机制。
我们在开发红包系统时,考虑到原有IDC机房的解决方案对于弹性扩容和流量带宽支持不太完美,选择了使用AWS的公有云作为服务基础环境。对于第三方的服务,缺少实践经验的把握,于是从开发到运维过程中,我们都保持了一种防御式的思考方式,包括数据库、缓存节点故障,以及应用服务环境的崩溃、网络抖动,我们都认为随时可能出问题,都需要对应的自动替换降级策略,严重时甚至可通过手动触发配置开关修改策略。当然,如果组件自身具有降级功能,可以给上层业务节约很多成本资源,要自己实现全部环节的降级能力的确是一件比较耗费资源的事情,这也是一个公司技术慢慢积累的过程。

结束语

以上是我们整个系统研发运维的一些体会。这次春节红包活动,在资源有限的情况下成功抵抗超乎平常的流量峰值压力,对于技术而言是一次很大的挑战,也是一件快乐的事情,让我们从中积累了很多实践经验。未来我们将不断努力,希望能够将部分转化成较为通用的技术,去更好的推动业务成功。真诚希望本文的分享能够对大家的技术工作有所帮助。

感谢郭蕾对本文的审校。

 

(FYI) MGIndexs的威力

(FYI) MGIndexs的威力

MGIndexs的威力

bitmap 索引是一種索引形式,它以行的形式存儲一系列位的值。例如,如果您有一百萬條記錄,並且正在名稱列中搜索’bob’,那麼名稱列的b +樹可能類似於下面的圖片,並且當您在葉節點中找到’bob’時,您將得到一個BitArray,表示在該列中按照記錄號索引的’bob’的存在。所以如果BitArray的第3位是1,那麼’bob’在第3行等等。很容易看出這是非常緊湊和高效的存儲和檢索機制。

xxxx
*上面的圖片顯示了b +樹/位圖索引結構,而不是使用字典的MGIndex,但原理相同。

下面的例子顯示了實際的功能,您想要查詢以下的“Name =’bob’和Code = between(1,3)”,查詢處理器將採用過濾器,解析該過濾器的值並生成執行計劃如下:xxxx
從上圖可以看出,執行計劃是一系列BitArrays,您需要做的就是根據分析的過濾器和{AND,OR,NOT} BitArrays一起跟踪位算術邏輯。這些操作通常在亞毫秒範圍內,即使對於數百萬或數據庫中的行也是如此。結果是一個位置索引行查找到您從磁盤讀取的行內容,並發送給調用者,其中1表示讀取行,0表示跳過行內容(即100001 … – >讀取記錄號:1, 6,…)

 

 

ElasticSearch Auto Build With Docker

ElasticSearch Auto Build With Docker

近日因為大數據寫入與分析應用需求的關系

需要大量的使用到ElasticSearch

然而,每次在不同的環境要佈署ELK並配置不同的node來達到Sharding的效果的工作覺得繁瑣

因此手刻了一些半自動化部署Elasticsearch的Cluster的機制(同一台主機,不同的Docker),不同台需要參考使用Docker Swarn

 

以下是相關Shell Script操作,相關步驟如下:

0.設定相關的環境變數,例如java memory, elasticsearch version

1.詢問要建立的容器名稱

2.詢問export的port名稱(有2組,通常為9200、9300)

3.動態建立容器網路(若容器放在同一個網路下無綁定方向問題問題)

4.動態建立cluster的目錄(容器名稱會建立在此目錄下再建立對應容器的目錄)與相關config/esdatadir、log、data目錄,並設立權限

5.動態從版控取得對應的ElasticSearch的config,也可以自行改成放在某個統一位置取得 (網址:http://xxx.xxx.xxx.xxx:yyyy是git的位置)

6. sudo sysctl -w vm.max_map_count=262144(這句不知為什麼一定要執行..java環境才起的來)

7.啟動Docker(並結合相關參數)

#!/bin/bash


sudo docker ps -a
read -p "Enter your elasticsearch container_name(eg. es1, es2): " p_container_name

container_name=$p_container_name
java_memory="2g"
es_version="5.6.6"

read -p "Enter your elasticsearch container_name default export port1(eg. 9200, 9201, 9202): " port1

export_port1=$port1

read -p "Enter your elasticsearch container_name default export port1(eg. 9300, 9301, 9302): " port2
export_port2=$port2

network_name="es_net"

if [ ! "$(sudo docker network ls |grep $network_name)" ]; then
  sudo docker network create "$network_name"
  echo "network created!"
else
  sudo docker network ls | grep "$network_name"
  echo "network $network_name already exist!"
fi

echo "continue..."

DIRECTORY="es_cluster"

sudo cd /mnt

if [ ! -d "$DIRECTORY" ]; then
  sudo mkdir "$DIRECTORY"
fi

cd es_cluster
sudo mkdir "$container_name"
cd "$container_name"

echo "$container_name folder created!"

sudo git init
sudo git remote add origin "http://xxx.xxx.xxx.xxx:yyyy/scm/mes/elasticsearch.git"
sudo git pull origin master
sudo mkdir config/scripts
sudo mkdir esdatadir
cd esdatadir
sudo mkdir log
sudo mkdir data
sudo chmod 777 /mnt/es_cluster/$container_name/esdatadir/*
sudo chmod 777 /mnt/es_cluster/$container_name/config/scripts

echo "$container_name configuration setting done!"

cd /mnt/es_cluster/$container_name

sudo sysctl -w vm.max_map_count=262144

sudo docker run -d -v "/mnt/es_cluster/$container_name/esdatadir/data":/var/lib/elasticsearch \
 -v "/mnt/es_cluster/$container_name/esdatadir/log":/var/log/elasticsearch \
 -v "/mnt/es_cluster/$container_name/config":/usr/share/elasticsearch/config \
 -p "$export_port1:9200" -p "$export_port2:9300" --name $container_name -e "bootstrap.memory_lock=true" \
 -e ES_JAVA_OPTS="-Xms$java_memory -Xmx$java_memory" --ulimit memlock=-1:-1 \
 --restart always \
 --net "$network_name" \
 elasticsearch:$es_version

echo "$container_name docker container up done!"
00-01 關於前端開發生態圈的總整理(Base on React)

00-01 關於前端開發生態圈的總整理(Base on React)

有鑑於最近旁聽Front-end meeting後,發現許多套件名詞擺在一起的時候好像會迷失方向,雖然一部分知道大概功能,但有一部分套件的角色與在整體生態圈的定位仍有些陌生

因此整理以下提供一個整體性的概念

React Tech Stack

前端框架工具介紹文:https://gist.github.com/weihanglo/ccefb5107b1d60669ded1177e6f954ca#es6babel

 

ReactJs:

React僅僅是Web應用程式中的視圖層(View),定義上不能算是框架,應用程式中可以將之分解成一個一個小的組件,因此你的Web站台可以是經由各種型式的元件(Component)組裝而成,當Web的問題與場景愈來愈具備挑戰性的時候,元件導向開發(Component-Driven Development)可以達成問題分解解決與並易於重用,而React的開發方法就是符合元件導向開發模式。

React之於元件、元件之於類別的使用方法初探,可見:https://paulfun.net/wordpress/?p=471

React的革命:Virtual Dom

當React的View engine要追踪模型更改並將其應用於DOM ,這個動作又稱為渲染(Rendering),React採用了Virtual Dom的演算法

Virtual Dom是可以加快渲染速度的機制,Dom的更改以往是透過在Browser層直接操作真實網頁上的Dom,React的差異化算法是使用Tree的形式去計算,在內存中,直接建立一組假的Dom,在內存中處理Dom的效能大幅提升,React同時也會盡可能的保持Dom大部分不變,保持最小幅度的更新

關於虛擬Dom的秘密:https://conferences.oreilly.com/fluent/fluent2014/public/schedule/detail/32395
React.js Tutorial React re-render

在Server端渲染?

因為react對DOM的處理是使用假的DOM,都在內存中進行,因此更可以將此渲染工作拋到Server上。僅僅只剩下讓Browser客戶端處理事件就好

註:React呈現的HTML標記包含data-reactid屬性,這有助於React跟踪DOM節點。

更多好文:https://blog.risingstack.com/the-react-way-getting-started-tutorial/

進一步:JSX語法糖

JSX的特性是讓標籤更加語意,更加簡化,可以整合JavaScript,以單單組合Div包含Class定義的標籤工作,JSX讓我們可以有如使用Template一樣簡潔:

一般JavaScript語法
1-1
JSX語法
2-1

當然 JSX 並非強制使用,你也可以選擇不用,因為最終 JSX 的內容還是會轉化成 JavaScript(瀏覽器只看的懂 JavaScript)

更多好文:https://blog.techbridge.cc/2016/04/21/react-jsx-introduction/ 、https://reactjs.org/docs/jsx-in-depth.html

強化React

React-Flux

React本身只是個MVC架構中的View(視圖)函式庫,沒有類似Model或Controller的角色,這個最初是由React的最早的創作小組所提出的概念性架構,雖然利用Action與Store解決了React 控制視圖的問題,但Flux發表至今並沒有太多源碼更新, 一方面被認為學習曲線高,另一方面是很妥善的設計,重用程式碼不少,因此建議參考Redux。

React Flux

更多好文:http://eddychang.me/blog/javascript/94-flux-concept.html

React-Redux

Redux 是管理應用狀態的js庫。是單向數據流應用架構中更優雅的實現,Redux 和 React 之間無依賴關係。Redux支持 React、Angular、Ember、jQuery 甚至純 JavaScript。

相較於Flux,Redux現在發展得很好,學習資源與使用者很多,也有很多週邊的擴充或應用可用,當遇到問題時比較容易找到解答,所以Redux應該是首選。

React Redux

更多的Redux:https://noootown.gitbooks.io/deeperience-react-native-boilerplate/content/Redux/Redux%20&%20React.html

 

NodeJs:

Node.js模塊系統,它擁有強大的npm套件管理機制。我們將以“Node風格”編寫我們的代碼,並引用Require我們所需的一切。React可作為一個單獨的npm package引用進來。

現在我們使用Node來管理系統的相依性(統一ES6),我們幾乎所有的解決方案都有npm可以來使用,那接下來就是選擇打包工具。

WebPack+Babel

WebPack

Webpack 是德國開發者 Tobias Koppers 開發的模組整合工具。它的核心功能如下:

  • 可同時整合 CommonJS 和 AMD 模組
  • 轉換 JSX, Coffee Script, TypeScript 等
  • 分散封裝專案使用的程式碼,使載入頁面時只需載入當頁所需的程式碼以加速載入速度
  • 整合樣式表 (css, sass, less 等)
  • 處理圖片與字型
  • 建置 production-ready 的程式碼 (壓縮)

更多好文:https://rhadow.github.io/2015/03/23/webpackIntro/

 

Babel

Babel是一個java script的Transpiler。Babel 轉換ES6的代碼到ES5
我們稱這個過程叫作transpiling。我們享用es6的功能與好處,但至今仍必須確保僅支援es5的瀏覽器仍然能運作。
Babel的另一個重要工作就是去瞭解JSX。Babel會編譯我們的jsx成ES5的js。讓我們的瀏覽器可以直譯與執行。

 

整理至此,仍發現,每個環節其實都可以更深入的探討,不過對於overall的角色定位已經有了更具體的認識,因此我整理總心智圖如下(可能有誤或缺,歡迎指教):

放大:https://paulfun.net/wordpress/wp-content/uploads/2018/03/591.png

 

若有新的消息或觀念上的修正,後續會再持續更新本文

結合docker swarm 建置ElasticSearch Cluster

結合docker swarm 建置ElasticSearch Cluster

最近因為有要整合ELK方案的需求,因此又再度接觸了這個曾經相當熟悉的工具,還記得那個時候(也才1年多前),還是在2.x版左右,kibana的介面還是長這個樣子的時候。

在先前的公司,有平台團隊專門架設這個服務提供RD團隊整合使用,對於使用是絲毫不陌生,但是建置卻完全沒有經驗。沒想到現在已經轉眼間來到了6.1版本,介面跟架構看起來調整的更鮮艷與簡潔(?),我知道ELK目前在windows上面仍然是有一鍵安裝的版本。對於開發要測試的需求上已經相當足夠(連結),但因為我們目前對外主機主要都是CentOS,因此我這次預計實作如下:

假設我有2台CentOS主機的情境下(一台稱為A Host, 一台稱作B Host),如何透過各別安裝elasticsearch的docker 容器,來串連成elasticsearch 的集群。

同一台的多個容器理論上是相對簡單的,而這邊會遇到的問題,主要的關鍵就是如何解決跨主機間docker容器的通信問題,網路架構也通常都是應用程式間管控較為複雜的一個部分

這邊我打算採用Docker Swarm,Swarm為Docker自行開發的容器調度工具,其中的跨主機建立overlay網路功能看起來是非常符合我的情境需求,由於是Docker平台內建工具,看起來並不需要額外的安裝與學習,因此我先從這套工具開始著手,至於關於其他容器調度工具還包含了,Kubernetes、Mesos等。甚至現在的顯學看起來是Kubernetes(k8s),有許多的討論也直指要如何決定該採用哪種方案。

目前看到這一篇(連結)是相對從各面向都有討論到,有興趣的可以看看

Docker swarm的架構圖(來自Docker官網)

從docker 1.9版開始,DockerSwarm已經是內建工具,因此透過以下指令,就可以將主機宣告成DockerSwarm的Manager主機(第一台總是master嘛)

sudo docker swarm init

若有成功啟動DockerSwarm的話,會提示以下訊息

docker swarm join –token SWMTKN-1-5wsc3yya3e87w2e84jkzbpieubpxr9v6qwnbj87m6t2ynv7kxm-8q5amih216368atw8adklx24f %A Host IP%:2377
這個hint很明顯就是我們可以在其他的Host(稱作Worker),透過以上指令來加入到Manager下的叢集中。同時他是透過A Host的2377 Port來進行串連
但假如希望建立起來的Swarm使用其他Port的話,我們可以使用以下指令來做
docker swarm init –advertise-addr=%A Host IP% –listen-addr %A Host IP%:2377
所以我們也可以使用這句語法檢查,是否有啟動了這兩個Port

sudo netstat -nap | grep ^tcp.*dockerd

正常應該會列出對應的2377與7946

tcp6 0 0 :::7946 :::* LISTEN 28184/dockerd
tcp6 0 0 :::2377 :::* LISTEN 28184/dockerd

接著我們在B Host上,輸入剛剛建立完swarm manager 所提示的加入語法

   docker swarm join –token SWMTKN-1-5wsc3yya3e87w2e84jkzbpieubpxr9v6qwnbj87m6t2ynv7kxm-8q5amih216368atw8adklx24f %A Host IP%:2377

在這邊若有無法加入的情況的話,可以往防火牆先檢查,若是防火牆確定有通(telnet看看),那我這邊有遇到原本怎樣都加入不了,但是重開機、重啟docker後,就可以加入的情況。

若Worker成功加入Swarm的話,我們可以在A Host輸入以下指令確認是否節點都完整

docker node ls

既然A-B Host已經加入了同一個網路架構,接著就是建立覆疊網路

sudo docker network create   –driver overlay  –attachable es_net
註:這邊–attachable 若後續要透過run 語法加入指定網路的話,參數一定要加,是不是Docker Compose就不用加,我不確定
成功建立overlay網路後,我們就可以透過以下指令看到我們的網路已經被建立起來,SCOPE是顯示swarm , Driver是顯示overlay
sudo docker network ls
現在網路架構已經看似完成,接著我們來準備Elasticsearh的服務配置吧
這邊我寫了一個腳本來自動建立elasticsearch的相關目錄,目前只要elasticsearch會異動到的檔案目錄權限要開啟來,否則之後容器啟動後,會有權限例外產生:
腳本中改777的部分都是因為遇到所以加上去的,可以自行改成合適的權限配置。
sudo cd /mnt
sudo mkdir elasticsearch
cd elasticsearch
sudo git init
sudo git remote add origin "http://git server ip/scm/mes/elasticsearch.git"
sudo git pull origin master
sudo mkdir config/scripts
sudo mkdir esdatadir
cd esdatadir
sudo mkdir log
sudo mkdir data
sudo chmod 777 /mnt/elasticsearch/esdatadir/*
sudo chmod 777 /mnt/elasticsearch/config/scripts

cd /mnt/elasticsearch

config下的檔案,因為A, B Host各自擁有自己的獨立硬碟,因此我假設他們的環境配置應該都一致,jvm.options、log4j2.properties、檔案都一樣,只有Elasticsearch的yml檔會有些許差異:

path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
cluster.name: meso_es_cluster1
node.master: true #if worker then mark it
node.name: ${HOSTNAME}
network.host: 0.0.0.0
discovery.zen.ping.unicast.hosts: ["A HOST IP", "B HOST IP"]
以上都完成以後,就是來啟動Docker 容器的時候了,以A Host為例,啟動語法如下,B Host只是改–name成es2
sudo docker run -d -v /mnt/elasticsearch/esdatadir/data:/var/lib/elasticsearch -v /mnt/elasticsearch/esdatadir/log:/var/log/elasticsearch -v /mnt/elasticsearch/config:/usr/share/elasticsearch/config -p 9200:9200 -p 9300:9300 --net=es_net --name es1 --security-opt seccomp=unconfined -e "bootstrap.memory_lock=true" -e ES_JAVA_OPTS="-Xms1g -Xmx1g" --ulimit memlock=-1:-1 elasticsearch:5.6.6

分別執行後,我們可以看到docker ps 下是否狀態是正常在up的。

補充說明,啟動es容器時,我曾經發現會有以下的錯誤訊息:max virtual memory areas vm.max_map_count [65530] is too low(請透過docker logs查看)
這個問題若資源分配問題的話,可以透過以下指令解決:
sudo sysctl -w vm.max_map_count=262144
 但我也有一個疑問,logs是記錄容器內部的jvm的錯誤,但是我是在容器外部下語法可解決,這個原因我確實不清楚就是了..

若都是正常,我們可以透過elasticsearch的Api來檢查我們的es cluster是否有正常運作起來

從瀏覽器輸入A Host IP:9200,會返回相關json資訊,若是如下,我們可以認定cluster有啟動,而且服務版本正確,

可以看到name的部分,就正是各別主機上Docker 容器的name,而cluster_name因為配置所以會一致、而若有正確加入的話,cluster_uuid必須要一致
註:我在實作的時候,就一直遇到B Host啟動的起來,但是他無法加入cluster,所以cluster_uuid一直顯示為_na_,後來竟然是整個docker服務重啟就好了,這個羅生門,讓我相信3R的救命招(Recycle, Reset, Reboot),測試期間,也可以透過docker exec -it B HOST Container IP ping -c 3 A_Host_IP來看看是否可以透過容器內解析出跨主機的另一個同網路下的容器名稱

如何觀察Elasticsearch的Cluster狀態呢?具瞭解在es 2.x版本的時候,有一套es的plugin,叫作kopf,是拿來可以直接透過es api來監控es服務的工具

後來5.x版以後,發現plugin功能被拔掉了,取而代之是獨立的”cerebro“套件可以達成完全一樣的功能,看來是同個作者寫的。

介面如下

只要在Node Address輸入A Host es容器名稱:9200(注意容器互連必須透過容器名稱,而不能只是ip),就可以看到以下的dashboard,相當的方便,除了可以即時監控Node的資源使用狀況,也可以看到目前的index數量與使用狀況、空間

cerebro 這邊,只需要透過以下指令,就可以啟動:

sudo docker run -d -p 9000:9000 –name cerebro –net es_net yannart/cerebro:latest

同場加映Kibana 5.6.6的啟動指令

sudo docker run –name meso-kibana –link es1:elasticsearch –net es_net -p 5601:5601 -d kibana:5.6.6
透過A Host IP:5601啟用Kibana後,就可加入已經自定好的Index pattern來使用囉