GraphQL 优化:如何在 Go 中提高性能

阅读时长 17 分钟读完

GraphQL 是一种新型的 API 查询语言,它通过定义数据格式和查询语法来使客户端能够按需查询数据。GraphQL 的出现使得前端开发人员可以更方便地获取需要的数据,而无需受限于后端 API 的固有数据结构。然而,在面对大量数据和高并发请求时,GraphQL 的性能优化尤为重要。在本文中,我们将讨论如何在 Go 中优化 GraphQL 的性能。

构建 GraphQL Server

在优化性能之前,首先需要构建一个 GraphQL Server。在 Go 中,我们可以使用大量轻便的 GraphQL 实现,比如graphql-gogqlgengraphjin 等。本文将使用 graphql-go 作为 GraphQL 实现。

安装 graphql-go

首先需要安装 graphql-go。我们可以使用 go get 直接安装:

安装完成后,我们需要导入 graphql-go 包以便使用。

定义 Schema

定义 GraphQL API 的基石是 Schema,它描述了数据的类型和数据之间的关系。在 GraphQL 中,一个完整的 Schema 包括类型的定义和各种查询、变更和订阅。下面是一个简单的例子:

-- -------------------- ---- -------
---- ----- -
  ------ -------
-

---- -------- -
  ------------------- --------- -------
-

------ -
  ------ -----
  --------- --------
-

该 Schema 定义了一个查询 hello 和一个变更 addMessage。查询 hello 返回一个字符串,变更 addMessage 接受一个字符串并返回一个字符串。

编写 Resolver

Schema 只是 GraphQL API 的定义,而 Resolver 才是它的本质。它连接了 Schema 中的定义和最终的数据提供者。在 GraphQL 中,每个字段都需要对应一个 Resolver 函数,用来实际执行数据的获取过程。下面是对应上面 Schema 的 Resolver 实现:

Resolver 实现了 Query 和 Mutation 中各个字段的具体逻辑。Hello 返回字符串 "world",AddMessage 接受一个 message 字符串,将其保存到数据库中,并返回该字符串。需要注意的是 Resolver 函数必须匹配 Schema 定义中字段的名称和参数。

处理请求

最后一步是处理请求。GraphQL 的请求是一个 POST 请求,请求体格式如下:

其中 query 是必须提供的,它包括了需要查询或变更的字段和它们的参数。variables 是一些可选的变量,operationName 是可选的操作名称。

在 Go 中,我们可以使用 http 包来处理请求:

-- -------------------- ---- -------
---- ------ -
    ------- - -- ---------------------------------------
        ------ ---------
        --------- ------------
    --

    --------------------------- ------ -------------------- - -------------- -
        ------ -- --------------------------
            -------        -------
            -------------- ---------------------------
        --
        ---------------------------------
    --

    ---------------------------- ----
-

该代码片段中,我们首先创建了一个 Schema 并将其传递给 Do 函数进行请求处理。在处理请求时,我们从 URL 中获取 query 参数,调用 graphql.Do 函数返回结果。我们可以将结果序列化为 JSON 并将其作为响应写回客户端。

现在我们已经构建了一个简单的 GraphQL Server,下面我们将讨论如何通过优化来提高其性能。

使用 DataLoader

在 Graph 世界中,一个常见的问题是 N+1 查询。这是指向数据库发出 N 次查询以获取 N 条记录以及该记录关联的数据的问题,这对性能产生了巨大影响,因为它在不需要的情况下也会查询大量数据。为了缓解这个问题,我们可以使用 DataLoader。

DataLoader 是一个支持批处理的数据加载器。它可以缓存多个查询,合并它们并检索缓存中的批处理结果。这可以避免重复查询,提高性能。下面是如何使用 DataLoader:

-- -------------------- ---- -------
------ -- ------------------------------------- ---------------- -------------------- -
    --- ------- --------------------
    -- ----- ---- ----------- --- --- ----- ----
    -- --- - --- -- ------- ----- --- ------- -- ---- ---
    ------- -- -------------------
    --- -- --- -- ----- ---- -
        ------ -- ---------
        -- ----- -- -- ---------------- -- -
            ------- - --------------- ------------------------ ------
            --------
        -
        ----- --- -- -----------------
        -- --- -- --- -
            ------- - --------------- ------------------------- -----
            --------
        -
        ------- - --------------- ------------------------ ------
        --------------- - ----
    -
    ------ -------
--

---- ----------- ---------- ------- --- ------- -- --------- ------ -
    -- ------- --------- -- --- ----- -----
    ---- -- --------------------- --------------
    --- -- -- -- ----- -------- -
        ------- - --
    -

    -- ----- --- ----- ----- --- -----------
    ------- -- ------------------------------------ -----
    ----- -- ------------- -------------
    --- -- ------ -- ----- ------- -
        -- ------------ -- --- -
            ------ ---- ------------
        -
        -------- - -------------------
    -
    ------ ------ ---
-

该代码片段中,我们首先创建了一个 DataLoader 实例,这个实例可以接受一个 keys 数组,并返回对应的结果数组。当 keys 数组中的元素数量较少时,DataLoader 会缓存并减少请求。当 keys 数组中的元素数量较多时,DataLoader 会在需要时自动批处理请求。

接下来,我们编写了一个 Resolver 函数 Users 用来获取多个用户,它接受一个 IDs 参数表示要获取的用户 ID 列表。在 Resolver 中,我们使用 DataLoader 获取用户对象。我们首先将参数 IDs 转换为 DataLoader 要求的 keys 类型,然后使用 loader.Batch 函数获取结果。注意,我们在将结果返回给客户端之前,还需要将结果中的 Error 和 Data 部分分开处理。

使用缓存

另一个有效的性能优化是使用缓存。缓存可以避免重复计算,从而减少数据库请求。

Go 中有很多缓存可供选择,例如RedisMemcachedgroupcachecache2go 等。本文将使用 cache2go 作为缓存实现。

安装 cache2go

我们可以使用 go get 直接安装 cache2go:

安装完成后,我们需要导入 cache2go 包以便使用。

实现缓存

下面是如何使用 cache2go 实现缓存:

-- -------------------- ---- -------
-- ------ - --- ---------- ---- - ------- ---- -- - ------- ------
----- -- ------------------------------ --------------

---- ------------ ---- ------- ------ -
    -- --- --- ----- -- ----- -------- ---------
    -- -- --- -- ---------------- --- -- --- -
        ------ ----------------- ---
    -

    -- ------- --- -------- ------
    ----- --- -- ------------------
    -- --- -- --- -
        ------ ---- ---
    -

    -- ----- --- ------ -- --- ------
    ------------- -------------- -----

    ------ ----- ---
-

---- ----------- --------- ------- -- ----- -- ------- ------ -
    ----- --- -- -----------------------
    -- --- -- --- -
        ------ ---- ---
    -
    ------ ----- ---
-

该代码片段中,我们首先创建了一个缓存,它能够存储 1 百万个项目,并且在 5 分钟内未被访问时自动清除。接下来,我们创建了一个 fetchUser 函数,它首先在缓存中查找用户,如果找到了,则直接返回用户。如果没有找到,则执行数据库查询,将其结果存储到缓存中,并返回结果。

在我们的 Resolver 函数 User 中,我们只需要使用 fetchUser 函数就可以得到用户对象。如果 cache2go 中已经存在了缓存,那么将直接返回无需重新查询数据库。

合适的查询字段

在使用 GraphQL 时,合适的查询字段可以很大程度上提高性能。因为 GraphQL API 允许客户端获取应用程序的所有数据,所以我们需要谨慎地选择要公开的数据和查询字段。

在定义 GraphQL Schema 时,我们需要确保每个字段都是必要的,并在可能的情况下为其添加限制器。这可以避免在不需要的情况下返回许多数据。另一个常见的优化是将常用和热门字段添加到接口和类型中,以减少额外和重复的查询。

减少嵌套

GraphQL 允许嵌套查询数据,这可能会导致太多的查询和反序列化操作。当我们使用 GraphQL 时,我们应该考虑使用批处理和深度查询来避免多次查询一个 API。

在定义 Schema 时,我们需要确保最大限度地减少嵌套的查询来最小化数据库访问次数。另一方面,如果我们得到了嵌套查询,我们也可以使用强大的工具来分析和重组查询。

结论

在 Go 中优化 GraphQL 查询的性能是一个主要问题。我们可以使用 DataLoader、缓存、限制字段和减少嵌套等多种方法来优化查询。在选择优化方法时,我们需要考虑应用程序的特定处理和性能要求。最终目标是使 GraphQL 查询快、可靠和可伸缩。下面是所有相关代码的实现:

-- -------------------- ---- -------
------- ----

------ -
----------------
-----------
-------

--------------------------------------
-----------------------------
------------------------------
---------------------------------------
-

---- ---- ------ -
---        ---
-----      ------
------     ------
---------- ---------
---------- ---------
-

---- ----- ------ -
-

---- -- ------- ------- ------ -
------- -------
-

---- -------- --------- ------- -- ----- -- ------- ------ -
------ --- -- -----------------------
--- --- -- --- -
-------- ---- ---
--
------- ----- ---
-

---- -------- ---------- ------- --- ------- -- --------- ------ -
--- ------- --------- -- --- ----- -----
----- -- --------------------- --------------
---- -- -- -- ----- -------- -
--------- - --
--

--- ----- --- ----- ----- --- -----------
-------- -- ------------------------------------ -----
------ -- ------------- -------------
---- -- ------ -- ----- ------- -
---- ------------ -- --- -
--------- ---- ------------
---
---------- - -------------------
--
------- ------ ---
-

---- ----------- --------------- ------- ------- ------ -- -------- ------ -
--- ---- --- ------- -- - ---------
------- ------------- ---
-

---- ------------ ---- ------- ------ -
--- --- --- ----- -- ----- -------- ---------
--- -- --- -- ---------------- --- -- --- -
-------- ----------------- ---
--

--- ------- --- -------- ------
------ --- -- ------------------
--- --- -- --- -
-------- ---- ---
--

--- ----- --- ------ -- --- ------
-------------- -------------- -----

------- ----- ---
-

--- -
------  --------------------
------- ------------------
-

---- ------ -
--- ------ - --- ---------- ---- - ------- ---- -- - ------- ------
------ - -------------------------------- --------------

--- ------ - ---------- -- ----- -------- ---------
------- - ------------------------------------- ---------------- -------------------- -
----- ------- --------------------
---- ----- ---- ----------- --- --- ----- ----
---- --- - --- -- ------- ----- --- ------- -- ---- ---
--------- -- -------------------
----- -- --- -- ----- ---- -
--------- -- ---------
----- ----- -- -- ---------------- -- -
----------- - --------------- ------------------------ ------
------------
----
-------- --- -- -----------------
----- --- -- --- -
----------- - --------------- ------------------------- -----
------------
----
---------- - --------------- ------------------------ ------
------------------ - ----
---
-------- -------
---
-

---- ------ -
--- ------ ------- -------
------------- -- -
------ ----- -
--------- -------
----------- ------ ----
------------- --------- ------
---

------ ---- -
------ ----
-------- -------
--------- -------
------------- -------
------------- -------
---
--

--- ------ -------- --- -------
--------- -- ----------
-------- --- -- --------------------------------- ----------
--- --- -- --- -
------------
--

--- ------ -- ---- ------- --- ------- ---------
------------------------ ---------------------- --------

--- ----- --- ---- -------
--- --- -- ---------------------------- ----- --- -- --- -
------------
--
-

参考资料:

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/6729a30e2e7021665e252a8c

纠错
反馈