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的定义:
// 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 函数:
func Register(rcvr any) error {
return DefaultServer.Register(rcvr)
}
Register函数接收一个interface{}类型的参数rcvr,
这个参数代表要注册的RPC服务类型。函数内部直接调用了DefaultServer的Register方法,
将服务注册到默认的RPC服务器上。如果注册成功, 则返回nil, 如果注册失败, 则返回一个错误。
需要注意的是,
Register函数使用接收者的具体类型作为服务的名称。这意味着,
如果你注册了同一个类型的多个接收者, 后面注册的接收者会覆盖先前注册的服务。
2、RegisterName 函数:
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,具体代码如下所示:
// 这段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中,实现代码如下所示:
// 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
}

浙公网安备 33010602011771号