go實現簡易分散式系統
眾所周知,go語言在網路服務模組有著得天獨厚的優勢,今天看了一些資料,準備自己寫一個簡單的分散式系統,具有以下功能:
- 能夠傳送和接收請求/響應
- 能夠連線到叢集
- 如果無法連線到叢集(假設它是第一個節點),則可以作為主節點啟動節點
- 每個節點有唯一的標識
- 能夠在節點之間交換json資料包
接收命令列引數的所有資訊
一 搭建模型
首先我們需要一個儲存節點資訊的結構體,包括節點id、節點ip、節點埠號:
//結構體物件可以和json互轉
type NodeInfo struct {
NodeId int `json:"nodeId"` //節點ID,通過隨機數生成
NodeIpAddr string `json:"nodeIpAddr"` //節點ip地址
Port string `json:"port"` //節點埠號
}
其次我們需要一個節點到叢集的一個請求或者響應的標準格式的結構體
type AddToClusterMessage struct {
Source NodeInfo `json:"source"`
Dest NodeInfo `json:"dest"`
Message string `json:"message"`
}
然後分別實現結構體的tostring格式化方法,整體程式碼distribute.go如下:
package main
import (
"fmt"
"math/rand"
"net"
"strconv"
"time"
)
//用於json和結構體物件的互轉
type NodeInfo struct {
NodeId int `json:"nodeId"` //節點ID,通過隨機數生成
NodeIpAddr string `json:"nodeIpAddr"` //節點ip地址
Port string `json:"port"` //節點埠號
}
//新增一個節點到叢集的一個請求或者響應的標準格式
type AddToClusterMessage struct {
Source NodeInfo `json:"source"`
Dest NodeInfo `json:"dest"`
Message string `json:"message"`
}
//將節點資訊格式化輸出
func (node *NodeInfo) String() string {
return "NodeInfo {nodeId:" + strconv.Itoa(node.NodeId) + ", nodeIpAddr:" + node.NodeIpAddr + ", port:" + node.Port + "}"
}
//將新增節點資訊格式化
func (req AddToClusterMessage) String() string {
return "AddToClusterMessage:{\n source:" + req.Source.String() + ",\n dest: " + req.Dest.String() + ",\n message:" + req.Message + " }"
}
func main() {
// makeMasterOnError := flag.Bool("makeMasterOnError", false, "make this node master if unable to connect to the cluster ip provided.")
// clusterip := flag.String("clusterip", "127.0.0.1:8001", "ip address of any node to connnect")
// myport := flag.String("myport", "8001", "ip address to run this node on. default is 8001."
rand.Seed(time.Now().UTC().UnixNano()) //種子
myid := rand.Intn(9999999)
fmt.Println(myid)
//獲取ip地址
myIp, _ := net.InterfaceAddrs()
fmt.Println(myIp[13])
//建立nodeInfo結構體
me := NodeInfo{NodeId: myid, NodeIpAddr: myIp[13].String(), Port: "8001"}
fmt.Println(me.String())
}
然後跑一下程式碼:
go run distribute.go
輸出如圖:
二 接受命令列引數解析
我們使用flag模組來解析命令列引數,先看一個簡單的例子瞭解下flag:
package main
import (
"flag"
"fmt"
)
func main() {
//第一個引數,為引數名稱,第二個引數為預設值,第三個引數是說明
username := flag.String("name", "", "Input your username")
flag.Parse()
fmt.Println("Hello, ", *username)
}
編譯:
go build flag.go
執行:
./flag -name=world
輸出:
Hello, world
如果不輸入name引數:
./flag
則輸出:
Hello,
然後在這個簡單的分散式系統裡面我們要做什麼,有三個命令需要解析:
//當第一個節點啟動用這個命令來將第一個節點作為主節點
makeMasterOnError := flag.Bool("makeMasterOnError", false, "make this node master if unable to connect to the cluster ip provided.")
//設定要連線的目的地ip地址
clusterip := flag.String("clusterip", "127.0.0.1:8001", "ip address of any node to connnect")
//設定要連線的目的地埠號
myport := flag.String("myport", "8001", "ip address to run this node on. default is 8001.")
flag.Parse()
三 連線函式以及監聽函式
連線目的地節點ip地址以及埠號:
func connectToCluster(me NodeInfo, dest NodeInfo) bool {
//連線到socket的相關細節資訊
connOut, err := net.DialTimeout("tcp", dest.NodeIpAddr+":"+dest.Port, time.Duration(10)*time.Second)
if err != nil {
if _, ok := err.(net.Error); ok {
fmt.Println("不能連線到叢集", me.NodeId)
return false
}
} else {
fmt.Println("連線到叢集")
text := "Hi nody.. 請新增我到叢集"
requestMessage := getAddToClusterMessage(me, dest, text)
json.NewEncoder(connOut).Encode(&requestMessage)
decoder := json.NewDecoder(connOut)
var responseMessage AddToClusterMessage
decoder.Decode(&responseMessage)
fmt.Println("得到資料響應:\n" + responseMessage.String())
return true
}
return false
}
getAddToClusterMessage函式用來返回響應資訊:
//傳送請求時格式化json包有用的工具
func getAddToClusterMessage(source NodeInfo, dest NodeInfo, message string) AddToClusterMessage {
return AddToClusterMessage{
Source: NodeInfo{
NodeId: source.NodeId,
NodeIpAddr: source.NodeIpAddr,
Port: source.Port},
Dest: NodeInfo{
NodeId: dest.NodeId,
NodeIpAddr: dest.NodeIpAddr,
Port: dest.Port},
Message: message,
}
}
本節點連線目的地節點成功或者成為第一個主節點後需要開啟監聽:
//me節點連線其它節點成功或者自身成為主節點之後開始監聽別的節點在未來可能對它自身的連線
func listenOnPort(me NodeInfo) {
//監聽即將到來的資訊
ln, _ := net.Listen("tcp", fmt.Sprint(":"+me.Port))
//接受連線
for {
connIn, err := ln.Accept()
if err != nil {
if _, ok := err.(net.Error); ok {
fmt.Println("Error received while listening.", me.NodeId)
}
} else {
var requestMessage AddToClusterMessage
json.NewDecoder(connIn).Decode(&requestMessage)
fmt.Println("Got request:\n" + requestMessage.String())
text := "已新增你到叢集"
responseMessage := getAddToClusterMessage(me, requestMessage.Source, text)
json.NewEncoder(connIn).Encode(&responseMessage)
connIn.Close()
}
}
}
四 完整程式碼
package main
import (
"encoding/json"
"flag"
"fmt"
"math/rand"
"net"
"strconv"
"strings"
"time"
)
//用於json和結構體物件的互轉
type NodeInfo struct {
NodeId int `json:"nodeId"` //節點ID,通過隨機數生成
NodeIpAddr string `json:"nodeIpAddr"` //節點ip地址
Port string `json:"port"` //節點埠號
}
//新增一個節點到叢集的一個請求或者響應的標準格式
type AddToClusterMessage struct {
Source NodeInfo `json:"source"`
Dest NodeInfo `json:"dest"`
Message string `json:"message"`
}
//將節點資訊格式化輸出
func (node *NodeInfo) String() string {
return "NodeInfo {nodeId:" + strconv.Itoa(node.NodeId) + ", nodeIpAddr:" + node.NodeIpAddr + ", port:" + node.Port + "}"
}
//將新增節點資訊格式化
func (req AddToClusterMessage) String() string {
return "AddToClusterMessage:{\n source:" + req.Source.String() + ",\n dest: " + req.Dest.String() + ",\n message:" + req.Message + " }"
}
func main() {
makeMasterOnError := flag.Bool("makeMasterOnError", false, "make this node master if unable to connect to the cluster ip provided.")
clusterip := flag.String("clusterip", "127.0.0.1:8001", "ip address of any node to connnect")
myport := flag.String("myport", "8001", "ip address to run this node on. default is 8001.")
flag.Parse()
rand.Seed(time.Now().UTC().UnixNano()) //種子
myid := rand.Intn(9999999)
//獲取ip地址
myIp, _ := net.InterfaceAddrs()
//建立nodeInfo結構體
me := NodeInfo{NodeId: myid, NodeIpAddr: myIp[13].String(), Port: *myport}
dest := NodeInfo{NodeId: -1, NodeIpAddr: strings.Split(*clusterip, ":")[0], Port: strings.Split(*clusterip, ":")[1]}
fmt.Println("我的節點資訊:", me.String())
//嘗試連線到叢集,在已連線的情況下向叢集傳送請求
ableToConnect := connectToCluster(me, dest)
//如果dest節點不存在,則me節點為主節點啟動,否則直接退出系統
if ableToConnect || (!ableToConnect && *makeMasterOnError) {
if *makeMasterOnError {
fmt.Println("將啟動me節點為主節點")
}
listenOnPort(me)
} else {
fmt.Println("正在退出系統,請設定me節點為主節點")
}
}
//傳送請求時格式化json包有用的工具
func getAddToClusterMessage(source NodeInfo, dest NodeInfo, message string) AddToClusterMessage {
return AddToClusterMessage{
Source: NodeInfo{
NodeId: source.NodeId,
NodeIpAddr: source.NodeIpAddr,
Port: source.Port},
Dest: NodeInfo{
NodeId: dest.NodeId,
NodeIpAddr: dest.NodeIpAddr,
Port: dest.Port},
Message: message,
}
}
func connectToCluster(me NodeInfo, dest NodeInfo) bool {
//連線到socket的相關細節資訊
connOut, err := net.DialTimeout("tcp", dest.NodeIpAddr+":"+dest.Port, time.Duration(10)*time.Second)
if err != nil {
if _, ok := err.(net.Error); ok {
fmt.Println("不能連線到叢集", me.NodeId)
return false
}
} else {
fmt.Println("連線到叢集")
text := "Hi nody.. 請新增我到叢集"
requestMessage := getAddToClusterMessage(me, dest, text)
json.NewEncoder(connOut).Encode(&requestMessage)
decoder := json.NewDecoder(connOut)
var responseMessage AddToClusterMessage
decoder.Decode(&responseMessage)
fmt.Println("得到資料響應:\n" + responseMessage.String())
return true
}
return false
}
//me節點連線其它節點成功或者自身成為主節點之後開始監聽別的節點在未來可能對它自身的連線
func listenOnPort(me NodeInfo) {
//監聽即將到來的資訊
ln, _ := net.Listen("tcp", fmt.Sprint(":"+me.Port))
//接受連線
for {
connIn, err := ln.Accept()
if err != nil {
if _, ok := err.(net.Error); ok {
fmt.Println("Error received while listening.", me.NodeId)
}
} else {
var requestMessage AddToClusterMessage
json.NewDecoder(connIn).Decode(&requestMessage)
fmt.Println("Got request:\n" + requestMessage.String())
text := "已新增你到叢集"
responseMessage := getAddToClusterMessage(me, requestMessage.Source, text)
json.NewEncoder(connIn).Encode(&responseMessage)
connIn.Close()
}
}
}
go install之後啟動第一個節點:
main --makeMasterOnError true
另開一個終端連線剛剛啟動的主節點8001:
main --myport 8002 --clusterip 127.0.0.1:8001
可以發現得到8001節點的響應,8001節點也得到8002的請求
當然也可以使用8003節點去連線8002節點:
main --myport 8003 --clusterip 127.0.0.1:8002
相關文章
- 第17章-golang實現簡易的分散式系統Golang分散式
- 實現一個簡易的響應式系統
- Go 實現簡易 RPC 框架GoRPC框架
- 分散式系統Session 實現方式分散式Session
- Go實踐:用Sync.Map實現簡易記憶體快取系統Go記憶體快取
- 大型分散式系統現場,阿里大牛帶你實戰分散式系統分散式阿里
- 最簡單的分散式檔案系統 go-fastdfs分散式GoAST
- Go語言分散式系統配置管理實踐--go archaiusGo分散式AI
- 基於AOP和Redis實現的簡易版分散式鎖Redis分散式
- 用 Go + Redis 實現分散式鎖GoRedis分散式
- 用Akka構建一個簡易的分散式檔案系統分散式
- web端作業控制系統簡易實現Web
- Python實現簡易版選課系統Python
- Go 實現簡易的 Redis 客戶端GoRedis客戶端
- 簡單瞭解分散式系統分散式
- gin websocket 簡單分散式實現Web分散式
- jQuery實現簡易商城系統專案實操詳解jQuery
- 分散式系統-實驗-shardkv分散式
- 使用go搭建一個簡易的部落格系統Go
- 分散式跟蹤系統zipkin簡介分散式
- Go語言實現的簡易TCP通訊框架GoTCP框架
- 分散式系統選主場景分析及實現分散式
- 分散式系統限流演算法分析與實現分散式演算法
- PHP 實現簡單阻塞分散式鎖PHP分散式
- 簡易ATM系統
- 分散式系統1:什麼是分散式系統——簡要的介紹與定義分散式
- namedtuple簡易實現
- 【分散式系統設計簡卷(0)】MapReduce分散式
- 分散式系統分散式
- VirtualView iOS 簡易字串表示式的實現ViewiOS字串
- 日誌系統實戰(三)-分散式跟蹤的Net實現分散式
- 分散式系統2:分散式系統中的時鐘分散式
- 基於long pull實現簡易的訊息系統參考
- 分散式系統:系統模型分散式模型
- 整合spring cloud雲架構 --spring cloud分散式系統中實現分散式鎖SpringCloud架構分散式
- 作業系統實驗5 簡易檔案管理系統作業系統
- 分散式 - 分散式系統的特點分散式
- 分散式系統(三)——分散式事務分散式