概述
OAP是skywalking的服务端,负责接受探针上报的数据、提供数据分析的接口等,本文重点描述OAP如何为Web展示界面提供接口
armeria
skywalking 使用微框架armeria处理http请求及路由转发
armeria顶替的是tomcat或者spring-boot-web的功能
构建armeria的位置:HTTPServer
, 初始化代码如下:
public void initialize() {
final String contextPath = StringUtils.stripEnd(config.getContextPath(), "/");
// 初始化Armeria
sb = com.linecorp.armeria.server.Server
.builder()
.serviceUnder(contextPath + "/docs", DocService.builder().build())
.workerGroup(config.getMaxThreads())
.http(new InetSocketAddress( // http请求
config.getHost(), // Socket host
config.getPort() // Socket port
))
整个web项目只提供一个接口,即Post graphql
, 并把请求转至graphql服务, 代码在GraphQLQueryHandler
:
@Blocking
@Post("/graphql")
public HttpResponse graphql(
final ServiceRequestContext ctx,
final HttpRequest req) throws Exception {
return graphqlService.serve(ctx, req);
}
graphql
graphql是一种用于API的查询语言,skywalking使用它实现同一接口获取不同资源,根据参数描述所需资源的功能
graphql顶替的是restful协议
传统项目使用spring-web实现restful,skywalking使用graphql-java
实现graphql
语言
graphql使用schema文件(文件后缀为.graphqls
)来进行资源描述,文件的内容使用的就是graphql语言
后端开发人员使用schema文件描述系统提供的资源、数据,并描述出资源接受的参数类型即返回的数据类型、字段描述,类似于接口文档(使用graphql就不需要接口文档了)
前端人员通过查看schema文件就知道后端提供了什么资源,可以通过唯一的一个请求接口传递不同的参数描述需要什么资源,需要返回值中的哪些字段等
一个简单的schema文件例子
type User {
id: Long!
name: String!
age: Int
role: Role
}
type Role {
id: Long!
name: String!
}
extend type Query {
listUsers(name: String): [User!]!
listRoles(name: String): [Role!]!
}
该文件使用graphql语言编写,意思是后端提供两个列表查询资源,一个是用户列表:listUsers,一个是角色列表:listRoles
其中用户列表接受name参数,类型是String,返回是一个User数组,[]代表数组,且数组元素和数组本身都不为空, !代表不为空,其中User对象的字段在type User
定义,包含id,name,age,role四个字段,其中id和name不为空,role字段返回的是一个type Role
对象
此时即可发起如下请求来获取用户资源:
{
"query": "query getUsers($name: String) {
users : listUsers(name: $name) {
value: name
role
}
}",
"variables": {
"name": "wmf"
}
}
解释
1.query
代表请求描述
-
query getUsers($name: String)
这只是对请求的一个描述,其中getUsers应该是请求的名字,($name: String)是对资源参数一个描述,需要与schema文件一致 -
users: listUsers
指定请求资源,对应schema文件中的listUsers
,users
对请求数组的key起的别名,默认就是资源名 -
value: name
,role
,描述请求只要name、role两个字段,其中name重命名为value
2.variables
即变量传参
-
name
: 即变量名,后面的value就是参数值
java
skywalking 使用graphql-java
完成graphql到方法的映射,相关代码在server-query-plugin.query-graphql-plugin
模块下,使用如下:
schema文件
文件位置在 server-query-plugin\query-graphql-plugin\resources\query-protocol
文件夹下
文件以.graphqls结尾,如
- common.graphqls 通用资源描述
- metadata-v2.graphqls 元数据资源描述
- trace.graphqls 链路跟踪资源描述
与java方法对应
每个schema文件描述了多个资源Query,如:listServices(获取所有服务), findService(搜索服务),这些资源与实际的java代码相对应,即请求指定该资源java就执行对应方法返回数据
Query资源的映射代码写在GraphQLQueryProvider
中,通过schema文件文件名与java类进行匹配(方法自动按名称匹配), 如下
schemaBuilder.file("query-protocol/common.graphqls")
.resolvers(new Query(), new Mutation(), new HealthQuery(getManager()))
...省略
.file("query-protocol/metadata-v2.graphqls")
.resolvers(new MetadataQueryV2(getManager()))
...
.scalars(ExtendedScalars.GraphQLLong);
代码中metadata-v2.graphqls文件对应到MetadataQueryV2
类,metadata-v2.graphqls包含如下描述
extend type Query {
listLayers: [String!]!
listServices(layer: String!): [Service!]!
...
两个资源对应MetadataQueryV2类的如下方法(名称匹配)
public Set listLayers() throws IOException {
return getMetadataQueryService().listLayers();
}
public List listServices(final String layer) throws IOException {
return getMetadataQueryService().listServices(layer, null);
}
skywalking通过graphql-java
完成schema与java类的自动匹配
架构
以获取服务列表为例:
Query层(server-query-plugin)
上面这些与schema文件对应的类,如MetadataQueryV2
,就类似传统MVC框架中的Controller
Service层(server-core)
Query层接受请求后会转交给Service层处理
public List listServices(final String layer) throws IOException {
return getMetadataQueryService().listServices(layer, null);
}
Dao层(server-storage-plugin)
Service层通过调用Dao层完成对数据库的操作,如下
public List listServices(final String layer, final String group) throws IOException {
return this.combineServices(getMetadataQueryDAO().listServices(layer, group));
}
Dao层最终获取数据的方式就是拼装sql去调用数据库(没有使用ORM框架),链接池使用了Hikari
容器
skywalking没有使用spring容器,而是自己实现了一个简单容器,实际上就是一个HashMap作为对象容器:ModuleManager
,调用它的provider()方法可获取ModuleProvider
,容器就定义在这个对象里,如下
private final Map, Service> services = new HashMap<>();
当想获取某个对象时,与spring一样不需new,而是调用调用ModuleProvider的getService(Class)方法,如下
this.metadataQueryDAO = moduleManager.find(StorageModule.NAME).provider().getService(IMetadataQueryDAO.class);
Query>Service>Dao层就是通过容器来获取下层对象,初始化时手动注入到本对象的属性中,以便请求到达时并执行下层方法
vue
前端基于VUE3,TS,请求的格式即graphql语言,没有使用特殊工具,只是简单的使用axios发起请求
但graphql的相关工具自己也有封装,即graphql类:index.ts
class Graphql {
private queryData = "";
public query(queryData: string) {
this.queryData = queryData;
return this;
}
public params(variablesData: unknown): AxiosPromise {
return axios // 使用axios
.post(
"/graphql",
{
...