gRPC是谷歌开源的一个高性能RPC框架,支持多种语言。由于ONOS是运行在基于OSGi框架的karaf容器中,而当前官方提供的gRPC包没有OSGi化,因此若要在ONOS中实现基于gRPC的应用,需要考虑gRPC依赖的OSGi化问题。
1. gRPC依赖的OSGi化
默认情况下,OSGi框架下各个bundle是相互隔离的,不同bundle中的包不能直接引用,而依赖包的OSGi化,就是使该依赖以bundle的方式注册到OSGi框架里面并Export一些特定的package,当该bundle处于激活状态时,其它的bundle就能直接引用该bundle中的类,实现bundle的复用。
要对一个jar包OSGi化,一般有两种方式:
- 解压jar包,然后编辑jar包内的META-INF/MANIFEST.MF文件,配置Bundle-Name,Bundle-SymbolicName,Bundle-Version,Export-Package,Import-Package等属性,完成后重新生成jar包。
- 使用maven-bundle-plugin插件完成jar包的OSGi化。
对于运行在Karaf容器中的应用,可以使用wrap deployer来部署一个没有OSGi化的jar包。Wrap deployer会扫描jar包并自动配置jar包内的MANIFEST.MF文件,从而完成一个jar包的OSGi化。例如可以在feature.xml中添加如下配置来使用wrap deployer:
1 | <bundle>wrap:mvn:io.grpc/grpc-netty/${grpc.version}$Bundle-SymbolicName=io.grpc.grpc-netty&Bundle-Version=${grpc.package.version}</bundle> |
部署该bundle后,可以使用bundle:headers id
查看bundle中的OSGi header信息,也就是META-INF/MANIFEST.MF中配置的信息:
1 | onos> bundle:headers 175 |
其中,resolution:=optional表示解析过程中对应的package不是必须的,即当一个bundle无法import到这个package时,该bundle也可以安装并激活,这可以加速bundle的部署,这也是wrap deployer默认的设置方式。但需要注意的是,当我们使用该bundle过程中需要加载使用了该package的类时,就会产生类加载的异常,所以可以使用Import-Package显示设置需要import的package:1
<bundle>wrap:mvn:io.grpc/grpc-netty/${grpc.version}$Bundle-SymbolicName=io.grpc.grpc-netty&Bundle-Version=${grpc.package.version}&Import-Package=!io.netty.handler.proxy,*</bundle>
同样,可以使用bundle:headers id
查看该bundle的OSGi header信息: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
56onos> bundle:headers 175
io.grpc.grpc-netty (175)
------------------------
Built-JDK = 1.8.0_131
Implementation-Title = grpc-netty
Source-Compatibility = 1.6
Implementation-Version = 1.3.1
Created-By = 1.8.0_131 (Oracle Corporation)
Manifest-Version = 1.0
Bnd-LastModified = 1517399352696
Generated-By-Ops4j-Pax-From = wrap:mvn:io.grpc/grpc-netty/1.3.1$Bundle-SymbolicName=io.grpc.grpc-netty&Bundle-Version=1.3.1&Import-Package=!i
o.netty.handler.proxy,*Built-By = root
Target-Compatibility = 1.6
Tool = Bnd-2.3.0.201405100607
Bundle-ManifestVersion = 2
Bundle-SymbolicName = io.grpc.grpc-netty
Bundle-Version = 1.3.1
Bundle-Name = io.grpc.grpc-netty
Require-Capability =
osgi.ee;filter:=(&(osgi.ee=JavaSE)(version=1.6))
Export-Package =
io.grpc.netty;
uses:="io.grpc,
io.grpc.internal,
io.netty.buffer,
io.netty.channel,
io.netty.handler.codec.http2,
io.netty.handler.ssl,
io.netty.util,
javax.annotation"
Import-Package =
com.google.common.base,
com.google.common.io,
io.grpc,
io.grpc.internal,
io.netty.bootstrap,
io.netty.buffer,
io.netty.channel,
io.netty.channel.nio,
io.netty.channel.socket.nio,
io.netty.handler.codec,
io.netty.handler.codec.http,
io.netty.handler.codec.http2,
io.netty.handler.codec.http2.internal.hpack,
io.netty.handler.logging,
io.netty.handler.ssl,
io.netty.util,
io.netty.util.concurrent,
io.netty.util.internal,
io.netty.util.internal.logging,
javax.annotation,
javax.net.ssl
可以发现,io.netty.handler.proxy包已经不在Import-Package列表中,而其它的package都是在resolve该bundle过程中必须的。
然而,由于grpc中的grpc-core和grpc-context两个组件包含相同的包名(io.grpc),而OSGi是根据包名来查找并加载类的,这会导致其中一个包下的类无法加载,从而产生NoClassDefFoundError
的错误,这就是OSGi中的 split package问题。
解决思路就是重构包含相同包名的package并打包生成一个新的package,在这里可以使用maven-bundle-plugin插件的Export-Package功能,使用Export-Package后,对应的类也会拷贝到jar包中,这样就可以将相同包名中的类进行合并:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>3.0.1</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>
org.onosproject.grpc.demo,
io.grpc;version="${grpc.package.version}",
io.grpc.inprocess;version="${grpc.package.version}",
io.grpc.internal;version="${grpc.package.version}",
io.grpc.util;version="${grpc.package.version}"
</Export-Package>
<Import-Package>!com.google.errorprone.annotations,*</Import-Package>
</instructions>
</configuration>
</plugin>
基于wrap deployer和maven-bundle-plugin完成gRPC的OSGi化详细过程参考:onos-app-grpc-demo/features.xml和onos-app-grpc-demo/pom.xml。
另外,由于ONOS中使用了gRPC服务,因此ONOS也对gRPC包做了OSGi化处理,ONOS使用buck编译,gRPC的OSGi化参考:onos/incubator/grpc-dependencies/BUCK。默认情况下,ONOS没有激活gRPC依赖对应的bundle,因此若应用中需要使用gRPC服务,可以先激活ONOS中的应用:org.onosproject.protocols.grpc,这样gRPC依赖对应的bundle也将处于激活状态,然后你在自己的应用中就不用考虑gRPC依赖的OSGi化问题了。
2. 实现基于gRPC的应用
由于官方文档关于Hello World介绍已经很清楚了,本文还是以实现Hello World为例,介绍实现gRPC应用需要注意的问题。
导入gRPC依赖和相关插件
首先导入的gRPC依赖要和ONOS中的gRPC bundle的版本相同,另外,使用
build-helper-maven-plugin
插件添加${project.build.directory}/generated-sources/protobuf/
目录下由.proto文件生成的源码,同时开启maven-src-plugin插件的类扫描功能,使得该插件生成scr描述信息时能够找到${project.build.directory}/generated-sources/protobuf/
中生成的类,详细参考:pom.xml构建gRPC Server
官方使用ServerBuilder
构建HelloWorldServer
,这在Karaf容器中会产生如下的异常:Caused by: io.grpc.ManagedChannelProvider$ProviderNotFoundException: No functional channel service provider found. Try adding a dependency on the grpc-okhttp or grpc-netty artifact
这是由于OSGi框架下不同的bundle使用的类加载器是不同的,gRPC中的这部分代码没有考虑OSGi中的类加载器的特殊性问题,解决办法是使用
NettyServerBuilder
构建一个Server:1
2
3
4
5
6
7
8private void start() throws IOException {
server = NettyServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
log.info("Server started, listening on " + port);
}
详细代码参考:gRPC demo