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