基于Python編寫一個簡單的服務(wù)注冊發(fā)現(xiàn)服務(wù)器
我們都知道有很多的非常著名的注冊服務(wù)器,例如: Consul
、ZooKeeper
、etcd
,甚至借助于redis
完成服務(wù)注冊發(fā)現(xiàn)。但是本篇文章我們將使用python socket
寫一個非常簡單的服務(wù)注冊發(fā)現(xiàn)服務(wù)器。
本篇文章所依賴的環(huán)境為:
案例展示
項目地址:
gitee
: gitee.com/pdudo/golearn/blob/master/python/registerCenter/registerSer.py
該服務(wù)注冊服務(wù)器,沒有使用第三方庫,可以復(fù)制到本地,使用python registerSer.py
啟動即可,啟動成功后,會輸出“注冊服務(wù)器已經(jīng)啟動...”的信息。
該案例使用的協(xié)議是TCP
協(xié)議,為了防止粘包,客戶端在發(fā)送消息的時候,使用CRLF
進行區(qū)分,服務(wù)器是按照讀取到CRLF
進行分割的,服務(wù)數(shù)據(jù),均采用json
格式,共支持注冊服務(wù)、查詢服務(wù)、銷毀服務(wù)。
對應(yīng)的json
為:
注冊服務(wù):
{"type":"register","key":"redis","values":"123456@127.0.0.1:6379","timeout":"30"}
注冊和更新服務(wù)都是用的上述報文,其中type
是告訴服務(wù)器需要做什么,register
代表注冊服務(wù),key
和values
是注冊服務(wù)的名稱和注冊服務(wù)的值,最后的timeout
是存活時間,單位是秒,若超過這個時間沒有更新服務(wù),則自動銷毀該服務(wù)。
如果客戶端發(fā)送的信息沒有攜帶time
,則注冊存活時間為30s
。
查詢服務(wù):
{"type":"query","key":"redis"}
{"type":"query","key":"all"}
查詢服務(wù)使用的type
是query
,查詢的key
若是all
則是查詢?nèi)?,其他則是查詢值為key
的服務(wù)信息。
銷毀服務(wù):
{"type":"destruction","key":"redis"}
銷毀服務(wù)的type
是destruction
,key
為需要銷毀服務(wù)的名稱。
目前還暫不支持登錄驗證。
案例測試
由于該注冊服務(wù)報文采用的是使用CRLF
進行分割,所以可以直接使用telnet
進行模擬注冊,為了方便,我們還是使用python
來寫這個測試,代碼如下:
上述代碼,我們需要自己指定\r\n
進行分割,我們指定了4個注冊,以及等待5s
再進行查詢,最后再銷毀了一個服務(wù)。
運行結(jié)果如下:
可以看到上述結(jié)果,第一個輸出是我們注冊的消息,而后我們等待了5秒,目的是為了等redis
服務(wù)因為超時被自動銷毀,而后再進行查詢,就沒有redis2
的注冊信息了,接著我們銷毀了mysql1
的服務(wù),整個測試完畢。
如何來寫一個注冊發(fā)現(xiàn)服務(wù)器
我們想要寫一個注冊服務(wù)器,我們至少需要知道以下幾個知識點:
- 服務(wù)注冊發(fā)現(xiàn)核心是啥?
- 應(yīng)用層數(shù)據(jù)報文應(yīng)該如何定義。
- 如何從tcp數(shù)據(jù)流中按條獲取記錄。
服務(wù)器發(fā)現(xiàn)核心是啥
在python
中,我們可以通過定義一個字典,來存儲所有注冊信息,例如:
{ "redis": { "value": "123456@127.0.0.1:6379", "timeout":30, "lastUpdateTime": time.time() }, "redis1": {....} }
我們只需要維護好這個字典,就可以做到最基本的服務(wù)注冊和服務(wù)發(fā)現(xiàn)了。
報文如何定義
該服務(wù),我們使用的是json
來定義的數(shù)據(jù)報文,按照type
區(qū)分,分別為:
register
: 注冊或更新服務(wù)。query
: 查詢服務(wù)。destruction
銷毀服務(wù)。
在json
中,以key
和value
來確定服務(wù)名稱和服務(wù)值,還有一個timeout
來定義超時銷毀時間。
例如:
{"type":"register","key":"redis1","values":"123456@127.0.0.1:6379","timeout":"30"} {"type":"query","key":"all"} {"type":"destruction","key":"mysql1"}
如何從tcp流中獲取記錄
如何從tcp
中讀取需要的記錄呢?
這個點其實核心是如何解決tcp
粘包,我們都知道,tcp socket
也被稱之為數(shù)據(jù)流,就是因為一旦三次握手之后,數(shù)據(jù)就像水龍頭一樣源源不斷流動,我們不知道哪兒是頭,哪兒是尾巴。
所以需要指定一個結(jié)束符,告知程序,讀到哪兒,一條記錄就讀完了,這里我們每條信息都發(fā)送一個\r\n
來作為結(jié)束符。
編寫注冊發(fā)現(xiàn)服務(wù)器
如何從tcp流中讀取數(shù)據(jù)
要解決粘包,有2種方法,第一種是定義特殊字段分隔符,例如: \r\n
當讀到\r\n
的時候,就判斷一條消息結(jié)束了。
例如,如圖:
我們發(fā)送了一條記錄,hello world
如何判斷結(jié)束了呢? 在最后面加一個\r\n
,當程序讀到的時候,就判斷這條消息接收完了,就可以了。
還有一種方法是,提前計算發(fā)送的字節(jié)數(shù),然后記錄到行首,每次讀取的時候,先讀取行首的個數(shù),而后更具個數(shù)再讀取數(shù)據(jù),例如:
我們提前計算hello world
字符串占用多少個字節(jié),而后再在行首分n個字節(jié)來存儲個數(shù),例如我們使用2個字節(jié)來存儲,存儲數(shù)據(jù)為10。
我們這篇服務(wù)發(fā)現(xiàn)使用的是第一種方法。
那要如何判斷\r\n
呢?可以參考如下代碼:
data = b"" while True: data += client.recv(1) if data.endswith(b"\r\n"): break data = data[:len(data)-2]
上述代碼中,我們先定義了一個空的byte
,變量名為data
,而后每次讀取1個字節(jié),再判斷是否以\r\n
結(jié)尾,如果是以\r\n
結(jié)尾的話,就跳出循環(huán),跳出循環(huán)后,我們需要刪除掉\r\n
所以會有data = data[:len(data)-2]
這個語句,這樣的話,我們就成功拿到了記錄。
數(shù)據(jù)包如何區(qū)分類型
上個段落,我們已經(jīng)拿到了一條數(shù)據(jù)包,那么我們這個服務(wù)有3種操作,分別是 查詢、注冊、銷毀,如何客戶端發(fā)上來的請求呢? 其實如之前所述,我們可以通過type
來定義,操作流程為:
- 解析客戶端發(fā)上來的
json
信息。 - 判斷其
type
類型。
整合為語句的話,可以理解為這樣:
上述代碼,我們先使用json.loads
序列化為json
格式,而后根據(jù)type
信息區(qū)分是注冊服務(wù)、銷毀服務(wù)還是其查詢服務(wù)。這里注意的是,若json
字符串都沒有type
或者key
類型,則證明該數(shù)據(jù)包有問題,直接拒絕掉即可。如果不是上述三種服務(wù)類型的話,那也證明包有問題,直接拒絕即可。
服務(wù)注冊
服務(wù)注冊,需要獲取客戶端傳上來的key
和values
信息,若沒有的話,則證明該包有問題,而后判斷其timeout
是否攜帶,若沒有攜帶的話,則定義為默認值30s
,而后將該值入早已定義好的字典類型中即可,代碼如下:
這里,需要注意的是,我們再更新服務(wù)信息的時候,會將lastUpdateTime
更新到目前的時間戳,方便后續(xù)查詢服務(wù)的時候做惰性刪除。
服務(wù)銷毀
服務(wù)銷毀,核心點是刪除字典中的記錄即可,為了避免拋錯,還是要檢查客戶端傳上來的json
字符串是否攜帶key
,雖然在前面檢查過,這個再檢查一下,還是有必要的。
銷毀服務(wù),就直接刪除字典的該記錄就可以,如果傳入的值,在該字典中沒有記錄的話,默認為銷毀完畢。
代碼如下:
服務(wù)查詢
服務(wù)查詢,客戶端發(fā)上來的還是以key
為主,若key
的值為all
的話,則查詢所有服務(wù),若不為all
的話,則查詢單個服務(wù)注冊的信息,這里在查詢的時候,是將服務(wù)超時銷毀一起做了的,采用的是惰性刪除,當查詢到該值的是,它會比對超時時間,若已經(jīng)超時了,則直接刪除該對象,代碼如下:
上述查詢到key
后,會做一次超時檢測,若超時了,則刪除該記錄的值,若沒有超出時間,再返回信息。
總結(jié)
該篇文章,介紹了如何使用python socket
寫一個服務(wù)注冊發(fā)現(xiàn),該項目還是有很多不完美的地方,比如: 沒有對字典進行加鎖、內(nèi)存數(shù)據(jù)沒有落地等等。但是可以將它理解為一個初始的demo
。
該篇文章由于篇幅和時間關(guān)系,所以寫的更多的是思路,應(yīng)該如何如何才能完成這個項目,而沒有對詳細的語法進行梳理,比如 如何開一個多線程來服務(wù)多個客戶端等,這個后面有時間再詳細介紹。
到此這篇關(guān)于基于Python編寫一個簡單的服務(wù)注冊發(fā)現(xiàn)服務(wù)器的文章就介紹到這了,更多相關(guān)Python服務(wù)注冊發(fā)現(xiàn)服務(wù)器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用matplotlib繪制并排柱狀圖的實戰(zhàn)案例
堆積柱狀圖有堆積柱狀圖的好處,比如說我們可以很方便地看到多分類總和的趨勢,下面這篇文章主要給大家介紹了關(guān)于使用matplotlib繪制并排柱狀圖的相關(guān)資料,需要的朋友可以參考下2022-07-07Python表格數(shù)據(jù)處理庫之tablib庫詳解
這篇文章主要介紹了Python表格數(shù)據(jù)處理庫之tablib庫詳解,Tablib是一個用于處理電子表格數(shù)據(jù)的Python庫,它可以輕松地進行數(shù)據(jù)的導(dǎo)入和導(dǎo)出,以及數(shù)據(jù)格式的轉(zhuǎn)換,需要的朋友可以參考下2023-08-08Python設(shè)置在shell腳本中自動補全功能的方法
今天小編就為大家分享一篇Python設(shè)置在shell腳本中自動補全功能的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06