Go从入门到进阶,大厂案例全流程实践
实战 Go:构建简单分布式系统
分布式系统设计思路
在编写代码之前,我们先来确定需要实现的功能:
- 主节点(Master Node):作为中控系统,负责派发任务命令。
- 工作节点(Worker Node):作为执行者,负责执行任务。
此外,系统还需要实现以下功能:
- 上报运行状态(Report Status):工作节点向主节点上报其当前状态。
- 分派任务(Assign Task):通过 API 请求,主节点向工作节点分派任务。
- 运行脚本(Execute Script):工作节点执行任务中的脚本。
实战步骤
节点通信
节点间的通信是分布式系统的核心。我们选择使用 gRPC 协议,它是一种流行的远程过程调用(RPC)框架,允许调用者在远程机器上执行命令。
-
创建
go.mod文件并添加依赖:module go-distributed-system // go 1.17 require ( github.com/golang/protobuf v1.5.0 google.golang.org/grpc v1.27.0 google.golang.org/protobuf v1.27.1 ) -
使用 Protocol Buffers 创建
node.proto文件定义 gRPC 协议:syntax = "proto3"; package core; option go_package = ".;core"; message Request { string action = 1; } message Response { string data = 1; } service NodeService { rpc ReportStatus(Request) returns (Response){}; // Simple RPC rpc AssignTask(Request) returns (stream Response){}; // Server-Side RPC } -
使用
protoc命令将.proto文件转换为 Go 代码:mkdir core protoc --go_out=./core \ --go-grpc_out=./core \ node.proto
gRPC 服务端实现
- 创建
core/node_service_server.go文件,实现 gRPC 服务端逻辑:package core import ( "context" ) type NodeServiceGrpcServer struct { UnimplementedNodeServiceServer // channel to receive command CmdChannel chan string } func (n NodeServiceGrpcServer) ReportStatus(ctx context.Context, request *Request) (*Response, error) { return &Response{Data: "ok"}, nil } func (n NodeServiceGrpcServer) AssignTask(request *Request, server NodeService_AssignTaskServer) error { for { select { case cmd := <-n.CmdChannel: // receive command and send to worker node (client) if err := server.Send(&Response{Data: cmd}); err != nil { return err } } } } var server *NodeServiceGrpcServer // GetNodeServiceGrpcServer singleton service func GetNodeServiceGrpcServer() *NodeServiceGrpcServer { if server == nil { server = &NodeServiceGrpcServer{ CmdChannel: make(chan string), } } return server }
主节点实现
- 创建
node.go文件,实现主节点逻辑:package core import ( "github.com/gin-gonic/gin" "google.golang.org/grpc" "net" "net/http" ) // MasterNode is the node instance type MasterNode struct { api *gin.Engine // api server ln net.Listener // listener svr *grpc.Server // grpc server nodeSvr *NodeServiceGrpcServer // node service } func (n *MasterNode) Init() (err error) { // grpc server listener with port as 50051 n.ln, err = net.Listen("tcp", ":50051") if err != nil { return err } // grpc server n.svr = grpc.NewServer() // node service n.nodeSvr = GetNodeServiceGrpcServer() // register node service to grpc server RegisterNodeServiceServer(n.svr, n.nodeSvr) // api n.api = gin.Default() n.api.POST("/tasks", func(c *gin.Context) { // parse payload var payload struct { Cmd string `json:"cmd"` } if err := c.ShouldBindJSON(&payload); err != nil { c.AbortWithStatus(http.StatusBadRequest) return } // send command to node service n.nodeSvr.CmdChannel <- payload.Cmd c.AbortWithStatus(http.StatusOK) }) return nil } func (n *MasterNode) Start() { // start grpc server go n.svr.Serve(n.ln) // start api server _ = n.api.Run(":9092") // wait for exit n.svr.Stop() } var node *MasterNode // GetMasterNode returns the node instance func GetMasterNode() *MasterNode { if node == nil { // node node = &MasterNode{} // initialize node if err := node.Init(); err != nil { panic(err) } } return node }
工作节点实现
- 创建
core/worker_node.go文件,实现工作节点逻辑:package core import ( "context" "google.golang.org/grpc" "os/exec" ) type WorkerNode struct { conn *grpc.ClientConn // grpc client connection c NodeServiceClient // grpc client } func (n *WorkerNode) Init() (err error) { // connect to master node n.conn, err = grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { return err } // grpc client n.c = NewNodeServiceClient(n.conn) return nil } func (n *WorkerNode) Start() { // log fmt.Println("worker node started") // report status _, _ = n.c.ReportStatus(context.Background(), &Request{}) // assign task stream, _ := n.c.AssignTask(context.Background(), &Request{}) for { // receive command from master node res, err := stream.Recv() if err != nil { return } // log command fmt.Println("received command: ", res.Data) // execute command parts := strings.Split(res.Data, " ") if err := exec.Command(parts[0], parts[1:]...).Run(); err != nil { fmt.Println(err) } } } var workerNode *WorkerNode func GetWorkerNode() *WorkerNode { if workerNode == nil { // node workerNode = &WorkerNode{} // initialize node if err := workerNode.Init(); err != nil { panic(err) } } return workerNode }
封装主程序
- 创建
main.go文件,封装主程序逻辑:package main import ( "go-distributed-system/core" "os" ) func main() { nodeType := os.Args[1] switch nodeType { case "master": core.GetMasterNode().Start() case "worker": core.GetWorkerNode().Start() default: panic("invalid node type") } }
运行和测试
-
启动主节点:
go run main.go master -
启动工作节点:
go run main.go worker -
通过 API 发送任务:
curl -X POST \ -H "Content-Type: application/json" \ -d '{"cmd": "touch /tmp/hello-distributed-system"}' \ http://localhost:9092/tasks -
检查文件是否生成:
ls -l /tmp/hello-distributed-system
构建完备的Golang知识体系
1. Golang 语言概述
Go(Golang)是由Google开发的编程语言,具备静态强类型、编译型、并发性,并且内置垃圾回收功能。Go的语法与C语言相似,但在变量声明上有所区别。Go语言并未包含C++的一些特性,如枚举、异常处理、继承等,而是提供了切片(Slice)、并发、管道、垃圾回收和接口等语言级特性。
2. Golang学习资料
- 文章:理解Go语言特性。
- 书籍:系统学习Go编程。
- 作者论文:了解Go设计的理念。
- 理论分析:掌握Go的内部机制。
- 开源框架:通过实践学习Go应用。
- 云原生技术:探索Go在云服务中的应用。
- 专家视频:观看业界专家讲解。
- 大厂实战分享:学习业界最佳实践。
3. Go语言的应用场景
Go语言设计用于系统编程,特别适合于Web服务器、存储集群等大型中央服务器。在高性能分布式系统领域,Go提供了高效的开发效率和海量并行支持,非常适合游戏服务端开发。
4. Golang适用的开发领域
- 云计算基础设施:Docker、Kubernetes等。
- 基础后端软件:TiDB、InfluxDB等。
- 微服务架构:go-kit、micro等。
- 互联网基础设施:以太坊、Hyperledger等。
5. Golang的局限性
- 包管理:依赖于GitHub,由个人账户维护,存在风险。
- 泛化类型缺失:Go 2.0计划中将加入。
- 异常处理:所有异常通过Error处理。
- C语言互操作性:与C的互操作并非无缝,存在序列化问题。
6. Golang CPU性能分析
6.1 程序运行时间分析
使用time指令(Linux系统)分析程序运行时间,包括:
real:程序实际经过的时间。user:程序在用户CPU上的时间。sys:程序在系统CPU上的时间。
一般real时间大于user和sys之和。
/usr/bin/time -v go run a.go
6.2 Golang CPU性能分析工具
-
引入
net/http/pprof包并开启监听:import _ "net/http/pprof" http.ListenAndServe("0.0.0.0:10000", nil)通过浏览器访问
http://127.0.0.1:10000/debug/pprof/查看CPU信息。 -
使用
go tool pprof命令分析性能数据:go tool pprof [binary] [profile] top //查看当前profile文件的cpu使用率
7. Golang程序内存分析方法
7.1 内存占用情况
使用top -p $(pidof 进程名)命令查看进程的内存占用情况。
7.2 使用GODEBUG和gctrace分析内存使用
设置GODEBUG='gctrace=1'来输出GC数据,分析垃圾回收时间和内存使用情况。
7.3 runtime.MemStats调试内存占用
定义runtime.MemStats对象来查看内存状态。
7.4 pprof分析内存
在程序中添加web端口监听,并通过http://127.0.0.1:10000/debug/pprof/heap?debug=1查看内存信息。
8. Golang面试题知识点总结
- 函数返回值:多返回值时,至少一个有名称,其他也需命名。
- 结构体比较:只有相同类型和属性顺序的结构体才可比较。
- String与Nil类型:空字符串
""表示String的空值,不能将nil赋值给String。 - 常量:数据类型是固定内存大小的别名,用于编译器预算内存空间。
- 内存四区:栈区、堆区、全局区、代码区,各自有不同的特点和用途。
- 数组与切片:讨论了切片的初始化、追加、拼接和使用
new的问题。 - Map:讨论了Map的value赋值和遍历问题。
- Interface:讨论了interface的赋值、内部构造和空接口问题。
- Channel:总结了Channel可能出现的特殊情况。
- WaitGroup:提供了WaitGroup的使用图解。