1. 程式人生 > 程式設計 >詳解PHP呼叫Go服務的正確方式

詳解PHP呼叫Go服務的正確方式

問題

服務耦合

我們在開發過程中可能會遇到這樣的情況:

  • 程序依賴於某服務,所以把服務耦合在程序程式碼中;
  • 服務初始化耗時長,拖慢了程序啟動時間;
  • 服務執行要佔用大量記憶體,多程序時記憶體損耗嚴重。

文字匹配服務,它是訊息處理流程中的一環,被多個訊息處理程序依賴,每次初始化程序要 6秒 左右時間構造 Trie 樹,而且服務讀取關鍵詞大檔案、使用樹組構造 Trie 樹,會佔用大量(目前設定為 256M )記憶體。

我已經把程序寫成了守護程序的形式,讓它們長時間執行,雖然不用更多地考慮初始化時間了,但佔用記憶體量巨大的問題沒有辦法。如果關鍵詞量再大一些,一臺機器上面跑十來個訊息處理程序後就幹不了其他了。

而且,如果有需求讓我把文字匹配服務封裝為介面給外部呼叫呢?我們知道,web 服務時,每一個請求處理程序的生存週期是從受理請求到響應結束,如果每次請求都用大量記憶體和時間來初始化服務,那介面響應時間和伺服器壓力可想而知。

服務抽取

這樣,服務形式必須要改變,我們希望這個文字匹配這個服務能做到:

  • 隨調隨走,不依賴,不再與“訊息處理服務”耦合在一起;
  • 一次初始化,程序執行期間持續提供服務;
  • 同步響應,高效而準確,最好能不用各種鎖來保持資源佔有;

解決辦法也很簡單,就是把這個文字匹配的服務抽取出來,單獨作為一個守護程序來執行,像一個特殊的伺服器,多個“訊息處理服務”在有需要時能呼叫此服務程序。

現在,我們需要考慮文字匹配服務程序如何與外界通訊,接受匹配請求,響應匹配結果。繞來繞去,問題還是回到了 程序間通訊。

Unix Domain Sockets

程序間通訊

程序間通訊(IPC,Inter-Process Communication),指至少兩個程序或執行緒間傳送資料或訊號的一些技術或方法。程序是計算機系統分配資源的最小單位(嚴格說來是執行緒)。每個程序都有自己的一部分獨立的系統資源,彼此是隔離的。為了能使不同的程序互相訪問資源並進行協調工作,才有了程序間通訊。

程序間通訊的方式有很多,網上對此介紹的也很多,下面根據文章的需求來分析一下這些方式:

  • 管道:管道是Unix最初的IPC形式,但它只能用於具有共http://www.cppcns.com
    同祖先程序的各個程序,無法用於在沒有親緣關係的程序。如果使用它,需要在“訊息處理服務”中啟動“文字匹配服務”,跟原來差別不大。
  • 命名管道:也被稱為有名管道,它在Unix稱為FIFO,它通過一個檔案來進行程序間資料互動,但服務於多個程序時,需要新增鎖來保證原子性,從而避免寫入和讀取不對應。
  • 訊號和訊號量:用於程序/執行緒事件級的通訊,但它們能交流的資訊太少。
  • 訊息佇列和共享記憶體:都是通過一個公共記憶體介質來進行通訊
  • socket:通過Unix封裝好的網路API來進行通訊,像資料庫、伺服器都是通過這種方式實現,它們也能提供本地服務。不過網路socket固然能使用,但是要面臨著資料包裝和網路呼叫開銷,也不是完美的選擇。

簡單介紹

當然還是有完美的方式的,這就是今天的主角 - Unix Domain Sockets ,它可以理解為一種特殊的 Socket,但它不需要經過網路協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層資料從一個程序拷貝到另一個程序,所以在系統內通訊效率更高。而且免去了網路問題,它也更能保證訊息的完整性,既不會丟失也不會順序錯亂。

作為特殊的 Socket,它的建立、呼叫方式和網路 Socket 一樣,一次完整的互動,服務端都要經過create、bind、listen、accept、read、write,客戶端要通過create、connect、write、read。與普通 Socket 不同的是它繫結一個系統內的檔案,而不是 IP 和埠。

適用場景

Unix Domain Sockets 真的是程序間通訊的一個重型武器,用它可以快速實現程序間的資料、資訊互動,而且不需要鎖等繁雜操作,也不用考慮效率,可謂是簡單高效。

當然,“重型武器” 的在各種場景下也有適合不適合。Unix Domain Sockets適用於以下場景:

  • 服務長時間存在。 Unix Domain Sockets 的服務端是個伺服器一樣的存在,在守護程序中,它阻塞並等待客戶端連線的特性可以被充分利用。
  • 一伺服器多客戶端。它能通過 Socket 的檔案描述符來區分不同的客戶端,避免資源之間的鎖操作。
  • 同一系統內。它只能在同一系統內進行程序資料複製,跨系統請使用傳統 Sockets。

程式碼實現

接下來要 show code 了,不過學 php 的都知道,PHP 不太適合處理 CPU 密集形的任務,我剛好學了點 Go,一時手癢,就用 Go 實現了下 Trie 樹,所以才牽扯到 PHP 和 Go 之間的通訊,有了今天的文章。當然介紹的方法,並不只適合 PHP 與 Go 通訊,其他語言也可以,至少 C系語言中是通用的。

Go 實現的 Trie 樹

Trie樹不再是今天的主題,這裡介紹一下資料結構和需要注意的點。

// trie樹結點定義
type Node struct {
    depth    int
    children map[int32]Node // 用map實現key-value型的 字元-節點 對應
}

需要注意:

  • 使用 slice 的 append() 函式儲存遞增的匹配結果時,有可能由於 slice 容量不夠而重新分配地址,所以要傳入 slice 的地址來儲存遞增後的匹配結果結果,*result = append(*result,word),最後再將遞增之後的 slice 地址傳回。
  • 由於 Go 中的編碼統一使用的 utf-8,不用像 PHP 一樣判斷字元的邊界,所以在進行關鍵詞拆散和訊息拆散時,直接使用 int32() 方法將關鍵詞和訊息都轉換為成員為 int32 型別的 slice,匹配過程中就使用 int32 型別的數字來代表這個中文字元,匹配完成後再使用fmt.Printf("%c",int32)將其轉換為中文。

Go Server

Go 中建立一個 socket 並使用的步驟非常簡單,只是 Go 沒有異常,判tPFvuVDSBa斷 error 會比較噁心一點,不知道有沒有大神有更好的寫法。下面為了精簡,把 error 全置空了。

 // 建立一個Unix domain soceket
    socket,_ := net.Listen("unix","/tmp/keyword_match.sock")
    // 關閉時刪除繫結的檔案
    defer syscall.Unlink("/tmp/keyword_match.sock") 
    // 無限迴圈監聽和受理客戶端請程式設計客棧求
    for {
        client,_ := socket.Accept()
        
        buf := make([]byte,1024)
        data_len,_ := client.Read(buf)
        data := buf[0:data_len]
        msg := string(data)
        
        matched := trie.Match(tree,msg)
        response := []byte("[]") // 給響應一個預設值
        if len(matched) > 0 {
            json_str,_ := json.Marshal(matched)
            response = []byte(string(json_str))
        }
        _,_ = client.Write(response)
    }

PHP Client

下面是 PHP 實現的客戶端:

$msg = "msg";
// 建立 連線 傳送訊息 接收響應 關閉連線
$socket = socket_create(AF_UNIX,SOCK_STREAM,0);
socket_connect($socket,'/tmp/keyword_m程式設計客棧atch.sock');
socket_send($socket,$msg,strlen($msg),0);
$response = socket_read($socket,1024);
socket_close($socket);

// 有值則為匹配成功
if (strlen($response) > 3) {
    var_dump($response);
}

小結

效率

這裡總結一下這套設計的效率表現:

純粹用 Go 進行文字關鍵詞匹配,一千條資料執行一秒多,差不多是 PHP 效率的兩倍。不過說好的 8倍效率呢?果然測評都是騙人的。當然,也可能是我寫法有問題或者 Trie 樹不在 Go 的發揮範圍之內。然後是 PHP 使用 Unix Domain Socket 呼叫 Gohttp://www.cppcns.com 服務的耗時,可能是程序間複製資料耗時或 PHP 拖了後腿,3秒多一點,跟純 PHP 指令碼差不多。

雜談

用 PHP 的都知道,PHP 因為解釋型語言的特性和其高度的封裝,導致其雖然在開發上速度很快,可是執行與其他語言相比略差。對此,業界的 FB 有 HHVM,PHP7 有 opcache 新特性,據說還要在 PHP8 新增 JIT,用以彌補其先天硬傷。

不過,對於開發者,特別是跟我一樣對於效率有執著追求的人來說,在瞭解使用 PHP 的新特性之外,自己再掌握一門較高執行效率、開發效率略低的語言,用來寫一些高計算量,邏輯單一的程式碼,與 PHP 互補或許會更好一點。

於是,在考慮良久,也見識了各種 Go 的支持者和反對者之間的撕逼後,我覺得還是要相信一下谷歌爸爸,畢竟也沒什麼其他我覺得可選的語言了。

另外C呢,雖然暫時開發中用不到,可是畢竟是當代N多語言的起源,偶爾寫寫資料結構、演算法什麼的以免生鏽。而且學了些C,從 PHP 到 Go,切換起來還略有些得心應手的感覺~

以上就是詳解PHP呼叫Go服務的正確方式的詳細內容,更多關於PHP呼叫Go服務的正確方式的資料請關注我們其它相關文章!