Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——服务端注册源代码实现原理分析
远程过程调用 RPC——服务端注册源代码实现原理分析
rpc server demo代码参考我前一篇博文:https://www.cnblogs.com/zuoyang/p/18146870
RPC Server端的RPC代码架构主要由两大部分构成:
-
-
-
第一部分是服务方法的注册过程。在这个过程中,
我们首先通过调用
rpc.Register
接口将服务对象(如示例中的StringService
)的方法进行注册。rpc.Register
函数内部会利用反射机制, 自动提取出服务对象中满足RPC接口要求的方法 (即那些接收两个参数且返回error
类型结果的方法), 并将这些方法的信息存储在一个内部的方法映射表中。在我们的Server
端代码中,rpc.Register
(service
)这行代码就完成了StringService
中的Concat
和Diff
方法的注册, 使得这些方法可以被远程调用。 -
第二部分是处理来自网络的
RPC
调用。Server
端会监听指定的端口(如示例中的:1234
),等待客户端的连接。一旦有客户端连接成功,Server
就会读取传入的数据包, 解码RPC
请求, 并根据请求中指定的方法名从方法映射表中查找到对应的方法。然后,Server
会通过反射调用该方法, 将结果编码后发送回客户端。在我们的Server
端代码中, 这部分逻辑是通过l.Accept()
接受连接, 并使用rpc.ServeConn
(conn
)来处理每一个RPC
请求实现的。
-
第一部分是服务方法的注册过程。在这个过程中,
我们首先通过调用
-
关于第一部分的处理步骤, 可以概括为以下几个环节:
-
-
-
首先,
通过
rpc.Register
(service
)注册StringService
中的RPC
方法, 如Concat
。 -
接着,
rpc
包利用Go语言的反射功能, 自动获取已注册方法的详细信息, 如方法名、参数类型和返回值类型等。 -
最后, 这些信息被保存在
RPC
服务器的内部数据结构中, 以便在处理客户端的RPC
调用时能够快速定位和执行相应的方法。
-
首先,
通过
-
一、注册服务
Register 和 RegisterName 方法是进行 RPC 服务注册的入口方法, 其参数 interface{} 类型的 rcvr 就是要注册的 RPC 服务类型。这两个方法都直接调用了 DefaultServer 的相应方法,
C:\Program Files\Go\src\net\rpc\server.go,源代码如下所示:
上述代码中的 DefaultServer 是 rpc 库自带的默认网络 Server 的实例, 它的定义如下所示:
来, 我们详细看看,在Go的RPC(远程过程调用)框架中,
服务的注册是一个关键步骤,
它使得客户端能够通过网络调用服务端的方法。在提供的代码片段中,
Register
和RegisterName
是两个用于注册RPC
服务的方法, 它们都是对DefaultServer
中相应方法的封装。
首先, 我们来看一下DefaultServer
的定义:
1 2 | // DefaultServer is the default instance of *Server. var DefaultServer = NewServer() |
DefaultServer
是一个全局变量,
它是Server
类型的一个实例。Server
类型代表了一个RPC服务器,
其中包含了服务映射(serviceMap
)、请求锁(reqLock
)和响应锁(respLock
)等字段,用于处理并发的RPC
请求和响应。通过调用NewServer()
函数,
我们创建了一个新的Server
实例,
并将其赋值给DefaultServer
。
接下来,
我们分析Register
和RegisterName
函数:
1、Register 函数:
1 2 3 | func Register(rcvr any) error { return DefaultServer.Register(rcvr) } |
Register
函数接收一个interface{}
类型的参数rcvr
,
这个参数代表要注册的RPC服务类型。函数内部直接调用了DefaultServer
的Register
方法,
将服务注册到默认的RPC
服务器上。如果注册成功, 则返回nil
, 如果注册失败, 则返回一个错误。
需要注意的是,
Register
函数使用接收者的具体类型作为服务的名称。这意味着,
如果你注册了同一个类型的多个接收者, 后面注册的接收者会覆盖先前注册的服务。
2、RegisterName 函数:
1 2 3 | func RegisterName(name string, rcvr any) error { return DefaultServer.RegisterName(name, rcvr) } |
与Register
函数类似,
RegisterName
函数也接收一个RPC服务类型作为参数,
但是它还额外接收一个字符串参数name
, 用于指定服务的名称。这样,
你可以为同一类型的多个接收者指定不同的服务名称, 从而避免服务被覆盖的问题。
在RegisterName
函数内部,
同样调用了DefaultServer
的RegisterName
方法来完成服务的注册。
3、Register 和 RegisterName 函数的关键区别:
register
和registerName
函数在Go
语言的RPC
(远程过程调用)框架中用于注册服务,
以便远程客户端可以调用这些服务。除了函数名不同之外,
它们之间还有以下关键区别:
1. 服务命名方式:
register
函数使用接收者的具体类型名作为RPC
服务的名称。这意味着,
如果你有两个相同类型的服务实例并尝试使用register
进行注册,
后注册的服务会覆盖先前注册的服务, 因为它们共享相同的类型名。
registerName
函数允许开发者显式地指定一个字符串作为服务的名称,
而不是使用接收者的类型名。这个特性提供了更大的灵活性,
允许为同一类型的多个服务实例注册不同的名称,
从而避免了命名冲突和服务覆盖的问题。
2. 使用场景:
当你希望自动化管理服务名称, 并且确信同一类型只会有一个服务实例被注册时,
可以使用register
函数。
当你需要为同一类型的多个服务实例提供不同的访问点时,
或者想要更精细地控制服务名称时, 应该使用registerName
函数。
3. 参数差异:
register
函数只需要一个参数,
即要注册的服务实例(rcvr
), 它是一个接口类型的变量,
表示任何符合RPC
规范的结构体或对象。
registerName
函数需要两个参数:一个自定义的服务名称(name
)和要注册的服务实例(rcvr
)。
register
和registerName
函数的主要区别在于服务命名方式、使用场景以及函数参数的不同。这些差异使得开发者能够根据具体需求灵活选择适合的注册方式。
二、反射处理
我们再来具体看一下RPC Server
的Register
方法的实现。
通过反射获取接口类型和值,并通过suitableMethods
函数判断注册的RPC
是否符合规范,最后调用serviceMap
的LoadOrStore(sname, s)
方法将对应RPC
存根存放于map
中,供以后查找。
C:\Program Files\Go\src\net\rpc\server.go,具体代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | // 这段Golang代码定义了一个Server结构体,用于实现RPC(远程过程调用)服务。它包含了注册、检查类型和方法是否符合RPC要求的相关功能。以下是详细解释: // Server结构体 type Server struct { serviceMap sync.Map // map[string]*service 使用sync.Map存储已注册的服务(键为字符串,值为*service)。每个服务包含一个接收者对象及其可调用的方法。 reqLock sync.Mutex // protects freeReq 用于同步的锁对象,用于确保在处理请求时不会发生并发冲突。 freeReq *Request // 空闲请求对象队列,用于存储不再使用的请求对象。 respLock sync.Mutex // protects freeResp 用于保护和管理响应对象的互斥锁对象队列。 freeResp *Response // 用于保护和管理响应对象的空闲响应对象队列。 } // 返回一个新创建的Server实例。 func NewServer() *Server { return &Server{} } // 默认的Server实例, 通过NewServer()初始化。 var DefaultServer = NewServer() // isExportedOrBuiltinType函数, 用于检查给定的类型是否为导出的类型或内置类型。 func isExportedOrBuiltinType(t reflect.Type) bool { //对于指针类型,递归获取其元素类型。 for t.Kind() == reflect.Pointer { t = t.Elem() // 获取指针指向的元素类型 } // PkgPath will be non-empty even for an exported type, // so we need to check the type name as well. // 检查类型名是否以大写字母开头(表示导出),或其包路径是否为空(表示内置类型)。 return token.IsExported(t.Name()) || t.PkgPath() == "" } // Register publishes in the server the set of methods of the // receiver value that satisfy the following conditions: // - exported method of exported type // - two arguments, both of exported type // - the second argument is a pointer // - one return value, of type error // // It returns an error if the receiver is not an exported type or has // no suitable methods. It also logs the error using package log. // The client accesses each method using a string of the form "Type.Method", // where Type is the receiver's concrete type. // // Server的Register方法, 用于将接收者的方法注册到服务中。 // 注册一个接收者对象到服务器,使其提供的方法可供远程调用。 // 接收两个参数:rcvr(任何类型的接收者对象)和name(服务名称,若留空则使用接收者的类型名)。 // 调用register方法进行实际注册,传入useName=false表示使用接收者的类型名作为服务名。 func (server *Server) Register(rcvr any) error { return server.register(rcvr, "" , false) } // RegisterName is like Register but uses the provided name for the type // instead of the receiver's concrete type. // // RegisterName方法与Register类似,但使用提供的名称作为服务名称。 // 类似Register,但允许用户指定服务名称。 // 调用register方法进行实际注册,传入useName=true表示使用指定的name作为服务名。 func (server *Server) RegisterName(name string, rcvr any) error { return server.register(rcvr, name, true) } // logRegisterError specifies whether to log problems during method registration. // To debug registration, recompile the package with this set to true. // // logRegisterError控制是否在方法注册期间记录问题。 // 要调试注册,请将此设置为true重新编译该包。 const logRegisterError = false // Server的register方法: // 注册一个接收者对象到服务器,使其提供的方法可供远程调用。 func (server *Server) register(rcvr any, name string, useName bool) error { // 创建一个新的服务存根对象s。 s := new(service) // 获取接收者的类型。 s.typ = reflect.TypeOf(rcvr) // 获取接收者的值。 s.rcvr = reflect.ValueOf(rcvr) // 获取接收者的类型名。 sname := name // 如果服务名未提供(!useName),则从接收者类型中提取 if !useName { sname = reflect.Indirect(s.rcvr).Type().Name() } // 如果服务名不为空且不是导出的类型,则打印错误日志并返回错误。 if sname == "" { s := "rpc.Register: no service name for type " + s.typ.String() log.Print(s) return errors.New(s) } // 如果服务名已存在,则打印错误日志并返回错误。 if !useName && !token.IsExported(sname) { s := "rpc.Register: type " + sname + " is not exported" log.Print(s) return errors.New(s) } // 设置服务名称。 s.name = sname // Install the methods // 获取接收者的方法。 s.method = suitableMethods(s.typ, logRegisterError) // 如果接收者的方法为空,则打印错误日志并返回错误。 if len(s.method) == 0 { str := "" // To help the user, see if a pointer receiver would work. // 如果接收者的方法为空,则尝试使用指针接收者。 method := suitableMethods(reflect.PointerTo(s.typ), false) // 如果指针接收者的方法不为空,则打印警告日志。 if len(method) != 0 { str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)" } else { str = "rpc.Register: type " + sname + " has no exported methods of suitable type" } log.Print(str) return errors.New(str) } // 调用server.serviceMap.LoadOrStore方法将服务存根对象s存入服务映射map中。 if _, dup := server.serviceMap.LoadOrStore(sname, s); dup { // 如果服务名已存在,则打印错误日志并返回错误。 return errors.New( "rpc: service already defined: " + sname) } return nil } |
三、存根保存
suitableMethods
函数判断注册的服务类型是否符合规范,它会生成map[string]*methodType
。它会遍历类型中所有的方法, 依次判断方法能否被输出,取出其参数类型和返回类型,最后统一存储在返回值的map
中,实现代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | // suitableMethods returns suitable Rpc methods of typ. It will log // errors if logErr is true. // // suitableMethods函数返回typ类型的符合RPC规范的方法。 func suitableMethods(typ reflect.Type, logErr bool) map [string]*methodType { // 创建一个map,用于存储符合RPC规范的方法。 methods := make( map [string]*methodType) // 遍历typ的所有方法。 for m := 0; m < typ.NumMethod(); m++ { // 获取typ的方法。 method := typ.Method(m) // 获取方法的类型。 mtype := method.Type // 获取方法的名称。 mname := method.Name // Method must be exported. // 方法必须是导出的。 if !method.IsExported() { continue } // Method needs three ins: receiver, *args, *reply. // 方法应具有三个输入参数(接收者、*args、*reply)。 if mtype.NumIn() != 3 { if logErr { log.Printf( "rpc.Register: method %q has %d input parameters; needs exactly three\n" , mname, mtype.NumIn()) } continue } // First arg need not be a pointer. // 第一个参数(接收者)无特定要求。 argType := mtype.In(1) if !isExportedOrBuiltinType(argType) { if logErr { log.Printf( "rpc.Register: argument type of method %q is not exported: %q\n" , mname, argType) } continue } // Second arg must be a pointer. // 第二个参数(args)必须为指针类型。 replyType := mtype.In(2) if replyType.Kind() != reflect.Pointer { if logErr { log.Printf( "rpc.Register: reply type of method %q is not a pointer: %q\n" , mname, replyType) } continue } // Reply type 必须为导出的。 if !isExportedOrBuiltinType(replyType) { if logErr { log.Printf( "rpc.Register: reply type of method %q is not exported: %q\n" , mname, replyType) } continue } // 方法应有一个输出参数。 if mtype.NumOut() != 1 { // 如果方法没有输出参数,则打印错误日志并继续。 if logErr { log.Printf( "rpc.Register: method %q has %d output parameters; needs exactly one\n" , mname, mtype.NumOut()) } continue } // 输出参数必须为error类型。 if returnType := mtype.Out(0); returnType != typeOfError { if logErr { log.Printf( "rpc.Register: return type of method %q is %q, must be error\n" , mname, returnType) } continue } methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType} } return methods } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具