fengjiachun/Jupiter

关于重载方法匹配的可优化空间

uvletter opened this issue · 13 comments

根据我看到的代码目前的重载方法匹配是在server端运行时进行的,但是这会带来额外的overhead,而且代码实现偏复杂,可能有隐藏bug。

所以我想到的一个思路是,这个重载方法匹配可否在编译期由编译器帮我们完成呢?每一个方法有一个唯一ID(如根据包+类+方法名+函数列表唯一限定),client在调用stub时已通过编译器确定了这个最佳匹配的ID,而发送的message实际包含这个方法ID和参数,这样便可以把匹配的运行时开销移到编译期,而且代码实现会比自己去找匹配方法简单。

这是我的一个初步想法,没考虑实现细节,不知道你觉得怎么样。

@uvletter 感谢您的建议, 我觉得这个思路非常不错, 我会按照这个思路尝试一下

关于ID的生成机制, 不知道你有没有什么思路? @uvletter

我最初的设想是按C++的方式对重载方法重命名,比如一个void foo(int, int),编译后实际的函数名是_foo$int$int(各编译器的处理方式不尽相同,不过基本原理是按函数名和参数类型重命名),如果需要考虑包那就把包名简单加在类型名前面,如MyClass -> xx.xx.Myclass,不过这样函数ID会变长,不知道对传输和哈希会有多大影响。

对动态代理这一块,我也不是很熟(初学者一枚),所以可能帮不上什么忙⊙o⊙

@uvletter 这个方式生成ID还是太大了, 我希望找到一个更小的ID生成方式

有个思路,在注册中心定义一个查找表,provider注册接口时在查找表上新增条目,并返回一个对应方法ID(可以简单递增),consumer第一次调用时先去注册中心查询这个条目,得到方法ID,然后向provider调用方法。不过这样方案会变得复杂一些,注册中心除了负责注册主机还要注册接口。

@uvletter 恩, 我期望的就是id仅仅是一个简单递增, 那么一个short类型足够,
我最初是设想consumer/provider两端各自按照既定规则各自生成id, 但这样做会有一个问题无法解决:
比如provider是更新版本增加了一个方法, consumer仍旧是旧版本, 那么各自生成的id可能会对应不上

你提供的思路把id信息放入注册中心的确能解决我上面说的问题, 不过确实也带来了比较高的复杂度

方便介绍下ServiceMetadata里的三元组吗,版本还比较好理解,但组别和名称就不怎么理解了,可能我没遇到相应的应用场景,所以对它们的意义用途理解不是很深。

@uvletter
group:服务组别(有默认值, 通常不用设置, 当服务名称和别人冲突时可以利用group来彼此区分隔离)
serviceProviderName:服务名称(默认为服务接口的类全限定名称)
version:服务版本号, 通常在接口不兼容时版本号才需要升级

以上三个字段用于确定唯一的服务对象

哈哈,想到一块去了,turbo 现在是这么做的。
我刚开始也是思考放到注册中心的,不过担心可能对注册中心造成比较大的性能冲击放弃掉了。
最后的解决方案是:
1.建立连接
2.客户端调用一个特别的方法获取服务端的 serviceId 列表
3.客户端存储每个服务端的 method -> serviceId 列表
4.客户端负载均衡找到一个 Channel,确定 server,确定对应的 serviceId,然后调用

@hank-whu 恩, 昨天看了你的文章, 注意到了turbo的做法, 但我觉得还是有一点侵入性, 还是期望会有个更完美的方案, 哈哈

jupiter的目前做法是根据java语言规范中方法调用的静态分派规则在服务端进行匹配, 这个会有一点开销, 不过相比参数类型在网络层的传输, 这个开销暂时看还是比较值得的

嗯,turbo 是一次性能优化实验,用了很多奇巧淫技。
期待你的解决方案。

@hank-whu turbo-rpc看了下代码,命名规范,可读性更好。 而且看到了netty中很多组件的影子,非常值得一看。但是如果能像jupiter一样,有个总体的pdf讲解,就更好了。