譯注:
并發(fā)與并行:并發(fā)是虛擬的并行,比如通過時(shí)間切片技術(shù)在單核CPU上運(yùn)行多個(gè)任務(wù),讓每個(gè)使用者“以為”自己在獨(dú)占這一CPU資源;并行是實(shí)際的同一時(shí)間多任務(wù)同時(shí)運(yùn)行,多數(shù)是指在多核CPU的場(chǎng)景下。
隊(duì)列與雙端隊(duì)列:隊(duì)列遵循先入先出的原則,從一端存數(shù),從另一端取數(shù),雙端隊(duì)列支持從隊(duì)列的兩端存數(shù)和取數(shù)。
阻塞和非阻塞:阻塞和非阻塞描述了程序等待返回結(jié)果時(shí)的狀態(tài),阻塞代表不返回結(jié)果就掛起,不進(jìn)行任何操作;非阻塞是在沒返回結(jié)果時(shí)可以執(zhí)行其他任務(wù)。
合作和搶占:高優(yōu)先級(jí)任務(wù)可以打斷其他正在運(yùn)行的低優(yōu)先級(jí)任務(wù),則調(diào)度器是搶占式的;反之,則是合作式的。
服務(wù)端編程的陣營(yíng)中有很多新面孔,一水兒的谷歌血統(tǒng)。在谷歌開始將Golang應(yīng)用于其產(chǎn)品系統(tǒng)后,Golang快速的吸引了大量的關(guān)注。隨著微服務(wù)架構(gòu)的興起,人們開始關(guān)注一些現(xiàn)代的數(shù)據(jù)通信解決方案,如gRPC和Protobuf。在本文中,我們會(huì)對(duì)以上這些概念作一些簡(jiǎn)要的介紹。
一、Golang
Golang又稱Go語(yǔ)言,是一個(gè)開源的、多用途的編程語(yǔ)言,由Google研發(fā),并由于種種原因,正在日益流行。Golang已經(jīng)有10年的歷史,并且據(jù)Google稱已經(jīng)在生產(chǎn)環(huán)境中使用了接近7年的時(shí)間,這一點(diǎn)可能讓大多數(shù)人大跌眼鏡。
Golang的設(shè)計(jì)理念是,簡(jiǎn)單、現(xiàn)代、易于理解和快速上手。Golang的創(chuàng)造者將Golang設(shè)計(jì)為一個(gè)普通的程序員可以在一個(gè)周末的時(shí)間就可以掌握,并達(dá)到使用Golang進(jìn)行工作的程度。這一點(diǎn)我已經(jīng)親身證實(shí)。Golang的創(chuàng)造者,都是C語(yǔ)言原始草案的專家組成員,可以說(shuō),Golang根紅苗正,值得信賴。
理都懂,但話說(shuō)回來(lái),為什么我們需要另一門編程語(yǔ)言呢
多數(shù)場(chǎng)景下,確實(shí)并不需要。事實(shí)上,Go語(yǔ)言并不能解決其他語(yǔ)言或工具無(wú)法解決的新問題。但在一些強(qiáng)調(diào)效率、優(yōu)雅與直觀的場(chǎng)景下,人們通常會(huì)面臨一系列的相關(guān)問題,這正是Go所致力于解決的領(lǐng)域。Go的主要特點(diǎn)是:
一流的并發(fā)支持
內(nèi)核十分簡(jiǎn)單,語(yǔ)言優(yōu)雅、現(xiàn)代
高性能
提供現(xiàn)代軟件開發(fā)所需要的原生工具支持
我將簡(jiǎn)要介紹Go是如何提供上述的支持的。在Go語(yǔ)言的官網(wǎng)可以了解更多的特性和細(xì)節(jié)。
一流的并發(fā)支持
并發(fā)是多數(shù)服務(wù)端應(yīng)用所需要考慮的主要問題之一,考慮到現(xiàn)代微處理器的特性,并發(fā)也成為編程語(yǔ)言的主要關(guān)切之一。Go語(yǔ)言引入了“goroutine”的理念。可以把“goroutine”理解為一個(gè)“輕量級(jí)的用戶空間線程”(現(xiàn)實(shí)中,當(dāng)然遠(yuǎn)比這要復(fù)雜得多,同一線程可能會(huì)附著多路的goroutine,但這樣的提法可以讓你有一個(gè)大致的概念)。所謂“輕量級(jí)”,可以這樣理解,由于采用了十分袖珍的堆棧,你可以同時(shí)啟動(dòng)數(shù)以百萬(wàn)計(jì)的goroutine,事實(shí)上這也是Go語(yǔ)言官方所推薦的方式。在Go語(yǔ)言中,任何函數(shù)或方法都可以生成一個(gè)goroutine。比如,只需要運(yùn)行“go myAsyncTask()”就可以從“myAsyncTask”函數(shù)生成一個(gè)goroutine。示例代碼如下:
// This function performs the given task concurrently by spawing a goroutine
// for each of those tasks.
func performAsyncTasks(task []Task) {
for _, task := range tasks {
// This will spawn a separate goroutine to carry out this task.
// This call is non-blocking
go task.Execute()
}
}
goroutineExample.go hosted with ? by GitHub
(左右滑動(dòng)查看全部代碼)
怎么樣,是不是很簡(jiǎn)單?Go是一門簡(jiǎn)單的語(yǔ)言,因此注定是以這樣的方式來(lái)解決問題。你可以為每個(gè)獨(dú)立的異步任務(wù)生成一個(gè)goroutine而不需要顧慮太多事情。如果處理器支持多核運(yùn)行,Go語(yǔ)言運(yùn)行時(shí)會(huì)自動(dòng)的以并行的方式運(yùn)行所有的goroutine。那么,goroutine之間是如何通信的呢,答案是channel。
“channel”也是Go語(yǔ)言的一個(gè)概念,用于進(jìn)行g(shù)oroutine之間的通信。通過channel,你可以向另一個(gè)goroutine傳遞各種信息(比如Go語(yǔ)言概念里的type或者struct甚至是channel)。一個(gè)channel大體上是一個(gè)“雙端阻塞隊(duì)列”(也可以單端的)。如果需要goroutine基于特定條件觸發(fā)下一步的行動(dòng),也可以利用channel來(lái)實(shí)現(xiàn)goroutine的協(xié)作阻塞任務(wù)模式。
在編寫異步或者并發(fā)的代碼時(shí),goroutine和channel這兩個(gè)概念賦予了編程者大量的靈活性和簡(jiǎn)便性。可以籍此很容易的建立其他很有用的庫(kù),比如goroutine pool,舉個(gè)簡(jiǎn)單的例子:
package executor
import (
"log"
"sync/atomic"
)
// The Executor struct is the main executor for tasks.
// 'maxWorkers' represents the maximum number of simultaneous goroutines.
// 'ActiveWorkers' tells the number of active goroutines spawned by the Executor at given time.
// 'Tasks' is the channel on which the Executor receives the tasks.
// 'Reports' is channel on which the Executor publishes the every tasks reports.
// 'signals' is channel that can be used to control the executor. Right now, only the termination
// signal is supported which is essentially is sending '1' on this channel by the client.
type Executor struct {
maxWorkers int64
ActiveWorkers int64
Tasks chan Task
Reports chan Report
signals chan int
}
// NewExecutor creates a new Executor.
// 'maxWorkers' tells the maximum number of simultaneous goroutines.
// 'signals' channel can be used to control the Executor.
func NewExecutor(maxWorkers int, signals chan int) *Executor {
chanSize := 1000
if maxWorkers > chanSize {
chanSize = maxWorkers
}
executor := Executor{
maxWorkers: int64(maxWorkers),
Tasks: make(chan Task, chanSize),
Reports: make(chan Report, chanSize),
signals: signals,
}
go executor.launch()
return &executor
}
// launch starts the main loop for polling on the all the relevant channels and handling differents
// messages.
func (executor *Executor) launch() int {
reports := make(chan Report, executor.maxWorkers)
for {
select {
case signal := <-executor.signals:
if executor.handleSignals(signal) == 0 {
return 0
}
case r := <-reports:
executor.addReport(r)
default:
if executor.ActiveWorkers < executor.maxWorkers && len(executor.Tasks) > 0 {
task := <-executor.Tasks
atomic.AddInt64(&executor.ActiveWorkers, 1)
go executor.launchWorker(task, reports)
}
}
}
}
// handleSignals is called whenever anything is received on the 'signals' channel.
// It performs the relevant task according to the received signal(request) and then responds either
// with 0 or 1 indicating whether the request was respected(0) or rejected(1).
func (executor *Executor) handleSignals(signal int) int {
if signal == 1 {
log.Println("Received termination request...")
if executor.Inactive() {
log.Println("No active workers, exiting...")
executor.signals <- 0
return 0
}
executor.signals <- 1
log.Println("Some tasks are still active...")
}
return 1
}
// launchWorker is called whenever a new Task is received and Executor can spawn more workers to spawn
// a new Worker.
// Each worker is launched on a new goroutine. It performs the given task and publishes the report on
// the Executor's internal reports channel.
func (executor *Executor) launchWorker(task Task, reports chan<- Report) {
report := task.Execute()
if len(reports) < cap(reports) {
reports <- report
} else {
log.Println("Executor's report channel is full...")
}
atomic.AddInt64(&executor.ActiveWorkers, -1)
}
// AddTask is used to submit a new task to the Executor is a non-blocking way. The Client can submit
// a new task using the Executor's tasks channel directly but that will block if the tasks channel is
// full.
// It should be considered that this method doesn't add the given task if the tasks channel is full
// and it is up to client to try again later.
func (executor *Executor) AddTask(task Task) bool {
if len(executor.Tasks) == cap(executor.Tasks) {
return false
}
executor.Tasks <- task
return true
}
// addReport is used by the Executor to publish the reports in a non-blocking way. It client is not
// reading the reports channel or is slower that the Executor publishing the reports, the Executor's
// reports channel is going to get full. In that case this method will not block and that report will
// not be added.
func (executor *Executor) addReport(report Report) bool {
if len(executor.Reports) == cap(executor.Reports) {
return false
}
executor.Reports <- report
return true
}
// Inactive checks if the Executor is idle. This happens when there are no pending tasks, active
// workers and reports to publish.
func (executor *Executor) Inactive() bool {
return executor.ActiveWorkers == 0 && len(executor.Tasks) == 0 && len(executor.Reports) == 0
}
executor.go hosted with ? by GitHub
(左右滑動(dòng)查看全部代碼)
內(nèi)核十分簡(jiǎn)單,語(yǔ)言優(yōu)雅、現(xiàn)代
與其他多數(shù)的現(xiàn)代語(yǔ)言不同,Golang本身并沒有提供太多的特性。事實(shí)上,嚴(yán)格限制特性集的范圍正是Go語(yǔ)言的顯著特征,且Go語(yǔ)言著意于此。Go語(yǔ)言的設(shè)計(jì)與Java的編程范式不同,也不支持如Python一樣的多語(yǔ)言的編程范式。Go只是一個(gè)編程的骨架結(jié)構(gòu)。除了必要的特性,其他一無(wú)所有。
看過Go語(yǔ)言之后,第一感覺是其不遵循任何特定的哲學(xué)或者設(shè)計(jì)指引,所有的特性都是以引用的方式解決某一個(gè)特定的問題,不會(huì)畫蛇添足做多余的工作。比如,Go語(yǔ)言提供方法和接口但沒有類;Go語(yǔ)言的編譯器生成動(dòng)態(tài)鏈接庫(kù),但同時(shí)保留垃圾回收器;Go語(yǔ)言有嚴(yán)格的類型但不支持泛型;Go語(yǔ)言有一個(gè)輕量級(jí)的運(yùn)行時(shí)但不支持異常。
Go的這一設(shè)計(jì)理念的主要用意在于,在表達(dá)想法、算法或者編碼的環(huán)節(jié),開發(fā)者可以盡量少想或者不去想“在某種編程語(yǔ)言中處理此事的最佳方案”,讓不同的開發(fā)者可以更容易理解對(duì)方的代碼。不支持泛型和異常使得Go語(yǔ)言并不那么完美,也因此在很多場(chǎng)景下束手束腳 ,因此在“Go 2”版本中,官方加入了對(duì)這些必要特性的考慮。
高性能
單線程的執(zhí)行效率并不足以評(píng)估一門語(yǔ)言的優(yōu)劣,當(dāng)語(yǔ)言本身聚焦于解決并發(fā)和并行問題的時(shí)候尤其如此。即便如此,Golang還是跑出了亮眼的成績(jī),僅次于一些硬核的系統(tǒng)編程語(yǔ)言,如C/C++/Rust等等,并且Golang還在不斷的改進(jìn)。考慮到Go是有垃圾回收機(jī)制的語(yǔ)言,這一成績(jī)實(shí)際上相當(dāng)?shù)牧钊擞∠笊羁蹋@使得Go語(yǔ)言的性能可以應(yīng)付幾乎所有的使用場(chǎng)景。
(Image Source: Medium)
提供現(xiàn)代軟件開發(fā)所需要的原生工具支持
是否采用一種新的語(yǔ)言或工具,直接取決于開發(fā)者體驗(yàn)的好壞。就Go語(yǔ)言來(lái)說(shuō),其工具集是用戶采納的主要考量。同最小化的內(nèi)核一樣,Go的工具集也采用了同樣的設(shè)計(jì)理念,最小化,但足夠應(yīng)付需要。執(zhí)行所有Go語(yǔ)言工具,都采用 go 命令及其子命令,并且全部是以命令行的方式。
Go語(yǔ)言中并沒有類似pip或者npm這類包管理器。但只需要下面的命令,就可以得到任何的社區(qū)包:
go get github.com/farkaskid/WebCrawler/blob/master/executor/executor.go
(左右滑動(dòng)查看全部代碼)
是的,這樣就行。可以直接從Github或其他地方拉取所需要的包。所有的包都是源代碼文件的形態(tài)。
對(duì)于package.json這類的包,我沒有看到與 goget 等價(jià)的命令。事實(shí)上也沒有。在Go語(yǔ)言中,無(wú)須在一個(gè)單一文件中指定所有的依賴,可以在源文件中直接使用下面的命令:
import "github.com/xlab/pocketsphinx-go/sphinx"
(左右滑動(dòng)查看全部代碼)
那么,當(dāng)執(zhí)行g(shù)o build命令的時(shí)候,運(yùn)行時(shí)會(huì)自動(dòng)的運(yùn)行 goget 來(lái)獲取所需要的依賴。完整的源碼如下:
package main
import (
"encoding/binary"
"bytes"
"log"
"os/exec"
"github.com/xlab/pocketsphinx-go/sphinx"
pulse "github.com/mesilliac/pulse-simple" // pulse-simple
)
var buffSize int
func readInt16(buf []byte) (val int16) {
binary.Read(bytes.NewBuffer(buf), binary.LittleEndian, &val)
return
}
func createStream() *pulse.Stream {
ss := pulse.SampleSpec{pulse.SAMPLE_S16LE, 16000, 1}
buffSize = int(ss.UsecToBytes(1 * 1000000))
stream, err := pulse.Capture("pulse-simple test", "capture test", &ss)
if err != nil {
log.Panicln(err)
}
return stream
}
func listen(decoder *sphinx.Decoder) {
stream := createStream()
defer stream.Free()
defer decoder.Destroy()
buf := make([]byte, buffSize)
var bits []int16
log.Println("Listening...")
for {
_, err := stream.Read(buf)
if err != nil {
log.Panicln(err)
}
for i := 0; i < buffSize; i += 2 {
bits = append(bits, readInt16(buf[i:i+2]))
}
process(decoder, bits)
bits = nil
}
}
func process(dec *sphinx.Decoder, bits []int16) {
if !dec.StartUtt() {
panic("Decoder failed to start Utt")
}
dec.ProcessRaw(bits, false, false)
dec.EndUtt()
hyp, score := dec.Hypothesis()
if score > -2500 {
log.Println("Predicted:", hyp, score)
handleAction(hyp)
}
}
func executeCommand(commands ...string) {
cmd := exec.Command(commands[0], commands[1:]...)
cmd.Run()
}
func handleAction(hyp string) {
switch hyp {
case "SLEEP":
executeCommand("loginctl", "lock-session")
case "WAKE UP":
executeCommand("loginctl", "unlock-session")
case "POWEROFF":
executeCommand("poweroff")
}
}
func main() {
cfg := sphinx.NewConfig(
sphinx.HMMDirOption("/usr/local/share/pocketsphinx/model/en-us/en-us"),
sphinx.DictFileOption("6129.dic"),
sphinx.LMFileOption("6129.lm"),
sphinx.LogFileOption("commander.log"),
)
dec, err := sphinx.NewDecoder(cfg)
if err != nil {
panic(err)
}
listen(dec)
}
client.go hosted with ? by GitHub
(左右滑動(dòng)查看全部代碼)
上述的代碼將把所有的依賴聲明與源文件綁定在一起。
如你所見,Go語(yǔ)言是如此的簡(jiǎn)單、最小化但仍足夠滿足需要并且十分優(yōu)雅。Go語(yǔ)言提供了諸多的直接的工具支持,既可用于單元測(cè)試,也可以用于benchmark的火焰圖。誠(chéng)然,正如前面所講到的特性集方面的限制,Go語(yǔ)言也有其缺陷。比如, goget 并不支持版本化,一旦源文件中引用了某個(gè)URL,就將鎖定于此。但是,Go也還在逐漸的演進(jìn),一些依賴管理的工具也正在涌現(xiàn)。
Golang最初是設(shè)計(jì)用來(lái)解決Google的一些產(chǎn)品問題,比如厚重的代碼庫(kù),以及滿足編寫高效并發(fā)類應(yīng)用的急迫需求。在需要利用現(xiàn)代處理器的多核特性的場(chǎng)景,Go語(yǔ)言使得在應(yīng)用和庫(kù)文件的編程方面變得更加容易。并且,這些都不需要開發(fā)者來(lái)考慮。Go語(yǔ)言是一門現(xiàn)代的編程語(yǔ)言,簡(jiǎn)單是其主旨,Go語(yǔ)言永遠(yuǎn)不會(huì)考慮超過這一主旨的范疇。
二、Protobuf(Protocol Buffers)
Protobuf 或者說(shuō) Protocol Buffers是由Google研發(fā)的一種二進(jìn)制通信格式,用以對(duì)結(jié)構(gòu)化數(shù)據(jù)進(jìn)行序列化。格式是什么意思?類似于JSON這樣?是的。Protobuf已經(jīng)有10年的歷史,在Google內(nèi)部也已經(jīng)使用了一段時(shí)間。
既然已經(jīng)有了JSON這種通信格式,并且得到了廣泛的應(yīng)用,為什么需要Protobuf?
與Golang一樣,Protobuf實(shí)際上并有解決任何新的問題,只是在解決現(xiàn)有的問題方面更加高效,更加現(xiàn)代化。與Golang不同的是,Protobuf并不一定比現(xiàn)存的解決方案更加優(yōu)雅。下面是Protobuf的主要特性:
Protobuf是一種二進(jìn)制格式,不同于JSON和XML,后者是基于文本的也因此相對(duì)比較節(jié)省空間。
Protobuf提供了對(duì)于schema的精巧而直接的支持
Protobuf為生成解析代碼和消費(fèi)者代碼提供直接的多語(yǔ)言支持。
Protobuf的二進(jìn)制格式帶來(lái)的是傳輸速度方面的優(yōu)化
那么Protobuf是不是真的很快?簡(jiǎn)單回答,是的。根據(jù)Google Developer的數(shù)據(jù),相對(duì)于XML來(lái)說(shuō),Protobuf在體積上只有前者的1/3到1/10,在速度上卻要快20到100倍。毋庸置疑的是,由于采用了二進(jìn)制格式,序列化的數(shù)據(jù)對(duì)于人類來(lái)說(shuō)是不可讀的。
(Image Source: Beating JSON performance with Protobuf)
相對(duì)其他傳輸協(xié)議格式來(lái)說(shuō),Protobuf采用了更有規(guī)劃性的方式。首先需要定義 .proto 文件,這種文件與schema類似,但更強(qiáng)大。在 .proto 文件中定義消息結(jié)構(gòu),哪些字段是必選的哪些是可選的,以及字段的數(shù)據(jù)類型等。接下來(lái),Protobuf編譯器會(huì)生成用于數(shù)據(jù)訪問的類,開發(fā)者可以在業(yè)務(wù)邏輯中使用這些類來(lái)更方便的進(jìn)行數(shù)據(jù)傳輸。
觀察某個(gè)服務(wù)的 .proto 文件,可以清晰的獲知通信的細(xì)節(jié)以及暴露的特性。一個(gè)典型的 .proto 文件類似如下:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
protobufExample.proto hosted with ? by GitHub
(左右滑動(dòng)查看全部代碼)
曝個(gè)料:Stack Overflow的大牛Jon Skeet也是Protobuf項(xiàng)目的主要貢獻(xiàn)者之一。
三、gRPC
gRPC,物如其名,是一種功能齊備的現(xiàn)代的RPC框架,提供了諸多內(nèi)置支持的機(jī)制,如負(fù)載均衡、跟蹤、健康檢查和認(rèn)證等。gRPC由Google在2015年開源,并由此日益火爆。
既然已經(jīng)有了REST,還搞個(gè)RPC做什么?
在SOA架構(gòu)的時(shí)代,有相當(dāng)長(zhǎng)的時(shí)間,基于WSDL的SOAP協(xié)議是系統(tǒng)間通信的解決方案。彼時(shí),通信協(xié)議的定義是十分嚴(yán)格的,龐大的單體架構(gòu)系統(tǒng)暴露大量的接口用于擴(kuò)展。
隨著B/S理念的興起,服務(wù)器和客戶端開始解耦,在這樣的架構(gòu)下,即使客戶端和服務(wù)端分別進(jìn)行獨(dú)立的編碼,也不影響對(duì)服務(wù)的調(diào)用。客戶端想查詢一本書的信息,服務(wù)端會(huì)根據(jù)請(qǐng)求提供相關(guān)的列表供客戶端瀏覽。REST范式主要解決的就是這種場(chǎng)景下的問題,REST允許服務(wù)端和客戶端可以自由的通信,而不需要定義嚴(yán)格的契約以及獨(dú)有的語(yǔ)義。
從某種意義上講,此時(shí)的服務(wù)已經(jīng)開始變得像是單體式架構(gòu)系統(tǒng)一樣,對(duì)于某個(gè)特定的請(qǐng)求,會(huì)返回一坨毫無(wú)必要的數(shù)據(jù),用以滿足客戶端的“瀏覽”需求。但這并不是所有場(chǎng)景下都會(huì)發(fā)生的情況,不是么?
跨入微服務(wù)的時(shí)代
采用微服務(wù)架構(gòu)理由多多。最常提及的事實(shí)是,單體架構(gòu)太難擴(kuò)展了。以微服務(wù)架構(gòu)設(shè)計(jì)大型系統(tǒng),所有的業(yè)務(wù)和技術(shù)需求都傾向于實(shí)現(xiàn)成互相合作的組件,這些組件就是“微”服務(wù)。
微服務(wù)不需要以包羅萬(wàn)象的信息響應(yīng)用戶請(qǐng)求,而僅需要根據(jù)請(qǐng)求完成特定的任務(wù)并給出所需要的回應(yīng)。理想情況下,微服務(wù)應(yīng)該像一堆可以無(wú)縫組裝的函數(shù)。
使用REST做為此類服務(wù)的通信范式變得不那么有效。一方面,采用REST API確實(shí)可以讓服務(wù)的表達(dá)能力更強(qiáng),但同時(shí),如果這種表達(dá)的能力既非必要也并不出自設(shè)計(jì)者的本意,我們就需要根據(jù)不同的因素考慮其他范式了。
gRPC嘗試在如下的技術(shù)方面改進(jìn)傳統(tǒng)的HTTP請(qǐng)求:
默認(rèn)支持HTTP/2協(xié)議,并可以享受該協(xié)議帶來(lái)的所有好處
采用Protobuf格式用于機(jī)器間通信
得益于HTTP/2協(xié)議,提供了對(duì)流式調(diào)用的專有支持
對(duì)所有常用的功能提供了插件化的支持,如認(rèn)證、跟蹤、負(fù)載均衡和健康檢查等。
當(dāng)然,既然是RPC框架,仍舊會(huì)有服務(wù)定義和接口描述語(yǔ)言(DSL)的相關(guān)概念,REST世代的開發(fā)者可能會(huì)感覺這些概念有些格格不入,但是由于gRPC采用Protobuf做為通信格式,就不會(huì)顯得像以前那么笨拙。
Protobuf的設(shè)計(jì)理念使得其既是一種通信格式,又可以是一種協(xié)議規(guī)范工具,在此過程中無(wú)需做任何額外的工作。一個(gè)典型的gRPC服務(wù)定義類似如下:
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
serviceDefinition.proto hosted with ? by GitHub
(左右滑動(dòng)查看全部代碼)
只需要為服務(wù)定義一個(gè) .proto 文件,并在其中描述接口名稱,服務(wù)的需求,以及以Protobuf格式返回的消息即可。Protobuf編譯器會(huì)生成客戶端和服務(wù)端代碼。客戶端可以直接調(diào)用這些代碼,服務(wù)端可以用這些代碼實(shí)現(xiàn)API來(lái)填充業(yè)務(wù)邏輯。
四、結(jié)語(yǔ)
Golang,Protobuf和gRPC是現(xiàn)代服務(wù)端編程的后起之秀。Golang簡(jiǎn)化了并發(fā)/并行應(yīng)用的編程,gRPC和Protobuf的結(jié)合提供了更高效的通信并同時(shí)提供了更愉悅的開發(fā)者體驗(yàn)。
-
代碼
+關(guān)注
關(guān)注
30文章
4837瀏覽量
69122 -
服務(wù)端
+關(guān)注
關(guān)注
0文章
66瀏覽量
7066
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
SSR與微服務(wù)架構(gòu)的結(jié)合應(yīng)用
【米爾NXP i.MX 93開發(fā)板試用評(píng)測(cè)】4、使用golang搭建Modbus 服務(wù)器
調(diào)試stm32的TCP服務(wù)端程序,會(huì)導(dǎo)致hard fault的原因?
軟通動(dòng)力數(shù)據(jù)庫(kù)全棧服務(wù),助力企業(yè)數(shù)據(jù)庫(kù)體系全面升級(jí)
![軟通動(dòng)力數(shù)據(jù)庫(kù)全<b class='flag-5'>棧</b><b class='flag-5'>服務(wù)</b>,助力企業(yè)數(shù)據(jù)庫(kù)體系全面升級(jí)](https://file1.elecfans.com//web2/M00/06/2F/wKgaombYQEeAArOhAACG2y_aYds091.jpg)
使用NS1串口服務(wù)器HTTP模式上傳服務(wù)器數(shù)據(jù)
![使用NS1串口<b class='flag-5'>服務(wù)</b>器HTTP模式上傳<b class='flag-5'>服務(wù)</b>器數(shù)據(jù)](https://file.elecfans.com/web2/M00/3E/6A/pYYBAGJhBGGAGyDYAACBPQuBZQI711.png)
LwIP協(xié)議棧源碼詳解—TCP/IP協(xié)議的實(shí)現(xiàn)
請(qǐng)問ESP32作為藍(lán)牙服務(wù)端如何修改MTU?
請(qǐng)問esp_local_ctrl中服務(wù)端如何主動(dòng)發(fā)消息?
服務(wù)端測(cè)試包括什么類型
服務(wù)端測(cè)試是web測(cè)試嗎為什么
服務(wù)端測(cè)試和客戶端測(cè)試區(qū)別在哪
服務(wù)端的測(cè)試主要是測(cè)什么內(nèi)容
智行者聯(lián)合清華完成國(guó)內(nèi)首套全棧式端到端自動(dòng)駕駛系統(tǒng)的開放道路測(cè)試
![智行者聯(lián)合清華完成國(guó)內(nèi)首套全<b class='flag-5'>棧</b>式<b class='flag-5'>端</b>到<b class='flag-5'>端</b>自動(dòng)駕駛系統(tǒng)的開放道路測(cè)試](https://file1.elecfans.com/web2/M00/D5/44/wKgaomYlvJmAdFDQAAAe1YtYbSc681.jpg)
評(píng)論