濮阳杆衣贸易有限公司

主頁(yè) > 知識(shí)庫(kù) > 詳解PHP調(diào)用Go服務(wù)的正確方式

詳解PHP調(diào)用Go服務(wù)的正確方式

熱門標(biāo)簽:百度商鋪地圖標(biāo)注 福州人工外呼系統(tǒng)哪家強(qiáng) 安裝電銷外呼系統(tǒng) 衡水外呼系統(tǒng)平臺(tái) 地圖標(biāo)注平臺(tái)怎么給錢注冊(cè) 注冊(cè)400電話申請(qǐng) 常州地圖標(biāo)注服務(wù)商 釘釘打卡地圖標(biāo)注 新河科技智能外呼系統(tǒng)怎么樣

問(wèn)題

服務(wù)耦合

我們?cè)陂_發(fā)過(guò)程中可能會(huì)遇到這樣的情況:

  • 進(jìn)程依賴于某服務(wù),所以把服務(wù)耦合在進(jìn)程代碼中;
  • 服務(wù)初始化耗時(shí)長(zhǎng),拖慢了進(jìn)程啟動(dòng)時(shí)間;
  • 服務(wù)運(yùn)行要占用大量?jī)?nèi)存,多進(jìn)程時(shí)內(nèi)存損耗嚴(yán)重。

文本匹配服務(wù),它是消息處理流程中的一環(huán),被多個(gè)消息處理進(jìn)程依賴,每次初始化進(jìn)程要 6秒 左右時(shí)間構(gòu)造 Trie 樹,而且服務(wù)讀取關(guān)鍵詞大文件、使用樹組構(gòu)造 Trie 樹,會(huì)占用大量(目前設(shè)置為 256M )內(nèi)存。

我已經(jīng)把進(jìn)程寫成了守護(hù)進(jìn)程的形式,讓它們長(zhǎng)時(shí)間執(zhí)行,雖然不用更多地考慮初始化時(shí)間了,但占用內(nèi)存量巨大的問(wèn)題沒(méi)有辦法。如果關(guān)鍵詞量再大一些,一臺(tái)機(jī)器上面跑十來(lái)個(gè)消息處理進(jìn)程后就干不了其他了。

而且,如果有需求讓我把文本匹配服務(wù)封裝為接口給外部調(diào)用呢?我們知道,web 服務(wù)時(shí),每一個(gè)請(qǐng)求處理進(jìn)程的生存周期是從受理請(qǐng)求到響應(yīng)結(jié)束,如果每次請(qǐng)求都用大量?jī)?nèi)存和時(shí)間來(lái)初始化服務(wù),那接口響應(yīng)時(shí)間和服務(wù)器壓力可想而知。

服務(wù)抽取

這樣,服務(wù)形式必須要改變,我們希望這個(gè)文本匹配這個(gè)服務(wù)能做到:

  • 隨調(diào)隨走,不依賴,不再與“消息處理服務(wù)”耦合在一起;
  • 一次初始化,進(jìn)程運(yùn)行期間持續(xù)提供服務(wù);
  • 同步響應(yīng),高效而準(zhǔn)確,最好能不用各種鎖來(lái)保持資源占有;

解決辦法也很簡(jiǎn)單,就是把這個(gè)文本匹配的服務(wù)抽取出來(lái),單獨(dú)作為一個(gè)守護(hù)進(jìn)程來(lái)運(yùn)行,像一個(gè)特殊的服務(wù)器,多個(gè)“消息處理服務(wù)”在有需要時(shí)能調(diào)用此服務(wù)進(jìn)程。

現(xiàn)在,我們需要考慮文本匹配服務(wù)進(jìn)程如何與外界通信,接受匹配請(qǐng)求,響應(yīng)匹配結(jié)果。繞來(lái)繞去,問(wèn)題還是回到了 進(jìn)程間通信。

Unix Domain Sockets

進(jìn)程間通信

進(jìn)程間通信(IPC,Inter-Process Communication),指至少兩個(gè)進(jìn)程或線程間傳送數(shù)據(jù)或信號(hào)的一些技術(shù)或方法。進(jìn)程是計(jì)算機(jī)系統(tǒng)分配資源的最小單位(嚴(yán)格說(shuō)來(lái)是線程)。每個(gè)進(jìn)程都有自己的一部分獨(dú)立的系統(tǒng)資源,彼此是隔離的。為了能使不同的進(jìn)程互相訪問(wèn)資源并進(jìn)行協(xié)調(diào)工作,才有了進(jìn)程間通信。

進(jìn)程間通信的方式有很多,網(wǎng)上對(duì)此介紹的也很多,下面根據(jù)文章的需求來(lái)分析一下這些方式:

  • 管道:管道是Unix最初的IPC形式,但它只能用于具有共同祖先進(jìn)程的各個(gè)進(jìn)程,無(wú)法用于在沒(méi)有親緣關(guān)系的進(jìn)程。如果使用它,需要在“消息處理服務(wù)”中啟動(dòng)“文本匹配服務(wù)”,跟原來(lái)差別不大。
  • 命名管道:也被稱為有名管道,它在Unix稱為FIFO,它通過(guò)一個(gè)文件來(lái)進(jìn)行進(jìn)程間數(shù)據(jù)交互,但服務(wù)于多個(gè)進(jìn)程時(shí),需要添加鎖來(lái)保證原子性,從而避免寫入和讀取不對(duì)應(yīng)。
  • 信號(hào)和信號(hào)量:用于進(jìn)程/線程事件級(jí)的通信,但它們能交流的信息太少。
  • 消息隊(duì)列和共享內(nèi)存:都是通過(guò)一個(gè)公共內(nèi)存介質(zhì)來(lái)進(jìn)行通信
  • socket:通過(guò)Unix封裝好的網(wǎng)絡(luò)API來(lái)進(jìn)行通信,像數(shù)據(jù)庫(kù)、服務(wù)器都是通過(guò)這種方式實(shí)現(xiàn),它們也能提供本地服務(wù)。不過(guò)網(wǎng)絡(luò)socket固然能使用,但是要面臨著數(shù)據(jù)包裝和網(wǎng)絡(luò)調(diào)用開銷,也不是完美的選擇。

簡(jiǎn)單介紹

當(dāng)然還是有完美的方式的,這就是今天的主角 - Unix Domain Sockets ,它可以理解為一種特殊的 Socket,但它不需要經(jīng)過(guò)網(wǎng)絡(luò)協(xié)議棧,不需要打包拆包、計(jì)算校驗(yàn)和、維護(hù)序號(hào)和應(yīng)答等,只是將應(yīng)用層數(shù)據(jù)從一個(gè)進(jìn)程拷貝到另一個(gè)進(jìn)程,所以在系統(tǒng)內(nèi)通信效率更高。而且免去了網(wǎng)絡(luò)問(wèn)題,它也更能保證消息的完整性,既不會(huì)丟失也不會(huì)順序錯(cuò)亂。

作為特殊的 Socket,它的創(chuàng)建、調(diào)用方式和網(wǎng)絡(luò) Socket 一樣,一次完整的交互,服務(wù)端都要經(jīng)過(guò)create、bind、listen、accept、read、write,客戶端要通過(guò)create、connect、write、read。與普通 Socket 不同的是它綁定一個(gè)系統(tǒng)內(nèi)的文件,而不是 IP 和端口。

適用場(chǎng)景

Unix Domain Sockets 真的是進(jìn)程間通信的一個(gè)重型武器,用它可以快速實(shí)現(xiàn)進(jìn)程間的數(shù)據(jù)、信息交互,而且不需要鎖等繁雜操作,也不用考慮效率,可謂是簡(jiǎn)單高效。

當(dāng)然,“重型武器” 的在各種場(chǎng)景下也有適合不適合。Unix Domain Sockets適用于以下場(chǎng)景:

  • 服務(wù)長(zhǎng)時(shí)間存在。 Unix Domain Sockets 的服務(wù)端是個(gè)服務(wù)器一樣的存在,在守護(hù)進(jìn)程中,它阻塞并等待客戶端連接的特性可以被充分利用。
  • 一服務(wù)器多客戶端。它能通過(guò) Socket 的文件描述符來(lái)區(qū)分不同的客戶端,避免資源之間的鎖操作。
  • 同一系統(tǒng)內(nèi)。它只能在同一系統(tǒng)內(nèi)進(jìn)行進(jìn)程數(shù)據(jù)復(fù)制,跨系統(tǒng)請(qǐng)使用傳統(tǒng) Sockets。

代碼實(shí)現(xiàn)

接下來(lái)要 show code 了,不過(guò)學(xué) PHP 的都知道,PHP 不太適合處理 CPU 密集形的任務(wù),我剛好學(xué)了點(diǎn) Go,一時(shí)手癢,就用 Go 實(shí)現(xiàn)了下 Trie 樹,所以才牽扯到 PHP 和 Go 之間的通信,有了今天的文章。當(dāng)然介紹的方法,并不只適合 PHP 與 Go 通信,其他語(yǔ)言也可以,至少 C系語(yǔ)言中是通用的。

Go 實(shí)現(xiàn)的 Trie 樹

Trie樹不再是今天的主題,這里介紹一下數(shù)據(jù)結(jié)構(gòu)和需要注意的點(diǎn)。

// trie樹結(jié)點(diǎn)定義
type Node struct {
    depth    int
    children map[int32]Node // 用map實(shí)現(xiàn)key-value型的 字符-節(jié)點(diǎn) 對(duì)應(yīng)
}

需要注意:

  • 使用 slice 的 append() 函數(shù)保存遞增的匹配結(jié)果時(shí),有可能由于 slice 容量不夠而重新分配地址,所以要傳入 slice 的地址來(lái)保存遞增后的匹配結(jié)果結(jié)果,*result = append(*result, word),最后再將遞增之后的 slice 地址傳回。
  • 由于 Go 中的編碼統(tǒng)一使用的 utf-8,不用像 PHP 一樣判斷字符的邊界,所以在進(jìn)行關(guān)鍵詞拆散和消息拆散時(shí),直接使用 int32() 方法將關(guān)鍵詞和消息都轉(zhuǎn)換為成員為 int32 類型的 slice,匹配過(guò)程中就使用 int32 類型的數(shù)字來(lái)代表這個(gè)中文字符,匹配完成后再使用fmt.Printf("%c", int32)將其轉(zhuǎn)換為中文。

Go Server

Go 中創(chuàng)建一個(gè) socket 并使用的步驟非常簡(jiǎn)單,只是 Go 沒(méi)有異常,判斷 error 會(huì)比較惡心一點(diǎn),不知道有沒(méi)有大神有更好的寫法。下面為了精簡(jiǎn),把 error 全置空了。

 // 創(chuàng)建一個(gè)Unix domain soceket
    socket, _ := net.Listen("unix", "/tmp/keyword_match.sock")
    // 關(guān)閉時(shí)刪除綁定的文件
    defer syscall.Unlink("/tmp/keyword_match.sock") 
    // 無(wú)限循環(huán)監(jiān)聽和受理客戶端請(qǐng)求
    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("[]") // 給響應(yīng)一個(gè)默認(rèn)值
        if len(matched) > 0 {
            json_str, _ := json.Marshal(matched)
            response = []byte(string(json_str))
        }
        _, _ = client.Write(response)
    }

PHP Client

下面是 PHP 實(shí)現(xiàn)的客戶端:

$msg = "msg";
// 創(chuàng)建 連接 發(fā)送消息 接收響應(yīng) 關(guān)閉連接
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
socket_connect($socket, '/tmp/keyword_match.sock');
socket_send($socket, $msg, strlen($msg), 0);
$response = socket_read($socket, 1024);
socket_close($socket);

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

小結(jié)

效率

這里總結(jié)一下這套設(shè)計(jì)的效率表現(xiàn):

純粹用 Go 進(jìn)行文本關(guān)鍵詞匹配,一千條數(shù)據(jù)運(yùn)行一秒多,差不多是 PHP 效率的兩倍。不過(guò)說(shuō)好的 8倍效率呢?果然測(cè)評(píng)都是騙人的。當(dāng)然,也可能是我寫法有問(wèn)題或者 Trie 樹不在 Go 的發(fā)揮范圍之內(nèi)。然后是 PHP 使用 Unix Domain Socket 調(diào)用 Go 服務(wù)的耗時(shí),可能是進(jìn)程間復(fù)制數(shù)據(jù)耗時(shí)或 PHP 拖了后腿,3秒多一點(diǎn),跟純 PHP 腳本差不多。

雜談

用 PHP 的都知道,PHP 因?yàn)榻忉屝驼Z(yǔ)言的特性和其高度的封裝,導(dǎo)致其雖然在開發(fā)上速度很快,可是執(zhí)行與其他語(yǔ)言相比略差。對(duì)此,業(yè)界的 FB 有 HHVM,PHP7 有 opcache 新特性,據(jù)說(shuō)還要在 PHP8 添加 JIT,用以彌補(bǔ)其先天硬傷。

不過(guò),對(duì)于開發(fā)者,特別是跟我一樣對(duì)于效率有執(zhí)著追求的人來(lái)說(shuō),在了解使用 PHP 的新特性之外,自己再掌握一門較高執(zhí)行效率、開發(fā)效率略低的語(yǔ)言,用來(lái)寫一些高計(jì)算量,邏輯單一的代碼,與 PHP 互補(bǔ)或許會(huì)更好一點(diǎn)。

于是,在考慮良久,也見識(shí)了各種 Go 的支持者和反對(duì)者之間的撕逼后,我覺(jué)得還是要相信一下谷歌爸爸,畢竟也沒(méi)什么其他我覺(jué)得可選的語(yǔ)言了。

另外C呢,雖然暫時(shí)開發(fā)中用不到,可是畢竟是當(dāng)代N多語(yǔ)言的起源,偶爾寫寫數(shù)據(jù)結(jié)構(gòu)、算法什么的以免生銹。而且學(xué)了些C,從 PHP 到 Go,切換起來(lái)還略有些得心應(yīng)手的感覺(jué)~

以上就是詳解PHP調(diào)用Go服務(wù)的正確方式的詳細(xì)內(nèi)容,更多關(guān)于PHP調(diào)用Go服務(wù)的正確方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

您可能感興趣的文章:
  • goto語(yǔ)法在PHP中的使用教程
  • 基于Go和PHP語(yǔ)言實(shí)現(xiàn)爬樓梯算法的思路詳解
  • ThinkPHP5 框架引入 Go AOP,PHP AOP編程項(xiàng)目詳解
  • golang、python、php、c++、c、java、Nodejs性能對(duì)比
  • Linux安裝PHP MongoDB驅(qū)動(dòng)
  • PHP中安裝使用mongodb數(shù)據(jù)庫(kù)
  • 詳解Go語(yǔ)言微服務(wù)開發(fā)框架之Go chassis
  • 詳解Go與PHP的語(yǔ)法對(duì)比

標(biāo)簽:鶴崗 白城 遼陽(yáng) 柳州 唐山 克拉瑪依 六安 鷹潭

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《詳解PHP調(diào)用Go服務(wù)的正確方式》,本文關(guān)鍵詞  詳解,PHP,調(diào)用,服務(wù),的,正確,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《詳解PHP調(diào)用Go服務(wù)的正確方式》相關(guān)的同類信息!
  • 本頁(yè)收集關(guān)于詳解PHP調(diào)用Go服務(wù)的正確方式的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    景德镇市| 陵川县| 英吉沙县| 宜都市| 时尚| 嘉兴市| 梅河口市| 东山县| 济阳县| 定南县| 太保市| 扬中市| 神木县| 从江县| 芜湖市| 金秀| 文登市| 玉树县| 通城县| 灵石县| 栖霞市| 岳普湖县| 肃南| 盘锦市| 堆龙德庆县| 武穴市| 慈利县| 凭祥市| 钟山县| 应城市| 永州市| 仁布县| 扶沟县| 南靖县| 商都县| 鄂温| 盐亭县| 潮州市| 北安市| 德州市| 施甸县|