当前位置: 首页 > news >正文

GO RPC 教学文档

目录

  1. RPC简介
  2. GO RPC基础
  3. GO RPC的使用方法
    • 服务器端实现
    • 客户端实现
  4. GO RPC的高级特性
    • JSON RPC
    • HTTP RPC
    • 异步RPC
  5. 实例演示
  6. 最佳实践
  7. 常见问题与解决方案

1. RPC简介

RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许一个计算机程序调用另一个地址空间(通常是共享网络的另一台计算机上)的子程序或函数,而程序员就像调用本地程序一样,无需额外地为这个交互过程编程。

RPC的主要优点:

  • 透明性:调用远程服务就像调用本地函数一样简单
  • 封装性:隐藏了底层网络通信的细节
  • 位置透明:无需知道服务器的具体位置

2. GO RPC基础

Go语言在标准库中提供了net/rpc包,用于实现RPC服务。Go RPC具有以下特点:

  • 使用Go的gob编码进行序列化
  • 服务端和客户端都需要用Go实现
  • 支持TCP、HTTP等传输协议
  • 要求方法的参数和返回值都必须是导出类型(首字母大写)
  • 方法必须有两个参数,第一个是接收的参数,第二个是返回给客户端的参数,第二个参数必须是指针类型
  • 方法必须有一个返回值,类型为error

3. GO RPC的使用方法

3.1 服务器端实现

下面是一个简单的RPC服务器实现:

package mainimport ("errors""fmt""net""net/http""net/rpc"
)// 定义一个算术服务类型
type Arith struct{}// 乘法方法,必须满足RPC规范
func (t *Arith) Multiply(args *Args, reply *int) error {*reply = args.A * args.Breturn nil
}// 除法方法
func (t *Arith) Divide(args *Args, reply *float64) error {if args.B == 0 {return errors.New("divide by zero")}*reply = float64(args.A) / float64(args.B)return nil
}// 参数结构
type Args struct {A, B int
}func main() {// 创建服务实例arith := new(Arith)// 注册服务rpc.Register(arith)// 方式1:使用TCP协议rpc.HandleHTTP()l, e := net.Listen("tcp", ":1234")if e != nil {fmt.Println("listen error:", e)}go http.Serve(l, nil)// 方式2:直接使用TCP监听// listener, err := net.Listen("tcp", ":1234")// if err != nil {//     fmt.Println("listen error:", err)// }// go rpc.Accept(listener)fmt.Println("RPC server listening on :1234")// 保持服务运行select {}
}

3.2 客户端实现

下面是调用上述RPC服务的客户端代码:

package mainimport ("fmt""net/rpc"
)type Args struct {A, B int
}func main() {// 连接RPC服务器client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {fmt.Println("dialing:", err)}// 同步调用args := &Args{7, 8}var reply interr = client.Call("Arith.Multiply", args, &reply)if err != nil {fmt.Println("arith error:", err)} else {fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)}// 异步调用var quotient float64divCall := client.Go("Arith.Divide", args, &quotient, nil)replyCall := <-divCall.Done // 等待调用完成if replyCall.Error != nil {fmt.Println("arith error:", replyCall.Error)} else {fmt.Printf("Arith: %d/%d=%f\n", args.A, args.B, quotient)}
}

4. GO RPC的高级特性

4.1 JSON RPC

Go RPC还支持JSON格式的RPC,这使得其他语言编写的客户端也能调用Go的RPC服务。

服务器端实现:

package mainimport ("errors""fmt""net""net/rpc""net/rpc/jsonrpc"
)type Args struct {A, B int
}type Arith intfunc (t *Arith) Multiply(args *Args, reply *int) error {*reply = args.A * args.Breturn nil
}func (t *Arith) Divide(args *Args, reply *float64) error {if args.B == 0 {return errors.New("divide by zero")}*reply = float64(args.A) / float64(args.B)return nil
}func main() {arith := new(Arith)rpc.Register(arith)listener, err := net.Listen("tcp", ":1234")if err != nil {fmt.Println("listen error:", err)}for {conn, err := listener.Accept()if err != nil {continue}go jsonrpc.ServeConn(conn)}
}

客户端实现:

package mainimport ("fmt""net/rpc/jsonrpc"
)type Args struct {A, B int
}func main() {conn, err := net.Dial("tcp", "localhost:1234")if err != nil {fmt.Println("dialing:", err)}client := jsonrpc.NewClient(conn)args := &Args{7, 8}var reply interr = client.Call("Arith.Multiply", args, &reply)if err != nil {fmt.Println("arith error:", err)} else {fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)}
}

4.2 HTTP RPC

Go RPC也可以通过HTTP协议提供服务,这样可以通过HTTP客户端进行调用。

package mainimport ("errors""fmt""net/http""net/rpc"
)type Args struct {A, B int
}type Arith intfunc (t *Arith) Multiply(args *Args, reply *int) error {*reply = args.A * args.Breturn nil
}func (t *Arith) Divide(args *Args, reply *float64) error {if args.B == 0 {return errors.New("divide by zero")}*reply = float64(args.A) / float64(args.B)return nil
}func main() {arith := new(Arith)rpc.Register(arith)rpc.HandleHTTP()err := http.ListenAndServe(":1234", nil)if err != nil {fmt.Println("error:", err)}
}

4.3 异步RPC

Go RPC支持异步调用,通过Go方法实现:

package mainimport ("fmt""net/rpc""time"
)type Args struct {A, B int
}func main() {client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {fmt.Println("dialing:", err)}// 异步调用args := &Args{7, 8}var reply intcall := client.Go("Arith.Multiply", args, &reply, nil)// 可以做其他事情fmt.Println("Doing other work...")time.Sleep(1 * time.Second)// 获取结果<-call.Doneif call.Error != nil {fmt.Println("arith error:", call.Error)} else {fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)}
}

5. 实例演示

下面是一个完整的实例,实现一个简单的键值存储服务:

服务器端 (kv_server.go)

package mainimport ("net""net/rpc""sync"
)// 键值存储结构
type KeyValueStore struct {mu   sync.Mutexdata map[string]string
}// 存储值请求
type PutRequest struct {Key   stringValue string
}// 存储值响应
type PutResponse struct {Success boolMessage string
}// 获取值请求
type GetRequest struct {Key string
}// 获取值响应
type GetResponse struct {Value  stringFound  boolMessage string
}// 删除值请求
type DeleteRequest struct {Key string
}// 删除值响应
type DeleteResponse struct {Success boolMessage string
}// 存储值
func (k *KeyValueStore) Put(req *PutRequest, res *PutResponse) error {k.mu.Lock()defer k.mu.Unlock()k.data[req.Key] = req.Valueres.Success = trueres.Message = "Value stored successfully"return nil
}// 获取值
func (k *KeyValueStore) Get(req *GetRequest, res *GetResponse) error {k.mu.Lock()defer k.mu.Unlock()value, found := k.data[req.Key]if found {res.Value = valueres.Found = trueres.Message = "Value found"} else {res.Found = falseres.Message = "Key not found"}return nil
}// 删除值
func (k *KeyValueStore) Delete(req *DeleteRequest, res *DeleteResponse) error {k.mu.Lock()defer k.mu.Unlock()_, found := k.data[req.Key]if found {delete(k.data, req.Key)res.Success = trueres.Message = "Key deleted successfully"} else {res.Success = falseres.Message = "Key not found"}return nil
}func main() {// 初始化键值存储kvStore := &KeyValueStore{data: make(map[string]string),}// 注册RPC服务rpc.Register(kvStore)// 设置监听listener, err := net.Listen("tcp", ":1234")if err != nil {panic(err)}fmt.Println("KeyValueStore RPC server listening on :1234")// 接受连接for {conn, err := listener.Accept()if err != nil {continue}go rpc.ServeConn(conn)}
}

客户端 (kv_client.go)

package mainimport ("fmt""net/rpc"
)func main() {// 连接RPC服务器client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {fmt.Println("dialing:", err)return}// 存储值putReq := &PutRequest{Key:   "name",Value: "John Doe",}putRes := &PutResponse{}err = client.Call("KeyValueStore.Put", putReq, putRes)if err != nil {fmt.Println("Put error:", err)} else {fmt.Printf("Put: %s - %s\n", putRes.Message, putReq.Key)}// 获取值getReq := &GetRequest{Key: "name",}getRes := &GetResponse{}err = client.Call("KeyValueStore.Get", getReq, getRes)if err != nil {fmt.Println("Get error:", err)} else {if getRes.Found {fmt.Printf("Get: %s - %s: %s\n", getRes.Message, getReq.Key, getRes.Value)} else {fmt.Printf("Get: %s\n", getRes.Message)}}// 删除值delReq := &DeleteRequest{Key: "name",}delRes := &DeleteResponse{}err = client.Call("KeyValueStore.Delete", delReq, delRes)if err != nil {fmt.Println("Delete error:", err)} else {fmt.Printf("Delete: %s - %s\n", delRes.Message, delReq.Key)}// 再次获取值,验证删除err = client.Call("KeyValueStore.Get", getReq, getRes)if err != nil {fmt.Println("Get error:", err)} else {if getRes.Found {fmt.Printf("Get: %s - %s: %s\n", getRes.Message, getReq.Key, getRes.Value)} else {fmt.Printf("Get: %s\n", getRes.Message)}}
}

6. 最佳实践

  1. 错误处理

    • 总是检查并返回错误
    • 提供有意义的错误信息
  2. 并发安全

    • 使用互斥锁保护共享数据
    • 考虑使用读写锁提高读多写少场景的性能
  3. 超时控制

    • 设置合理的超时时间,避免客户端长时间等待
    • 使用context包控制超时
  4. 日志记录

    • 记录重要操作和错误信息
    • 考虑使用结构化日志
  5. 接口设计

    • 保持接口简单明了
    • 避免过度设计
  6. 性能优化

    • 考虑使用连接池
    • 批量处理请求
  7. 安全考虑

    • 添加认证机制
    • 考虑使用TLS加密通信

7. 常见问题与解决方案

问题1:RPC调用超时

解决方案

// 使用DialTimeout设置连接超时
client, err := rpc.DialTimeout("tcp", "localhost:1234", 10*time.Second)
if err != nil {fmt.Println("dialing error:", err)return
}// 使用context控制调用超时
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()// 使用Go方法异步调用
call := client.Go("Arith.Multiply", args, &reply, nil)
select {
case <-call.Done:// 调用完成if call.Error != nil {fmt.Println("call error:", call.Error)} else {fmt.Printf("Result: %d\n", reply)}
case <-ctx.Done():// 超时fmt.Println("call timeout")
}

问题2:并发访问共享数据

解决方案

type SafeCounter struct {mu sync.Mutexv  map[string]int
}func (c *SafeCounter) Inc(key string) {c.mu.Lock()defer c.mu.Unlock()c.v[key]++
}func (c *SafeCounter) Value(key string) int {c.mu.Lock()defer c.mu.Unlock()return c.v[key]
}

问题3:服务发现和负载均衡

解决方案

type ClientPool struct {mu      sync.Mutexclients []*rpc.Clientindex   int
}func (p *ClientPool) Get() *rpc.Client {p.mu.Lock()defer p.mu.Unlock()if len(p.clients) == 0 {return nil}client := p.clients[p.index]p.index = (p.index + 1) % len(p.clients)return client
}func (p *ClientPool) Add(addr string) error {client, err := rpc.Dial("tcp", addr)if err != nil {return err}p.mu.Lock()defer p.mu.Unlock()p.clients = append(p.clients, client)return nil
}

问题4:序列化和反序列化性能问题

解决方案

// 使用更高效的序列化方式,如protobuf
// 首先定义protobuf消息类型
// message Args {
//     int32 A = 1;
//     int32 B = 2;
// }// 然后在代码中使用
func (t *Arith) Multiply(argsBytes []byte, reply *int) error {args := &Args{}if err := proto.Unmarshal(argsBytes, args); err != nil {return err}*reply = args.A * args.Breturn nil
}


文章转载自:

http://8sjcOT1o.zmbzL.cn
http://AShIkaZB.zmbzL.cn
http://AFdAAKrH.zmbzL.cn
http://Fwmbpx0h.zmbzL.cn
http://uuz3Q3kz.zmbzL.cn
http://1LsL8vzN.zmbzL.cn
http://EB1IZ7JL.zmbzL.cn
http://ZeoeWf99.zmbzL.cn
http://8PuZ6Ab3.zmbzL.cn
http://zKCUf6Dj.zmbzL.cn
http://vYR3QqjZ.zmbzL.cn
http://hbeCFq5B.zmbzL.cn
http://j4bgRK6J.zmbzL.cn
http://x2Q1U58E.zmbzL.cn
http://KQQmViDW.zmbzL.cn
http://IHR4Wt2n.zmbzL.cn
http://A2kN7a4P.zmbzL.cn
http://rt6LOrCa.zmbzL.cn
http://fWbgkUAs.zmbzL.cn
http://quTt3o0S.zmbzL.cn
http://9tdIEJsX.zmbzL.cn
http://dQSvsKu1.zmbzL.cn
http://STdnaBUh.zmbzL.cn
http://EW0dm8Ez.zmbzL.cn
http://Pp4RGbCc.zmbzL.cn
http://bbVH0fpT.zmbzL.cn
http://C9ZAuZJn.zmbzL.cn
http://bweB6wLu.zmbzL.cn
http://pAtKoKGB.zmbzL.cn
http://8tXm3DwQ.zmbzL.cn
http://www.dtcms.com/a/375359.html

相关文章:

  • Atlantis Word Processor:全方位的文字处理专家
  • [iOS] 单例模式的深究
  • 视频通话实现语音转文字
  • String-HashCode源码分析
  • 深入浅出C++继承机制:从入门到实战
  • 级联框的实现
  • android 性能优化—内存泄漏,内存溢出OOM
  • 从PyTorch到ONNX:模型部署性能提升
  • JAVA:实现快速排序算法的技术指南
  • SQL 触发器从入门到进阶:原理、时机、实战与避坑指南
  • 无标记点动捕技术:重塑展厅展馆的沉浸式数字交互新时代
  • 【Agent】DeerFlow Planner:执行流程与架构设计(基于真实 Trace 深度解析)
  • R语言读取excel文件数据-解决na问题
  • 在钉钉上长出的AI组织:森马的路径与启示
  • IntelliJ IDEA 中 JVM 配置参考
  • JVM(二)--- 类加载子系统
  • 9.ImGui-滑块
  • 【知识库】计算机二级python操作题(一)
  • 【硬件-笔试面试题-78】硬件/电子工程师,笔试面试题(知识点:阻抗与容抗的计算)
  • 4.5Vue的列表渲染
  • 使用YOLO11进行路面裂缝检测
  • 常见并行概念解析
  • 9月9日
  • centos系统上部署安装minio
  • 下载CentOS 7——从阿里云上下载不同版本的 CentOS 7
  • 《预约一团乱麻?预约任务看板让你告别排班噩梦!宠物店效率翻倍指南》
  • Shell 脚本条件测试与 if 语句
  • 【倒数日子隐私收集】
  • Diamond基础4:仿真流程、添加原语IP核
  • Java入门级教程14——同步安全机制明锁