怎么做淘寶客網(wǎng)站做淘客拼多多代運(yùn)營(yíng)一般多少錢(qián)
Go管理工具
早期 Go 語(yǔ)言不使用 go module 進(jìn)行包管理,而是使用 go path 進(jìn)行包管理,這種管理方式十分老舊,兩者最顯著的區(qū)別就是:Go Path 創(chuàng)建之后沒(méi)有 go.mod 文件被創(chuàng)建出來(lái),而 go module 模式會(huì)創(chuàng)建出一個(gè) go.mod 文件用于管理包信息
現(xiàn)在就是:盡量使用 Go Modules 模式
另外,我們?cè)谝氚臅r(shí)候,可以先進(jìn)行 import 再通過(guò)編譯器來(lái)下載內(nèi)容,這樣能讓我們更簡(jiǎn)便的處理包關(guān)系
Go 編碼規(guī)范
命名規(guī)范
命名首字母大寫(xiě) 被視為 Public,小寫(xiě)被視為 Private
包名和目錄名應(yīng)為小寫(xiě),不帶有下劃線與駝峰
文件名應(yīng)為全小寫(xiě),其中可能帶有的多個(gè)單詞以下劃線分隔
對(duì)于接口的命名,我們應(yīng)該在末尾加上 ‘er’ 來(lái)標(biāo)注
常量使用全大寫(xiě)命名,中間使用下劃線隔開(kāi)
Go 語(yǔ)言中的包分為三種:Go 語(yǔ)言自帶的標(biāo)準(zhǔn)庫(kù)中的包、第三方包、自己寫(xiě)的包
RPC
rpc 是指 remote procedure call 也就是遠(yuǎn)程節(jié)點(diǎn)調(diào)用,其實(shí)就是一個(gè)節(jié)點(diǎn)調(diào)用另一個(gè)節(jié)點(diǎn)
這之中最關(guān)鍵的三個(gè)問(wèn)題是:Call的id映射、序列化與反序列化、網(wǎng)絡(luò)傳輸
Call id 映射問(wèn)題解決的是:系統(tǒng)A的程序想要遠(yuǎn)程調(diào)用系統(tǒng)B的程序時(shí),B中有許多個(gè)程序,到底調(diào)用哪個(gè)程序的問(wèn)題,也就是說(shuō),B系統(tǒng)中的每個(gè)程序都具有一個(gè)唯一 id,只要其他系統(tǒng)在發(fā)起遠(yuǎn)程調(diào)用時(shí)攜帶自己要調(diào)用程序的 Call id,系統(tǒng)B就能成功識(shí)別他想要運(yùn)行的程序
我們的調(diào)用邏輯是:
將傳遞的參數(shù)使用 json 協(xié)議進(jìn)行傳輸(類(lèi)似的協(xié)議還有 xml、protobuf、msgpack)另外現(xiàn)在網(wǎng)絡(luò)調(diào)用有兩個(gè)端:客戶端用于發(fā)送數(shù)據(jù),服務(wù)器端用于接收數(shù)據(jù)
另外一個(gè)問(wèn)題就是:JSON 不是一個(gè)高性能的編碼協(xié)議,我們?cè)谧非髽O致性能的時(shí)候可能不會(huì)優(yōu)先考慮 json
另外:json 的優(yōu)勢(shì)在于其通用性,可擴(kuò)展性,幾乎所有的系統(tǒng)都支持 json,但其另外的問(wèn)題就是其過(guò)于靈活,不能將其作為程序的對(duì)象存儲(chǔ)來(lái)代替struct
而我們的網(wǎng)絡(luò)協(xié)議是看不懂 struct 的,其只能識(shí)別二進(jìn)制的流,故而我們必須將我們轉(zhuǎn)換的數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制的流才可以進(jìn)行傳輸
在一次 RPC 過(guò)程中,服務(wù)器端和客戶端分別要做的事情:
客戶端:
1. 建立連接:tcp \ http
2. 將我們要發(fā)送的數(shù)據(jù)序列化為 json 字符串 - 序列化
3. 發(fā)送,實(shí)際上發(fā)送的是二進(jìn)制流
4. 等待服務(wù)器結(jié)果
5. 服務(wù)器返回結(jié)果,客戶端將結(jié)果反序列化為可識(shí)別數(shù)據(jù)
服務(wù)器端:
1. 監(jiān)聽(tīng)網(wǎng)絡(luò)端口(80)
2. 讀取客戶端發(fā)來(lái)的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換成Employee對(duì)象,反序列化
3. 處理數(shù)據(jù),生成一個(gè)帶有更完成信息的對(duì)象,例如:R,其中封裝了404、201等信息
4. 將處理的數(shù)據(jù)結(jié)果轉(zhuǎn)換成 json 二進(jìn)制, 序列化 發(fā)送給客戶端
我們的序列化技術(shù)不一定非要使用 json、我們還可以選擇:xml、protobuf、msgpack 等
我們只要解決了 序列化問(wèn)題,其實(shí)就解決了數(shù)據(jù)互通問(wèn)題,其實(shí)也就屏蔽了我們相互調(diào)用時(shí)的語(yǔ)言不同的問(wèn)題(java、python、go)
網(wǎng)絡(luò)問(wèn)題:
我們使用 http 與 tcp 最大的區(qū)別就是:
http是一次性的,建立連接之后,一旦收到數(shù)據(jù)的返回,tcp 連接就斷開(kāi),而 tcp 連接是可以復(fù)用的,解決了 tcp 連接要重新建立的問(wèn)題
另外,我們也可以使用 http2.0 來(lái)解決這個(gè)問(wèn)題,http2.0 支持長(zhǎng)連接,可以解決連接的建立問(wèn)題
一個(gè)簡(jiǎn)單的例子:(服務(wù)器端)
func main() {http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {// 這里面寫(xiě)邏輯_ = r.ParseForm() // 解析參數(shù),可能會(huì)報(bào)錯(cuò)fmt.Println("path: ", r.URL.Path)a, _ := strconv.Atoi(r.Form["a"][0])b, _ := strconv.Atoi(r.Form["a"][0])// 進(jìn)行返回w.Header().Set("Content-Type", "application/json")// 構(gòu)建返回體jData, _ := json.Marshal(map[string]int{"data": a + b,})// 真正寫(xiě)入w.Write(jData)})// 設(shè)置監(jiān)聽(tīng)的端口_ = http.ListenAndServe(":8000", nil)
}
上面這個(gè)例子就典型的解決了:
Call ID問(wèn)題:使用URL路徑指明要調(diào)用的方法
數(shù)據(jù)傳輸協(xié)議:Http 的參數(shù)傳遞協(xié)議
但這里的問(wèn)題是:使用的是 http1.0的協(xié)議,性能低,手寫(xiě)http,數(shù)據(jù)需要自己解析,效率低
客戶端舉例:
我們也可以不寫(xiě)客戶端,直接使用瀏覽器來(lái)發(fā)送請(qǐng)求解決問(wèn)題,
我們?cè)L問(wèn):127.0.0.1:8000/add?a=1&b=4
RPC 技術(shù)原理:
客戶端發(fā)送請(qǐng)求,由客戶端存根處理,客戶端存根將請(qǐng)求整理成協(xié)議對(duì)應(yīng)的格式進(jìn)行發(fā)送,將其發(fā)送到服務(wù)器端,由服務(wù)器端接收這之后發(fā)送給服務(wù)器端存根進(jìn)行解碼,再返回給服務(wù)器處理
RPC 開(kāi)發(fā)(Hello World級(jí)別)
創(chuàng)建目錄結(jié)構(gòu)
Project
server
server.go
client
client.go
server.go:
type HelloService struct {
}// 給這個(gè)類(lèi)綁定這個(gè)方法
func (s *HelloService) Hello(request string, reply *string) error {// 通過(guò)修改 reply 值來(lái)進(jìn)行返回*reply = "hello, " + requestreturn nil
}/*
*
1. 實(shí)例化一個(gè)server
2. 注冊(cè)邏輯
3. 開(kāi)啟服務(wù)
*/
func main() {// 第一步:實(shí)例化 serverlistener, _ := net.Listen("tcp", ":1234")// 第二步:將我們的 struct 注冊(cè)進(jìn) RPC// 我們?nèi)绻研螀⒘斜碇械膮?shù)定義為 interface{} 就代表這個(gè)參數(shù)我們可以任意傳入_ = rpc.RegisterName("HelloService", &HelloService{})// 第三部:啟動(dòng)服務(wù),綁定rpcconn, _ := listener.Accept()rpc.ServeConn(conn)
}
client.go:
func main() {// Dial() 意思是撥號(hào),也就是嘗試進(jìn)行連接,同時(shí)進(jìn)行Gob進(jìn)行編碼client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {panic("鏈接失敗")}// 注意這種方式會(huì)直接開(kāi)辟一片空間,并且給這片空間的 string 賦予初值 ''//var reply string// 如果以下面這種方式就復(fù)雜一點(diǎn)var replyy *string = new(string)// 發(fā)送請(qǐng)求,請(qǐng)求對(duì)應(yīng)的方法,其后面的參數(shù)是傳入的參數(shù),根據(jù)我們方法的編寫(xiě),我們最后一個(gè)參數(shù)用來(lái)接收數(shù)據(jù)err = client.Call("HelloService.Hello", "Chen", replyy)if err != nil {panic("方法調(diào)用出錯(cuò)")}fmt.Println(*replyy)}hello, Chen
注意在上面的代碼中,實(shí)例化 server、啟動(dòng)服務(wù)都是由 net 包完成的,但單獨(dú) net 包是不能完成一整個(gè)流程的,這是因?yàn)檫€有 call id 的匹配以及序列化機(jī)制是由 rpc 來(lái)完成的
另外的是:GRPC在此時(shí)還不夠簡(jiǎn)潔,使用效率不夠高,這體現(xiàn)在包括客戶端在調(diào)用時(shí)不能直接調(diào)用方法,而是需要明確方法名等
同時(shí),上面這種最基本的rpc使用的編碼解碼協(xié)議是 Gob,這只能在 go 語(yǔ)言中進(jìn)行通信,其不支持跨語(yǔ)言
使用JSON協(xié)議的RPC
建立目錄結(jié)構(gòu):
json_rpc_test
server
server.go
client
client.go
server.go
type HelloService struct {
}func (s *HelloService) Hello(request string, reply *string) error {// 通過(guò)修改 reply 值來(lái)進(jìn)行返回*reply = "hello, " + requestreturn nil
}
func main() {listener, _ := net.Listen("tcp", ":1234")_ = rpc.RegisterName("HelloService", &HelloService{})// 允許服務(wù)器處理多次請(qǐng)求:for {conn, _ := listener.Accept()// 使用自定義協(xié)議進(jìn)行修改go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) // 這里不加 go 協(xié)程會(huì)出現(xiàn)多個(gè)請(qǐng)求的并發(fā)問(wèn)題}
}
client.go
func main() {// 使用基礎(chǔ)的撥號(hào),不進(jìn)行編碼,編碼在后面進(jìn)行conn, err := net.Dial("tcp", "localhost:1234")if err != nil {panic("鏈接失敗")}// 進(jìn)行 json 編碼client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))var replyy *string = new(string)err = client.Call("HelloService.Hello", "Chen", replyy)if err != nil {panic("方法調(diào)用出錯(cuò)")}fmt.Println(*replyy)}
測(cè)試 RPC 的跨語(yǔ)言特性:
使用 python 的socket 編程發(fā)送一個(gè)json (不使用http,因?yàn)槲覀兎?wù)器沒(méi)有使用http,無(wú)法進(jìn)行解析)
import json
import socketrequest = {"id":0,"params":["Zhang"],"method":"HelloService.Hello"
}client = socket.create_connection(("localhost", 1234))
client.sendall(json.dumps(request).encode())# 獲取服務(wù)器返回的數(shù)據(jù)
rsp = client.recv(1024)
rsp = json.loads(rsp.decode())
print(rsp["result"])hello, Zhang
使用 java:
public static void main(String[] args) {try {// Connect to the serverSocket socket = new Socket("localhost", 1234);// Create a JSON requestString jsonRequest = "{\"id\": 0, \"method\": \"HelloService.Hello\", \"params\": [\"Yang\"]}";// Send the JSON request to the serverOutputStream outputStream = socket.getOutputStream();outputStream.write(jsonRequest.getBytes());outputStream.flush();// Receive the response from the serverBufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String response = reader.readLine();System.out.println("Response from server: " + response);// Close the socketsocket.close();} catch (IOException e) {e.printStackTrace();}}Response from server: {"id":0,"result":"hello, Yang","error":null}
使用 HTTP 協(xié)議 + JSON 的RPC
其實(shí)在這一步,已經(jīng)有成熟框架可以使用,這里為了學(xué)習(xí),我們使用rpc搭建一個(gè)自己的框架
構(gòu)建目錄:
http_rpc_test
server
server.go
client
client.go
server.go
type HelloService struct {
}func (s *HelloService) Hello(request string, reply *string) error {// 通過(guò)修改 reply 值來(lái)進(jìn)行返回*reply = "hello, " + requestreturn nil
}
func main() {_ = rpc.RegisterName("HelloService", &HelloService{})http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {var conn io.ReadWriteCloser = struct {io.Writerio.ReadCloser}{ReadCloser: r.Body,Writer: w,}rpc.ServeRequest(jsonrpc.NewServerCodec(conn))})http.ListenAndServe(":1234", nil)
}
在這里就可以使用其他語(yǔ)言發(fā)送 http 請(qǐng)求,獲取結(jié)果了
將方法改造成調(diào)用式的方法
以基礎(chǔ)的 HelloWorld 級(jí)別代碼為例,構(gòu)建目錄:
new_helloworld
server
server.go
client
client.go
handler
handler.go
server_proxy
server_proxy.go
client_proxy
client_proxy.go
我們通過(guò)定義一個(gè)公共文件,來(lái)實(shí)現(xiàn):
handler.go
const HelloServiceName = "handler/HelloService"type HelloService struct{}// 給這個(gè)類(lèi)綁定這個(gè)方法
func (s *HelloService) Hello(request string, reply *string) error {// 通過(guò)修改 reply 值來(lái)進(jìn)行返回*reply = "hello, " + requestreturn nil
}
server.go
func main() {// 第一步:實(shí)例化 serverlistener, _ := net.Listen("tcp", ":1234")// 第二步:將我們的 struct 注冊(cè)進(jìn) RPC// 我們?nèi)绻研螀⒘斜碇械膮?shù)定義為 interface{} 就代表這個(gè)參數(shù)我們可以任意傳入_ = rpc.RegisterName(handler.HelloServiceName, &handler.HelloService{})// 第三部:啟動(dòng)服務(wù),綁定rpcfor {conn, _ := listener.Accept()go rpc.ServeConn(conn)}
client.go
func main() {// Dial() 意思是撥號(hào),也就是嘗試進(jìn)行連接client := client_proxy.NewHelloServiceClient("tcp", "127.0.0.1:1234")// 注意這種方式會(huì)直接開(kāi)辟一片空間,并且給這片空間的 string 賦予初值 ''//var reply string// 如果以下面這種方式就復(fù)雜一點(diǎn)var replyy *string = new(string)// 發(fā)送請(qǐng)求,請(qǐng)求對(duì)應(yīng)的方法,其后面的參數(shù)是傳入的參數(shù),根據(jù)我們方法的編寫(xiě),我們最后一個(gè)參數(shù)用來(lái)接收數(shù)據(jù)_ = client.Hello("Chen", replyy)fmt.Println(*replyy)
}
server_proxy.go
type HellosSrvicer interface {Hello(request string, reply *string) error
}func RegisterHelloService(srv HellosSrvicer) error {return rpc.RegisterName(handler.HelloServiceName, srv)
}
client_proxy.go
type HelloServiceStub struct {*rpc.Client
}// 初始化,在Go中使用 Newxxx進(jìn)行初始化
func NewHelloServiceClient(protcol, address string) HelloServiceStub {conn, err := rpc.Dial(protcol, address)if err != nil {panic("connect error")}return HelloServiceStub{conn}
}func (c *HelloServiceStub) Hello(request string, reply *string) error {err := c.Call(handler.HelloServiceName+".Hello", request, reply)return err
}