標籤: Python

在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 app_user@mesotest.database.windows.net -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;UID=app_user@mesotest.database.windows.net;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

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,有時因為成本過高,我們會測出單位的負載量後再加倍估算。

這個就另開討論吧…

[Effective Python] 情境:考慮使用產生器而非回傳串列

[Effective Python] 情境:考慮使用產生器而非回傳串列

考慮一種情境,在python中,我們常常會設計一些function來查找串列中match的資料。

例如以下的程式,我們希望找出int串列中,符合特定值的所在位置,若我們按照傳統的寫法,最簡單的就是走訪所有的項目,逐一比對後,再append到result的串列中(看你想放什麼,可以是index或對應的object)

def find_matched_number_location(number, numbers):
    result = []
    if numbers:
        for index, number_in_list in enumerate(numbers):
            if number_in_list == number:
                result.append(index+1)

    return result



result = find_matched_number_location(3, [1, 2, 4, 5, 2, 3, 12, 3, 5, 7, 1])
print(result)

對於某些輸入的樣本來說,這樣能如期的運作

[6, 8]

不過這種函式有兩個問題存在(Effective Python, P41建議)

第1個問題是,這種程式碼有點過於密集,且帶有雜訊。每次符合的條件滿足後,就會呼叫一次append。這樣的呼叫體積大,其中又用了某一行建立結果串列,再一行來return它

第2個問題是,在回傳之前,它對將所有的結果儲存在串列中才行。這對於超大型輸入,可能會使得我們程式耗盡記憶體而當掉。相交之下,這種函式的改成generator版本

能夠輕易地處理任意長度的輸入。

 

撰寫這種函式比較好的方式是使用generator(產生器)。產生器是使用了yield運算式的函式。被呼叫時,產生器函式實際上還不會執行,而是立即回傳一個iterator(迭代器)

。每次呼叫next()函式時,這個iterator會將產生器到它的下一個yield運算式。因此我們來改寫這個find_matched_number_location函式吧

結果如下:

def find_matched_number_location_new(number, numbers):
    if numbers:
        for index, number_in_list in enumerate(numbers):
            if number_in_list == number:
                yield index + 1

result_new = list(find_matched_number_location_new(3, [1, 2, 4, 5, 2, 3, 12, 3, 5, 7, 1]))
print(result_new)

帶來的好處就是減少了與結果變數互動的所有地方。呼叫generator所回傳的iterator可輕易地被轉成一個串列。

 

為了突顯第2個問題,我建立了一個3MB的檔案,裡面盡是數字用逗點隔開

在此我定義了一個generator,它會從檔案接受逐行的串流輸入(不過此例只有一行),一次只產出一個比對數字的輸出。這個函式工作時的最大記憶體量,僅會是單一行輸入的最大長度

def find_matched_number_location_from_file(number, handle):
    offset = 0
    for line in handle:
        if line:
            for number_in_file in line.split(','):
                offset += 1
                if int(number_in_file) == number:
                    yield offset

執行2段的測試

start = time.time()
with open('新增資料夾/test_data.txt', 'r') as f:
    it = find_matched_number_location_from_file(3, f)
    result = list(it)
    print(len(result))
end = time.time()
print( '花費 %.3f 秒' % (end - start) )

start = time.time()
with open('新增資料夾/test_data.txt', 'r') as f:
    result = []
    for line in f:
        for index , number_in_file in enumerate(line.split( ',' )):
            if int(number_in_file) == 3:
                result.append(index + 1)
    print(len(result))
end = time.time()
print('花費 %.3f 秒' % (end - start))

執行結果如下:

307824
花費 0.551
307824
花費 0.730 秒

其實對於我在測試資料僅僅只有3MB的檔案之下,其實秒數落差不大,但是效能上還是有所區別,generator的方法比舊的比對方法快了約25%。而佔的記憶體量,舊的比對方法(用result來記錄)則會多用了了至少307824個數字所站的位元。這邊數字或許不大,但試想若是大型的資料的情況呢?就交由使用場景來決定吧。

最後,再補充一點,定義像這樣的產生器時,唯一要注意的部份就是呼叫者必須知道期回傳的iterator是有狀態的,不能被重複使用。

下篇來分享防備的做法

 

Reference:Effective Python中文版一書,做法16,改為自己的理解與實作

[CODE WAR記錄] 將Linked-List分成Front,Back2半的Linked-List(難度5)

[CODE WAR記錄] 將Linked-List分成Front,Back2半的Linked-List(難度5)

最近有點偷懶,沒有研究新的東西,blog鬧水荒,但是確實對於python的語法使用上還深深感到不足,因此還是來”高手村”練練功好了..,直接把codewar的練習結果與心得當作一篇好了XD

題目示例如下:

var source = 1 -> 3 -> 7 -> 8 -> 11 -> 12 -> 14 -> null
var front = new Node()
var back = new Node()
frontBackSplit(source, front, back)
front === 1 -> 3 -> 7 -> 8 -> null
back === 11 -> 12 -> 14 -> null

請建立frontBackSplit的程式碼

Read More Read More

[Code War記錄] 給定2參數:每個數字位數的總合與數字位數長度,找出區間內所有符合連續位數(由小到大)的數字組合

[Code War記錄] 給定2參數:每個數字位數的總合與數字位數長度,找出區間內所有符合連續位數(由小到大)的數字組合

之前為了訓練python的語感,去了codewar找題目來練練功,發現了這一題滿有趣的

1.找到所有的數字組合,其每位數的數字加總必須滿足給定的條件值
2.這些數字組合,必須是由小到大連續性的排列組合(例如, 118, 127, 136, 145, 226, 235, 244, 334)

請建立一Function,給定2個參數, x為位數加總的總合,y為預期位數長度,回傳set為3個值,a、b、c,a為滿足的個數,b為滿足的數字中,最小值,c為滿足條件的數字中,最大值

find_all(x, y)

舉例來說:

find_all(10, 3) == [8, 118, 334]
 find_all(27, 3) == [1, 999, 999]

Read More Read More

[平行處理] 考慮使用concurrent.futures模組來達成真正的平行處理

[平行處理] 考慮使用concurrent.futures模組來達成真正的平行處理

[原文擷自Effective Python中文版]結合自己的實作與理解

對於擁有越來越多CPU核心的現代電腦而言,我們可以合理的假設,改善程式碼效能其中一個解決方案就是平行處理(Parallelism)

然而,Python的GIL讓執行緒無法達到真正的平行處理,所以這個選項出局了。另一個常見的建議是將程式碼中效能最關鍵的地方以C語言改寫為一個擴充功能模組(extension module)

C讓我們更靠近底層機器,執行的比Python還要快,消除了平行處理的必要。

Read More Read More