Skip to content

单体架构 & 微服务结构了解 ​

  • 单体架构: 所有业务逻辑在一个程序中, 只要程序挂了所有服务不可用 一荣俱荣 一损俱损
  • 微服务架构: 将不同的功能拆分成不同的服务, 然后通过远程调用的方式实现交互 互不干扰 易于扩容 复杂度高

原生 RPC 入门 ​

什么是 RPC ​

Remote Procedure Call: 翻译为中文就是 远程过程调用, 简单理解就是通过对网络协议的封装, 将调用远程服务像调用本地函数一样简单方便

什么是GRPC ​

gRPC 是一个高性能/开源和通用的 RPC 框架, 面向 HTTP/2 设计, 多编程语言支持, 由 google 开发并维护

这个 g 就是 google 的意思, 表示 gRPC 是谷歌开发维护的高性能通用 RPC 开发框架

体验原生 RPC ​

1.初始化项目 ​

sh
mkdir go_rpc_demo && cd -
go mod init go_rpc_demo

mkdir server
touch server/main.go # rpc 服务端代码

mkdir client
touch client/main.go # rpc 客户端代码

mkdir shared         # 共同用到的代码
touch shared/arith.go

2.源码实现 ​

go
package main

import (
	"fmt"
	"log"
	"net"
	"net/rpc"

	"go_rpc_demo/shared"
)

func main() {
	// 1.注册服务
	arith := new(shared.Arith)
	err := rpc.Register(arith)
	if err != nil {
		log.Fatal("注册服务失败:", err)
	}

	// 3.创建 TCP 监听器
	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
		log.Fatal("监听失败:", err)
	}
	defer listener.Close()
	fmt.Println("RPC 服务端已启动,监听端口 :1234")

	// 4.接受连接并处理
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("接受连接失败:", err)
			continue
		}

		// 使用 goroutine 处理每个连接
		go rpc.ServeConn(conn)
	}
}
go
package main

import (
	"fmt"
	"log"
	"net/rpc"

	"go_rpc_demo/shared"
)

func main() {
	// 1.连接 RPC 服务器
	client, err := rpc.Dial("tcp", "localhost:1234")
	if err != nil {
		log.Fatal("连接失败:", err)
	}
	defer client.Close()

	// 2.准备参数和返回值
	args := &shared.Args{A: 10, B: 5}
	var reply int

	// 3.像使用本地的方法一样直接执行远程代码(这是通过网络的)
	// 3.1同步调用 - 乘法
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("调用 Multiply 失败:", err)
	}
	fmt.Printf("乘法: %d * %d = %d\n", args.A, args.B, reply)

	// 3.2同步调用 - 除法
	err = client.Call("Arith.Divide", args, &reply)
	if err != nil {
		log.Fatal("调用 Divide 失败:", err)
	}
	fmt.Printf("除法: %d / %d = %d\n", args.A, args.B, reply)

	// 3.3测试除以零的情况
	args2 := &shared.Args{A: 10, B: 0}
	err = client.Call("Arith.Divide", args2, &reply)
	if err != nil {
		fmt.Printf("错误: %v\n", err)
	}

	// 3.4异步调用示例
	done := make(chan *rpc.Call, 1)
	client.Go("Arith.Multiply", args, &reply, done)
	call := <-done
	if call.Error != nil {
		log.Fatal("异步调用失败:", call.Error)
	}
	fmt.Printf("异步乘法: %d * %d = %d\n", args.A, args.B, reply)
}
go
package shared

import "fmt"

// Args 定义 RPC 方法的参数
type Args struct {
	A, B int
}

// Arith 定义算术运算服务
type Arith struct{}

// Multiply 乘法运算
// 注意: RPC 方法必须满足以下条件:
// 1. 方法是公开的(首字母大写)
// 2. 有两个参数, 都是导出类型或内建类型
// 3. 第二个参数是指针
// 4. 返回 error 类型
func (a *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

// Divide 除法运算
func (a *Arith) Divide(args *Args, reply *int) error {
	if args.B == 0 {
		return fmt.Errorf("divide by zero")
	}
	*reply = args.A / args.B
	return nil
}
go
.
├── client
│   └── main.go
├── go.mod
├── server
│   └── main.go
└── shared
    └── arith.go

3.运行测试 ​

  1. 启动服务端监听1234端口: go run server/main.go
  2. 运行客户端测试远程过程调用是否成功: go run client/main.go

客户端输出如下:

txt
乘法: 10 * 5 = 50
除法: 10 / 5 = 2
错误: divide by zero
异步乘法: 10 * 5 = 50

4.原生 RPC 的缺点 ​

  1. 编写复杂, 需要自己实现调用过程
  2. 而且 cliet.Call("xxx") 都是字符串, 没有代码提示容易出错

GRPC 入门 ​

由于原生的 RPC 有许多缺点, 为了避免这些缺陷, 所以需要使用谷歌封装好的 RPC 框架: gRPC

1.初始化项目 ​

sh
mkdir go_grpc_demo && cd -
go mod init go_grpc_demo

mkdir server
touch server/main.go # gRPC 服务端

mkdir client
touch client/main.go # gRPC 客户端

mkdir proto
touch proto/hello/hello.proto # protobuf

# 安装 protobuf 序列化库和 grpc 框架
go get -u google.golang.org/grpc
go get -u google.golang.org/protobuf

2.源码实现 ​

go
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"time"

	pb "go_grpc_demo/proto/hello"

	"google.golang.org/grpc"
)

type server struct {
	pb.UnimplementedGreeterServer
}

// 简单 RPC 实现
func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received request from: %s, age: %d", req.Name, req.Age)

	message := fmt.Sprintf("Hello %s! You are %d years old.", req.Name, req.Age)

	return &pb.HelloReply{
		Message: message,
	}, nil
}

// 服务端流式 RPC 实现
func (s *server) SayHelloServerStream(req *pb.HelloRequest, stream pb.Greeter_SayHelloServerStreamServer) error {
	log.Printf("Server streaming for: %s", req.Name)

	for i := 0; i < 5; i++ {
		message := fmt.Sprintf("Hello %s! Message #%d", req.Name, i+1)

		if err := stream.Send(&pb.HelloReply{
			Message: message,
		}); err != nil {
			return err
		}

		time.Sleep(1 * time.Second)
	}

	return nil
}

// 客户端流式 RPC 实现
func (s *server) SayHelloClientStream(stream pb.Greeter_SayHelloClientStreamServer) error {
	var names []string

	for {
		req, err := stream.Recv()
		if err == io.EOF {
			// 客户端发送完毕,返回响应
			message := fmt.Sprintf("Hello to all: %v", names)
			return stream.SendAndClose(&pb.HelloReply{
				Message: message,
			})
		}
		if err != nil {
			return err
		}

		log.Printf("Received: %s", req.Name)
		names = append(names, req.Name)
	}
}

// 双向流式 RPC 实现
func (s *server) SayHelloBidirectional(stream pb.Greeter_SayHelloBidirectionalServer) error {
	var wg sync.WaitGroup
	wg.Add(1)

	// 接收消息的 goroutine
	go func() {
		defer wg.Done()
		for {
			req, err := stream.Recv()
			if err == io.EOF {
				return
			}
			if err != nil {
				log.Printf("Error receiving: %v", err)
				return
			}
			log.Printf("Received: %s", req.Name)
		}
	}()

	// 发送消息
	names := []string{"Alice", "Bob", "Charlie"}
	for _, name := range names {
		message := fmt.Sprintf("Hello %s from server!", name)
		if err := stream.Send(&pb.HelloReply{
			Message: message,
		}); err != nil {
			return err
		}
		time.Sleep(500 * time.Millisecond)
	}

	wg.Wait()
	return nil
}

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})

	log.Printf("Server listening on :50051")
	if err := s.Serve(lis); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}
go
package main

import (
	"context"
	"io"
	"log"
	"time"

	pb "go_grpc_demo/proto/hello"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
  // 0. 链接 gPRC 服务端
	conn, err := grpc.NewClient(
		"localhost:50051",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		log.Fatalf("Failed to create client: %v", err)
	}
	defer conn.Close()

	client := pb.NewGreeterClient(conn)

	// 1. 调用简单 RPC
	callSayHello(client)

	// 2. 调用服务端流式 RPC
	callSayHelloServerStream(client)

	// 3. 调用客户端流式 RPC
	callSayHelloClientStream(client)

	// 4. 调用双向流式 RPC
	callSayHelloBidirectional(client)
}

// 简单 RPC
func callSayHello(client pb.GreeterClient) {
	log.Println("=== 简单 RPC ===")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	resp, err := client.SayHello(ctx, &pb.HelloRequest{
		Name: "Alice",
		Age:  25,
	})
	if err != nil {
		log.Fatalf("Error calling SayHello: %v", err)
	}

	log.Printf("Response: %s\n", resp.Message)
}

// 服务端流式 RPC
func callSayHelloServerStream(client pb.GreeterClient) {
	log.Println("\n=== 服务端流式 RPC ===")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	stream, err := client.SayHelloServerStream(ctx, &pb.HelloRequest{
		Name: "Bob",
	})
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	for {
		resp, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatalf("Error receiving: %v", err)
		}
		log.Printf("Received: %s", resp.Message)
	}
}

// 客户端流式 RPC
func callSayHelloClientStream(client pb.GreeterClient) {
	log.Println("\n=== 客户端流式 RPC ===")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	stream, err := client.SayHelloClientStream(ctx)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	names := []string{"Alice", "Bob", "Charlie", "David"}

	for _, name := range names {
		if err := stream.Send(&pb.HelloRequest{
			Name: name,
		}); err != nil {
			log.Fatalf("Error sending: %v", err)
		}
		log.Printf("Sent: %s", name)
		time.Sleep(500 * time.Millisecond)
	}

	resp, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("Error receiving: %v", err)
	}

	log.Printf("Response: %s", resp.Message)
}

// 双向流式 RPC
func callSayHelloBidirectional(client pb.GreeterClient) {
	log.Println("\n=== 双向流式 RPC ===")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	stream, err := client.SayHelloBidirectional(ctx)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	// 发送消息
	names := []string{"Alice", "Bob", "Charlie"}

	go func() {
		for _, name := range names {
			if err := stream.Send(&pb.HelloRequest{
				Name: name,
			}); err != nil {
				log.Fatalf("Error sending: %v", err)
			}
			log.Printf("Sent: %s", name)
			time.Sleep(1 * time.Second)
		}
		stream.CloseSend()
	}()

	// 接收消息
	for {
		resp, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatalf("Error receiving: %v", err)
		}
		log.Printf("Received from server: %s", resp.Message)
	}
}
proto
syntax = "proto3";

package hello;

option go_package = "go_grpc_demo/proto/hello";

// 定义服务
service Greeter {
  // 简单 RPC
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  // 服务端流式 RPC
  rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply) {}

  // 客户端流式 RPC
  rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply) {}

  // 双向流式 RPC
  rpc SayHelloBidirectional (stream HelloRequest) returns (stream HelloReply) {}
}

// 请求消息
message HelloRequest {
  string name = 1;
  int32 age = 2;
}

// 响应消息
message HelloReply {
  string message = 1;
}
txt
module go_grpc_demo

go 1.26.1

require (
	google.golang.org/grpc v1.81.1
	google.golang.org/protobuf v1.36.11
)

require (
	golang.org/x/net v0.55.0 // indirect
	golang.org/x/sys v0.45.0 // indirect
	golang.org/x/text v0.37.0 // indirect
	google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
)
txt
.
├── client
│   └── main.go
├── go.mod
├── go.sum
├── justfile
├── proto
│   └── hello
│       ├── hello.pb.go
│       ├── hello.proto
│       └── hello_grpc.pb.go
└── server
    └── main.go

3.运行测试 ​

  1. 将 proto 编译为 go 语言代码, 确保已经安装了 protoc
sh
protoc --go_out=. --go_opt=paths=source_relative  --go-grpc_out=. --go-grpc_opt=paths=source_relative  proto/hello.proto
  1. 启动 gRPC 服务端, 监听 50051 端口
  2. 运行客户端测试远程过程调用是否成功: go run client/main.go

执行客户端最终输出如下:

sh
2026/06/07 15:09:02 === 简单 RPC ===
2026/06/07 15:09:02 Response: Hello Alice! You are 25 years old.
2026/06/07 15:09:02
=== 服务端流式 RPC ===
2026/06/07 15:09:02 Received: Hello Bob! Message #1
2026/06/07 15:09:03 Received: Hello Bob! Message #2
2026/06/07 15:09:04 Received: Hello Bob! Message #3
2026/06/07 15:09:05 Received: Hello Bob! Message #4
2026/06/07 15:09:06 Received: Hello Bob! Message #5
2026/06/07 15:09:07
=== 客户端流式 RPC ===
2026/06/07 15:09:07 Sent: Alice
2026/06/07 15:09:08 Sent: Bob
2026/06/07 15:09:08 Sent: Charlie
2026/06/07 15:09:09 Sent: David
2026/06/07 15:09:09 Response: Hello to all: [Alice Bob Charlie David]
2026/06/07 15:09:09
=== 双向流式 RPC ===
2026/06/07 15:09:09 Sent: Alice
2026/06/07 15:09:09 Received from server: Hello Alice from server!
2026/06/07 15:09:10 Received from server: Hello Bob from server!
2026/06/07 15:09:10 Sent: Bob
2026/06/07 15:09:10 Received from server: Hello Charlie from server!
2026/06/07 15:09:11 Sent: Charlie

Released under the MIT License.