geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。

Home Page:https://geektutu.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

动手写RPC框架 - GeeRPC第三天 服务注册(service register) | 极客兔兔

geektutu opened this issue · comments

https://geektutu.com/post/geerpc-day3.html

7天用 Go语言/golang 从零实现 RPC 框架 GeeRPC 教程(7 days implement golang remote procedure call framework from scratch tutorial),动手写 RPC 框架,参照 golang 标准库 net/rpc 的实现,实现了服务端(server)、支持异步和并发的客户端(client)、消息编码与解码(message encoding and decoding)、服务注册(service register)、支持 TCP/Unix/HTTP 等多种传输协议。第三天实现了服务注册,即将 Go 语言结构体通过反射映射为服务。

感谢博主的详细博客
在用wg举例的main中returns的容量好像应该是method.Type.NumOut()

        argv := make([]string, 0, method.Type.NumIn())
        returns := make([]string, 0, method.Type.NumIn())
        // j 从 1 开始,第 0 个入参是 wg 自己。
		for j := 1; j < method.Type.NumIn(); j++ {
			argv = append(argv, method.Type.In(j).Name())
		}
		for j := 0; j < method.Type.NumOut(); j++ {
			returns = append(returns, method.Type.Out(j).Name())
		}

@IcePigZDB 感谢指出问题,已经修复~

returnValues := f.Call([]reflect.Value{s.rcvr, argv, replyv})

call的参数中为什么要加 s.rcvr?

这个函数不是只需要Sum(args Args, reply *int)两个参数么

@limaoxiaoer 第 0 个参数是对象自己,正常调用是 A.func(argv1, argv2),反射的时候就是 Call(A, argv1, argv2)

commented

想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

@Howie59 newService 之后解析出结构体的名称,也就是 serviceMap 中的 key。即使有参数变化,只要 key 不变,serviceMap 也是不会更新的。这么实现是为了简单,判断 serviceMap 是否有,也得通过反射才能得到。Register 调用次数理论上很少,所以这一块没考虑性能优化。

func (m *methodType) newArgv() reflect.Value {
var argv reflect.Value
// arg may be a pointer type, or a value type
if m.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(m.ArgType.Elem())
} else {
argv = reflect.New(m.ArgType).Elem()
}
return argv
}
这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

NewService方法里面:
s.rcvr = reflect.ValueOf(rcvr)
s.name = reflect.Indirect(s.rcvr).Type().Name()
s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

commented

问下 rcvr是哪些单词的简写

@devhg
问下 rcvr是哪些单词的简写

receiver

@shawn0762
NewService方法里面:
s.rcvr = reflect.ValueOf(rcvr)
s.name = reflect.Indirect(s.rcvr).Type().Name()
s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

因为rcvr可能是一个指针,通过Indirect可以返回它指向的对象的类型。不然的话,它的type就是reflect.Ptr。

// make sure that argvi is a pointer, ReadBody need a pointer as parameter
	argvi := req.argv.Interface()
	if req.argv.Type().Kind() != reflect.Ptr {
		argvi = req.argv.Addr().Interface()
	}

这里为什么是 !=reflect.Ptr 呢?不该是等于指针类型的时候取addr,拿到指针类型的interface?

@chenshuidejidan

// make sure that argvi is a pointer, ReadBody need a pointer as parameter
	argvi := req.argv.Interface()
	if req.argv.Type().Kind() != reflect.Ptr {
		argvi = req.argv.Addr().Interface()
	}

这里为什么是 !=reflect.Ptr 呢?不该是等于指针类型的时候取addr,拿到指针类型的interface?

Addr returns a pointer value representing the address of v.(官方文档)

commented

@Howie59
想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

应该也不需要判断吧,即便存在也是要覆盖,不存在则是新增,验证是不是正确的Method又是一定要走的逻辑,所以判断存不存在都可以

@liyuxuan89
func (m *methodType) newArgv() reflect.Value {
var argv reflect.Value
// arg may be a pointer type, or a value type
if m.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(m.ArgType.Elem())
} else {
argv = reflect.New(m.ArgType).Elem()
}
return argv
}
这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

好像确实会,而且加了之后可以set成功,但是f.call会panic,说参数类型不对
不加Elem()的话如果是指针型参数,我在测试用例中改成
argv.Elem().Set(reflect.ValueOf(Args{Num1: 1, Num2: 3}))
就成功了

func (m *methodType) newReplyv() reflect.Value {
	// reply must be a pointer type
	replyv := reflect.New(m.ReplyType.Elem())
	switch m.ReplyType.Elem().Kind() {
	case reflect.Map:
		replyv.Elem().Set(reflect.MakeMap(m.ReplyType.Elem()))
	case reflect.Slice:
		replyv.Elem().Set(reflect.MakeSlice(m.ReplyType.Elem(), 0, 0))
	}
	return replyv
}

这里为什么要区分reflect.Map和reflect.Slices?

	argvi := req.argv.Interface()
	if req.argv.Type().Kind() != reflect.Ptr {
		argvi = req.argv.Addr().Interface()
	}

	if err = cc.ReadBody(argvi); err != nil {
		log.Println("rpc server : read argv err", err)
		return req, err
	}

我的这一步报错了:read argv err gob: type mismatch in decoder: want struct type main.Args; got non-struct

不知道为啥

@zwkdhm

@liyuxuan89
func (m *methodType) newArgv() reflect.Value {
var argv reflect.Value
// arg may be a pointer type, or a value type
if m.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(m.ArgType.Elem())
} else {
argv = reflect.New(m.ArgType).Elem()
}
return argv
}
这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

好像确实会,而且加了之后可以set成功,但是f.call会panic,说参数类型不对
不加Elem()的话如果是指针型参数,我在测试用例中改成
argv.Elem().Set(reflect.ValueOf(Args{Num1: 1, Num2: 3}))
就成功了

你这个地方的参数类型不对是怎么解的

commented

@Howie59
想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

应该是参考了标准库rpc包的源码的写法

func (server *Server) register(rcvr interface{}, name string, useName bool) error {
	s := new(service)
	s.typ = reflect.TypeOf(rcvr)
	s.rcvr = reflect.ValueOf(rcvr)
	sname := reflect.Indirect(s.rcvr).Type().Name()
	...

	if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
		return errors.New("rpc: service already defined: " + sname)
	}
	return nil
}

在用wg举例的main中打印参数类型method.Type.In(j).Name()如果是参数是interface{}或chan这种会返回空字符串,用method.Type.In(j).String()会不会好一点

func main() {
	var wg sync.WaitGroup
	typ := reflect.TypeOf(&wg)
	for i := 0; i < typ.NumMethod(); i++ {
		method := typ.Method(i)
		argv := make([]string, 0, method.Type.NumIn())
		returns := make([]string, 0, method.Type.NumOut())
		// j 从 1 开始,第 0 个入参是 wg 自己。
		for j := 1; j < method.Type.NumIn(); j++ {
			argv = append(argv, method.Type.In(j).Name())
		}
		for j := 0; j < method.Type.NumOut(); j++ {
			returns = append(returns, method.Type.Out(j).Name())
		}
		log.Printf("func (w *%s) %s(%s) %s",
			typ.Elem().Name(),
			method.Name,
			strings.Join(argv, ","),
			strings.Join(returns, ","))
    }
}

@liyuxuan89
func (m *methodType) newArgv() reflect.Value {
var argv reflect.Value
// arg may be a pointer type, or a value type
if m.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(m.ArgType.Elem())
} else {
argv = reflect.New(m.ArgType).Elem()
}
return argv
}
这一个部分,Kind是Ptr时没有Elem(),Set不会Panic?

我自己测试了下确实会出现,可以用canSet()判断返回了是false ,不加Elem(),就会不可寻址,赋值失败

我想请教一下为什么Server注册服务的时候要用sync.Map存储呢
RPC服务器实际运行起来后对这个map不应该只有读请求嘛,我感觉好像可以直接用普通的map存储啊

@eurus10
我想请教一下为什么Server注册服务的时候要用sync.Map存储呢
RPC服务器实际运行起来后对这个map不应该只有读请求嘛,我感觉好像可以直接用普通的map存储啊

我的观点是:map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。
要是注册新的服务又同时读取用map会发生错误

commented

@WeiguoEric

func (m *methodType) newReplyv() reflect.Value {
	// reply must be a pointer type
	replyv := reflect.New(m.ReplyType.Elem())
	switch m.ReplyType.Elem().Kind() {
	case reflect.Map:
		replyv.Elem().Set(reflect.MakeMap(m.ReplyType.Elem()))
	case reflect.Slice:
		replyv.Elem().Set(reflect.MakeSlice(m.ReplyType.Elem(), 0, 0))
	}
	return replyv
}

这里为什么要区分reflect.Map和reflect.Slices?

个人理解是给map和slice初始化

commented

@shawn0762
NewService方法里面:
s.rcvr = reflect.ValueOf(rcvr)
s.name = reflect.Indirect(s.rcvr).Type().Name()
s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

s.name执行在先

@niconical

@shawn0762
NewService方法里面:
s.rcvr = reflect.ValueOf(rcvr)
s.name = reflect.Indirect(s.rcvr).Type().Name()
s.typ = reflect.TypeOf(rcvr)

s.name为什么不用s.typ.Name()呢?请问这两者有什么区别

原因在于无法确定用户传入的s.rcvr类型为结构体还是为指针,如果用户传入的为指针的话,直接采用s.typ.Name()输出的为空字符串

log.Println(" struct name: " + reflect.TypeOf(foo).Name())//输出Foo

log.Println("pointer name: " + reflect.TypeOf(&foo).Name())//输出空串

因此需要采用reflect.Indirect(s.rcvr)方法,提取实例对象再获取名称

太强了

这个应该只是一个单机版的服务注册

@vvzhihao

@eurus10
我想请教一下为什么Server注册服务的时候要用sync.Map存储呢
RPC服务器实际运行起来后对这个map不应该只有读请求嘛,我感觉好像可以直接用普通的map存储啊

我的观点是:map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。
要是注册新的服务又同时读取用map会发生错误

咋在程序运行时注册新的服务呢?

@cyj19

@Howie59
想问下register函数中为什么不先判断serviceMap中有没有呢?先newService是有可能会有变化的参数或者是变量吗?

应该是参考了标准库rpc包的源码的写法

func (server *Server) register(rcvr interface{}, name string, useName bool) error {
	s := new(service)
	s.typ = reflect.TypeOf(rcvr)
	s.rcvr = reflect.ValueOf(rcvr)
	sname := reflect.Indirect(s.rcvr).Type().Name()
	...

	if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
		return errors.New("rpc: service already defined: " + sname)
	}
	return nil
}

因为要获取服务的名称去map映射获取就得获取一个service实例,dup就是用于判断服务之前是否存在的