PHP與Golang管道通信模式

字號+ 編輯: IT男在阿里 修訂: 种花家 來源: Go语言学习网 2023-09-22 我要說兩句(1)

對phper來說很有參考價值的一篇文章

最近遇到的一個場景:php項目中需要使用一個第三方的功能(結巴分詞),而github上面恰好有一個用Golang寫好的類庫。那麽問題就來了,要如何實現不同語言之間的通信呢?

常槼的方案:

  1. 用Golang寫一個http/TCP服務,php通過http/TCP與Golang通信

  2. 將Golang經過較多封裝,做爲php擴展。

  3. PHP通過系統命令,調取Golang的可執行文档

存在的問題:

  1. http請求,網路I/O將會消耗大量時間

  2. 需要封裝大量代碼

  3. PHP每調取一次Golang程序,就需要一次初始化,時間消耗很多

優化目標:

  1. Golang程序只初始化一次(因爲初始化很耗時)

  2. 所有請求不需要走網路

  3. 盡量不大量修改代碼

解決方案:

  1. 簡單的Golang封裝,將第三方類庫編譯生成爲一個可執行文档

  2. PHP與Golang通過雙向管道通信

使用雙向管道通信優勢:

1:只需要對原有Golang類庫進行很少的封裝

2:性能最佳 (IPC通信是進程間通信的最佳途徑)

3:不需要走網路請求,節約大量時間

4:程序只需初始化一次,並一直保持在内存中

具體實現步驟:

1:類庫中的原始調取demo

package main
import (
    "fmt"
    "github.com/yanyiwu/gojieba"
    "strings"
)
func main() {
    x := gojieba.NewJieba()
    defer x.Free()
    s := "小明碩士畢業於中國科學院計算所,後在日本京都大學深造"
    words := x.CutForSearch(s, true)
    fmt.Println(strings.Join(words, "/"))
}

保存文档爲main.go,就可以運行

2:調整後代碼爲:

package main

import (
    "bufio"
    "fmt"
    "github.com/yanyiwu/gojieba"
    "io"
    "os"
    "strings"
)

func main() {

    x := gojieba.NewJieba(
        "/data/tmp/jiebaDict/jieba.dict.utf8",
        "/data/tmp/jiebaDict/hmm_model.utf8",
        "/data/tmp/jiebaDict/user.dict.utf8"
    )

    defer x.Free()

    inputReader := bufio.NewReader(os.Stdin)

    for {
        s, err := inputReader.ReadString('\n')
        if err != nil && err == io.EOF {
            break
        }

        s = strings.TrimSpace(s)

        if s != "" {
            words := x.CutForSearch(s, true)
            fmt.Println(strings.Join(words, " "))
        } else {
            fmt.Println("get empty \n")
        }
    }
}

只需要簡單的幾行調整,即可實現:從標準輸入接收字符串,經過分詞再輸出

測試:

# go build test
# ./test
# //等待用戶輸入,輸入”這是一個測試“
# 這是 一個 測試 //程序

3:使用cat與Golang通信做簡單測試

 準備一個title.txt,每行是一句文本

# cat title.txt | ./test

正常輸出,表示cat已經可以和Golang正常交互了

4:PHP與Golang通信

以上所示的cat與Golang通信,使用的是單向管道。即:只能從cat向Golang傳入數據,Golang輸出的數據並沒有傳回給cat,而是直接輸出到屏幕。但文中的需求是:php與Golang通信。即php要傳數據給Golang,同時Golang也必須把執行結果返回給php。因此,需要引入雙向管道。

在PHP中管道的使用:popen("/path/test"),具體就不展開說了,因爲此方法解決不了文中的問題。

雙向管道:

     $descriptorspec = array(
          0 => array("pipe", "r"),
            1 => array("pipe", "w")
      );
      $handle = proc_open(
          '/webroot/go/src/test/test',
          $descriptorspec,
          $pipes
      );
      fwrite($pipes['0'], "這是一個測試文本\n");
      echo fgets($pipes[1]);

解釋:使用proc_open打開一個進程,調用Golang程序。同時返回一個雙向管道pipes數組,php向$pipe['0']中寫數據,從$pipe['1']中讀數據。

好吧,也許你已經發現,我是標題档,這裡重點要講的並不只是PHP與Golang如何通信。而是在介紹一種方法: 通過雙向管道讓任意語言通信。(所有語言都會實現管道相關内容)

測試:

通過對比測試,計算出各個流程佔用的時間。下面提到的title.txt文档,包含100萬行文本,每行文本是從b2b平台取的商品標題

1: 整體流程耗時

# time cat title.txt | ./test > /dev/null

耗時:14.819秒,消耗時間包含:

  1. 進程cat讀出文本

  2. 通過管道將數據傳入Golang

  3. Golang處理數據,將結果返回到屏幕

2:計算分詞函數耗時。方案:去除分詞函數的調取,即:注釋掉Golang源代碼中的調取分詞那行的代碼

time cat title.txt | ./test > /dev/null

耗時:1.817秒時間,消耗時間包含:

  1. 進程cat讀出文本

  2. 通過管道將數據傳入Golang

  3. Golang處理數據,將結果返回到屏幕

  4. 分詞耗時 = (第一步耗時) - (以上命令所耗時)

  5. 分詞耗時 : 14.819 - 1.817 = 13.002秒


3:測試cat進程與Golang進程之間通信所佔時間

time cat title.txt > /dev/null

耗時:0.015秒,消耗時間包含:

  1. 進程cat讀出文本

  2. 通過管道將數據傳入Golang

  3. go處理數據,將結果返回到屏幕

  4. 管道通信耗時:(第二步耗時) - (第三步耗時)

  5. 管道通信耗時: 1.817 - 0.015 = 1.802秒

4:PHP與Golang通信的時間消耗

編寫簡單的php文档:

<?php
$descriptorspec = array(
    0 => array("pipe", "r"),
    1 => array("pipe", "w")
);
$handle = proc_open(
    '/webroot/go/src/test/test',
    $descriptorspec,
    $pipes
);
$fp = fopen("title.txt", "rb");
while (!feof($fp)) {
    fwrite($pipes['0'], trim(fgets($fp))."\n");
    echo fgets($pipes[1]);
}
fclose($pipes['0']);
fclose($pipes['1']);
proc_close($handle);

流程與上面基本一致,讀出title.txt内容,通過雙向管道傳入Golang進程分詞後,再返回給php (比上面的測試多一步:數據再通過管道返回)

time php popen.php > /dev/null

耗時:24.037秒,消耗時間包含:

  1. 進程PHP讀出文本

  2. 通過管道將數據傳入Golang

  3. Golang處理數據

  4. Golang將返回結果再寫入管道,PHP通過管道接收數據

  5. 將結果返回到屏幕

結論:

1:整個分詞過程中的耗時分布

使用cat控制邏輯耗時:      14.819 秒

使用PHP控制邏輯耗時:       24.037 秒(比cat多一次管道通信)

單向管道通信耗時:           1.8    秒

Golang中的分詞函數耗時: 13.002 秒

2:分詞函數的性能: 單進程,100萬商品標題分詞,耗時13秒

以上時間只包括分詞時間,不包括詞典載入時間。但在本方案中,詞典只載入一次,所以載入詞典時間可以忽略(1秒左右)

3:PHP比cat慢

語言層面慢: (24.037 - 1.8 - 14.819) / 14.819 = 50%

單進程對比測試的話,應該不會有哪個語言比cat更快。

相關問題:

1:以上Golang源碼中寫的是一個循環,也就是會一直從管道中讀數據。那麽存在一個問題:是不是php進程結束後,Golang的進程還會一直存在?

管道機制自身可解決此問題。管道提供兩個接口:讀、寫。當寫進程結束或者意外掛掉時,讀進程也會報錯,以上Golang源代碼中的err邏輯就會執行,Golang進程結束。

但如果PHP進程沒有結束,只是暫時沒有數據傳入,此時Golang進程會一直等待。直到php結束後,Golang進程才會自動結束。

2:能否多個php進程並行讀寫同一個管道,Golang進程同時爲其服務?

在同一個管道中同時寫入多個進程是不可行的,因爲管道是單向的。如果多個進程同時寫入管道,那麽Golang的返回值可能會出現混亂。

可以通過啓動多個Golang進程來實現,每個PHP進程對應一個Golang進程,以此避免多個進程同時寫入同一個管道的問題。

最後,上述解釋可能對於熟悉管道和雙向管道的人來說並沒有太大幫助。如果你對管道不太了解,在代碼跑起來時可能沒啥問題,但改一手就可能會遇到問題。哈哈,筆者建議你閲讀一本書,《UNIX網路編程》卷一和二, 花點時間把它們都看完,可能需要兩個月的時間,但非常值得!


閲完此文,您的感想如何?
  • 有用

    1

  • 沒用

    0

  • 開心

    0

  • 憤怒

    0

  • 可憐

    0

1.如文章侵犯了您的版權,請發郵件通知本站,該文章將在24小時内刪除;
2.本站標注原創的文章,轉發時煩請注明來源;
3.交流群: 2702237 13835667

相關課文
  • GO語言GORM如何更新字段

  • gorm如何創建記錄與模型定義需要注意什麽

  • gorm一般查詢與高級查詢

  • GORM時間戳跟蹤及CURD(增刪改查)

我要說說
網上賓友點評
1 樓 IP 61.149.***.101 的嘉賓 说道 : 很久前
你似乎不太了解php管道涉及到写操作的话是阻塞的, 如果你用的是swoole协程要阻塞怎么办?表面看起来有提升,但非常蹩脚,这并不是什么好方法。最好的方式还是手动编写一个swoole协程tcp连接池, 在接受请求时从连接池里取tcp句柄, 和另外一个语言(例如rust服务)本地/内网作读写逻辑,完事之后还给连接池。维护好连接池健康就很好,反复这样进行。如果实现不了,大一点的任务,建议你老老实实pclose(popen(&#039;xxxx&#039;, &#039;r&#039;)); 这种方式丢给脚本去做。