Kong 插件开发 - 缓存自定义实体

缓存自定义实体

如果有自定义的实体,可以通过 database_cache这个依赖把实体缓存到内存中。

local cache = require "kong.tools.database_cache"

这个模块暴露出来的方法如下:

  • ok, err = cache.set(key, value)

    根据指定的key把一个lua对象缓存到内存中。这个value可以是任何lua的数据类型,包括table。返回true或者 false和操作错误引起的 err。

  • value = cache.get(key)

    根据制定的 key 从被存储的 Lua 对象中检索

  • cache.delete(key)

    根据指定的 key 删除缓存的对象

  • newvalue, err = cache.incr(key, amount)

    将指定键中存储的数字增加指定的单位数量。该数字需要已经存在于缓存中,否则将返回错误。如果成功,则返回新的递增值,否则返回错误

  • value = cache.get_or_set(key, function)

    这是一个实用方法,它使用指定的键检索对象,但是如果对象是nil,那么将执行传递的函数,它的返回值将用于存储指定键的对象。这有效地确保对象仅从数据存储加载一次,因为每个其他调用将从内存中高速缓存加载对象

回过头来看我们authentication插件这个例子,用一个指定的api-key 来检索证书,写的代码如下:

-- access.lua

local credential

-- Retrieve the apikey from the request querystring
local apikey = request.get_uri_args().apikey
if apikey then -- If the apikey has been passed, we can check if it exists

  -- We are using cache.get_or_set to first check if the apikey has been already stored
  -- into the in-memory cache at the key: "apikeys."..apikey
  -- If it's not, then we lookup the datastore and return the credential object. Internally
  -- cache.get_or_set will save the value in-memory, and then return the credential.
  credential = cache.get_or_set("apikeys."..apikey, function()
    local apikeys, err = dao.apikeys:find_by_keys({key = apikey}) -- Lookup in the datastore
    if err then
      return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
    elseif #apikeys == 1 then
      return apikeys[1] -- Return the credential (this will be also stored in-memory)
    end
  end)
end

if not credential then -- If the credential couldn't be found, show an error message
  return responses.send_HTTP_FORBIDDEN("Invalid authentication credentials")
end

这样做之后,不管客户端用特定的 api-key 进行多少次请求,从第一次请求之后的每次搜索都会在内存中进行,不需要再查询数据库。

更新或者删除一个自定义的实体

每当在数据存储上更新或删除缓存自定义实体时,例如使用Admin API,它都会在数据存储区中的数据与在Kong节点内存中缓存的数据之间产生不一致。为了避免这种不一致,我们需要从内存存储中删除缓存的实体,并强制Kong从数据存储中再次请求它。为了这样做,我们必须实现一个处理无效钩子

无效的自定义实体

当一个实体被创建、更新或者删除的时候,Kong 会把这些操作通知到所有的nodes,并告知执行了哪些命令或者哪些实体会受到影响。这些不仅针对 APIs\Plugins\Consumers,而且也对自定义的实体有效。

感谢这一特性,我们可以监听这些事件和根据一些适当的动作做出响应,当被缓存的实体在数据库中改动之后,我们可以明确的从缓存中移除它,避免数据库与缓存中数据不一直的问题。从内存中移除缓存之后,会触发再次请求数据库,重新缓存实体。

Kong 传播的事件如下:

event name description
ENTITY_CREATED When any entity is being created.
ENTITY_UPDATED When any entity is being updated.
ENTITY_DELETED When any entity is being deleted.

为了监听这些事件,我们需要实现 hooks.lua 文件,并且在我们的插件中发布它,例如:

-- hooks.lua

local events = require "kong.core.events"
local cache = require "kong.tools.database_cache"

local function invalidate_on_update(message_t)
  if message_t.collection == "apikeys" then
    cache.delete("apikeys."..message_t.old_entity.apikey)
  end
end

local function invalidate_on_create(message_t)
  if message_t.collection == "apikeys" then
    cache.delete("apikeys."..message_t.entity.apikey)
  end
end

return {
  [events.TYPES.ENTITY_UPDATED] = function(message_t)
    invalidate_on_update(message_t)
  end,
  [events.TYPES.ENTITY_DELETED] = function(message_t)
    invalidate_on_create(message_t)
  end
}

在上边这个例子中,我们监听了 ENTITY_UPDATED 和 ENTITY_DELETED 两个事件,并且调用了适当的方法来相应。其中,message_t table 包含的事件属性如下:

property name type description
collection String 受操作影响的数据存储的集合
entity Table 最近更新、删除或者创建的实体
old_entity Table 只对更新事件有效,更新之前的实体

这里 entity 和 old_entity的属性,并不是 schema 中定义的所有属性,而是一个子集。这个情况是因为每个事件是通过 UDP 包发送的,受到 512 bytes的大小限制。这个子集是通过 schema 中的 marshall_event 函数来返回的。

marshall_event

这个函数序列化了在hooks.lua中用到的最小版本需要的字段的实体。如果这个函数没有被实现,默认 Kong 不会发送任何实体的字段。

例如:

-- daos.lua

local SCHEMA = {
  primary_key = {"id"},
  -- clustering_key = {}, -- none for this entity
  fields = {
    id = {type = "id", dao_insert_value = true},
    created_at = {type = "timestamp", dao_insert_value = true},
    consumer_id = {type = "id", required = true, queryable = true, foreign = "consumers:id"},
    apikey = {type = "string", required = false, unique = true, queryable = true}
  },
  marshall_event = function(self, t) -- This is related to the invalidation hook
    return { id = t.id, consumer_id = t.consumer_id, apikey = t.apikey }
  end
}

上边这个例子在自定义的实体中提供了 marshall_event,返回一个包含 id, consumer_id和 apikey的对象。在我们的hooks中不需要 creation_date来无效这个实体,所以我们不关心在事件中传播它。参数 t table是一个包含全部字段的原对象。

注意:这个lua table的 json 序列化串被返回的时候不能大于512 bytes, 以保证整个时间在一个 UDP 包中。

你可能感兴趣的:(Kong 插件开发 - 缓存自定义实体)