blog icon indicating copy to clipboard operation
blog copied to clipboard

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

Open geektutu opened this issue 5 years ago • 33 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 语言结构体通过反射映射为服务。

geektutu avatar Oct 08 '20 02:10 geektutu

感谢博主的详细博客 在用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 avatar Nov 18 '20 03:11 IcePigZDB

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

geektutu avatar Nov 22 '20 14:11 geektutu

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

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

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

limaoxiaoer avatar Nov 25 '20 09:11 limaoxiaoer

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

geektutu avatar Nov 25 '20 10:11 geektutu

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

Howie59 avatar Nov 29 '20 07:11 Howie59

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

geektutu avatar Nov 29 '20 14:11 geektutu

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?

liyuxuan89 avatar Apr 14 '21 11:04 liyuxuan89

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

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

shawn0762 avatar May 01 '21 13:05 shawn0762

问下 rcvr是哪些单词的简写

devhg avatar May 04 '21 12:05 devhg

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

receiver

shawn0762 avatar May 05 '21 02:05 shawn0762

@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。

shengxiang19 avatar May 29 '21 05:05 shengxiang19

// 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 avatar Jul 12 '21 09:07 chenshuidejidan

@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.(官方文档)

lwb0214 avatar Jul 22 '21 11:07 lwb0214

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

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

raykn avatar Jul 26 '21 07:07 raykn

@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})) 就成功了

zwkdhm avatar Aug 13 '21 07:08 zwkdhm

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?

WeiguoEric avatar Aug 23 '21 16:08 WeiguoEric

	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

不知道为啥

qiu-peng20 avatar Aug 30 '21 00:08 qiu-peng20

@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})) 就成功了

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

qiu-peng20 avatar Aug 30 '21 00:08 qiu-peng20

@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
}

cyj19 avatar Sep 13 '21 07:09 cyj19

在用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, ","))
    }
}

Roderland avatar Jan 08 '22 08:01 Roderland

@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(),就会不可寻址,赋值失败

609412635 avatar Jan 11 '22 07:01 609412635

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

eurus10 avatar Jun 24 '22 07:06 eurus10

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

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

vvzhihao avatar Jul 14 '22 13:07 vvzhihao

@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初始化

niconical avatar Jul 29 '22 09:07 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.name执行在先

niconical avatar Jul 29 '22 09:07 niconical

@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)方法,提取实例对象再获取名称

ShiMaRing avatar Jul 30 '22 15:07 ShiMaRing

太强了

huty1998 avatar Jan 29 '23 07:01 huty1998

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

SCUTking avatar Dec 01 '23 02:12 SCUTking

@vvzhihao

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

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

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

SCUTking avatar Dec 01 '23 02:12 SCUTking

@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就是用于判断服务之前是否存在的

SCUTking avatar Dec 01 '23 02:12 SCUTking