Skywalking源码分析之OAP

概述

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",
        {
            ...

你可能感兴趣的:(Skywalking源码分析之OAP)