---
title: "`lua-resty-*` Encapsulação Liberta Desenvolvedores de Cache Multinível - API7.ai"
description: "Este artigo apresenta os princípios de implementação e o uso de duas bibliotecas de encapsulamento de cache, `lua-resty-memcached-shdict` e `lua-resty-mlcache`."
url: "https://api7.ai/pt/learning-center/openresty/lua-resty-encapsulation-releases-developers-from-caching"
---

# \`lua-resty-\*\` Encapsulação Liberta Desenvolvedores de Cache Multinível

API7.ai

December 30, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

Nos dois artigos anteriores, aprendemos sobre o cache no OpenResty e o problema de "cache stampede", que são aspectos básicos. No desenvolvimento real de projetos, os desenvolvedores preferem uma biblioteca pronta para uso, com todos os detalhes tratados e ocultos, que possa ser usada diretamente para desenvolver o código de negócios.

Essa é uma vantagem da divisão de trabalho: os desenvolvedores de componentes básicos se concentram em arquitetura flexível, bom desempenho e estabilidade do código, sem se preocupar com a lógica de negócios de nível superior; enquanto os engenheiros de aplicação se preocupam mais com a implementação do negócio e iteração rápida, esperando não se distrair com vários detalhes técnicos da camada inferior. A lacuna entre esses dois grupos pode ser preenchida por bibliotecas de encapsulamento.

O cache no OpenResty enfrenta o mesmo problema. O `shared dict` e os `lru caches` são estáveis e eficientes o suficiente, mas há muitos detalhes para lidar. A "última milha" para os engenheiros de desenvolvimento de aplicativos pode ser árdua sem algum encapsulamento útil. É aqui que a importância da comunidade entra em jogo. Uma comunidade ativa tomará a iniciativa de encontrar as lacunas e preenchê-las rapidamente.

## `lua-resty-memcached-shdict`

Vamos voltar ao encapsulamento de cache. O `lua-resty-memcached-shdict` é um projeto oficial do OpenResty que usa o `shared dict` para fazer uma camada de encapsulamento para o `memcached`, lidando com detalhes como o problema de "cache stampede" e dados expirados. Se os seus dados em cache estiverem armazenados no `memcached` no backend, você pode tentar usar esta biblioteca.

É uma biblioteca desenvolvida oficialmente pelo OpenResty, mas não está incluída no pacote do OpenResty por padrão. Se você quiser testá-la localmente, precisa baixar o [código-fonte](https://github.com/openresty/lua-resty-memcached-shdict) para o caminho de pesquisa local do OpenResty primeiro.

Esta biblioteca de encapsulamento é a mesma solução que mencionamos no artigo anterior. Ela usa o `lua-resty-lock` para ser mutuamente exclusiva. Em caso de falha no cache, apenas uma solicitação vai ao `memcached` buscar os dados e evita tempestades de cache. Os dados antigos são retornados ao endpoint se os dados mais recentes não forem obtidos.

No entanto, esta biblioteca `lua-resty`, embora seja um projeto oficial do OpenResty, não é perfeita:

1. Primeiro, ela não tem cobertura de casos de teste, o que significa que a qualidade do código não pode ser consistentemente garantida.
2. Segundo, ela expõe muitos parâmetros de interface, com 11 parâmetros obrigatórios e 7 opcionais.

```
local memc_fetch, memc_store =
    shdict_memc.gen_memc_methods{
        tag = "my memcached server tag",
        debug_logger = dlog,
        warn_logger = warn,
        error_logger = error_log,

        locks_shdict_name = "some_lua_shared_dict_name",

        shdict_set = meta_shdict_set,  
        shdict_get = meta_shdict_get,  

        disable_shdict = false,  -- optional, default false

        memc_host = "127.0.0.1",
        memc_port = 11211,
        memc_timeout = 200,  -- in ms
        memc_conn_pool_size = 5,
        memc_fetch_retries = 2,  -- optional, default 1
        memc_fetch_retry_delay = 100, -- in ms, optional, default to 100 (ms)

        memc_conn_max_idle_time = 10 * 1000,  -- in ms, for in-pool connections,optional, default to nil

        memc_store_retries = 2,  -- optional, default to 1
        memc_store_retry_delay = 100,  -- in ms, optional, default to 100 (ms)

        store_ttl = 1,  -- in seconds, optional, default to 0 (i.e., never expires)
    }
```

A maioria dos parâmetros expostos pode ser simplificada por "criar um novo manipulador de `memcached`". A maneira atual de encapsular todos os parâmetros jogando-os para o usuário não é amigável, então eu daria as boas-vindas a desenvolvedores interessados em contribuir com PRs para otimizar isso.

Além disso, otimizações adicionais são mencionadas nas seguintes direções na documentação desta biblioteca de encapsulamento.

1. Use `lua-resty-lrucache` para aumentar o cache no nível do `Worker`, em vez de apenas o cache no nível do `server` com `shared dict`.
2. Use `ngx.timer` para fazer operações assíncronas de atualização de cache.

A primeira direção é uma sugestão muito boa, pois o desempenho do cache dentro do worker é melhor; a segunda sugestão é algo que você precisa considerar com base no seu cenário real. No entanto, eu geralmente não recomendo a segunda, não apenas porque há um limite para o número de timers, mas também porque, se a lógica de atualização aqui falhar, o cache nunca será atualizado novamente, o que tem um grande impacto.

## `lua-resty-mlcache`

A seguir, vamos apresentar um encapsulamento de cache comumente usado no OpenResty: o `lua-resty-mlcache`, que usa `shared dict` e `lua-resty-lrucache` para implementar um mecanismo de cache de várias camadas. Vamos ver como esta biblioteca é usada nos dois exemplos de código a seguir.

```
local mlcache = require "resty.mlcache"

local cache, err = mlcache.new("cache_name", "cache_dict", {
    lru_size = 500,    -- tamanho do cache L1 (Lua VM)
    ttl = 3600,   -- 1h de ttl para acertos
    neg_ttl  = 30,     -- 30s de ttl para falhas
})
if not cache then
    error("failed to create mlcache: " .. err)
end
```

Vamos olhar para o primeiro trecho de código. O início deste código introduz a biblioteca `mlcache` e define os parâmetros para inicialização. Normalmente, colocaríamos este código na fase `init` e só precisaríamos fazê-lo uma vez.

Além dos dois parâmetros obrigatórios, nome do cache e nome do dicionário, um terceiro parâmetro é um dicionário com 12 opções que são opcionais e usam valores padrão se não forem preenchidos. Isso é muito mais elegante do que o `lua-resty-memcached-shdict`. Se fôssemos projetar a interface nós mesmos, seria melhor adotar a abordagem do `mlcache` - manter a interface o mais simples possível, mantendo flexibilidade suficiente.

Aqui está o segundo trecho de código, que é o código lógico quando a solicitação é processada.

```
local function fetch_user(id)
    return db:query_user(id)
end

local id = 123
local user , err = cache:get(id , nil , fetch_user , id)
if err then
    ngx.log(ngx.ERR , "failed to fetch user: ", err)
    return
end

if user then
    print(user.id) -- 123
end
```

Como você pode ver, o cache de várias camadas está oculto, então você precisa usar o objeto `mlcache` para buscar o cache e definir a função de retorno quando o cache expirar. A lógica complexa por trás disso pode ser completamente ocultada.

Você pode estar curioso sobre como esta biblioteca é implementada internamente. A seguir, vamos dar uma olhada na arquitetura e implementação desta biblioteca. A imagem a seguir é um slide de uma palestra dada por Thibault Charbonnier, o autor do `mlcache`, na OpenResty Con 2018.

![Arquitetura do mlcache](https://static.api7.ai/2022/12/08/6391afa6d43fb.webp?imageMogr2/format/webp)

Como você pode ver no diagrama, o `mlcache` divide os dados em três camadas, ou seja, `L1`, `L2` e `L3`.

O cache `L1` é o `lua-resty-lrucache`, onde cada `Worker` tem sua cópia, e com `N` `Worker`s, há `N` cópias dos dados, então há redundância de dados. Como operar o `lrucache` dentro de um único `Worker` não aciona bloqueios, ele tem um desempenho superior e é adequado como um cache de primeiro nível.

O cache `L2` é um `shared dict`. Todos os `Worker`s compartilham uma única cópia dos dados em cache e consultarão o cache `L2` se o cache `L1` não for atingido. O `ngx.shared.DICT` fornece uma API que usa spinlocks para garantir a atomicidade das operações, então não precisamos nos preocupar com condições de corrida aqui.

O `L3` é o caso em que o cache `L2` também não é atingido, e a função de retorno precisa ser executada para consultar a fonte de dados, como um banco de dados externo, e então armazená-la em cache no `L2`. Aqui, para evitar tempestades de cache, ele usa `lua-resty-lock` para garantir que apenas um `Worker` vá à fonte de dados buscar os dados.

Do ponto de vista de uma solicitação:

- Primeiro, ele consultará o cache `L1` dentro do `Worker` e retornará diretamente se o `L1` for atingido.
- Se o `L1` não for atingido ou o cache falhar, ele consultará o cache `L2` entre os `Worker`s. Se o `L2` for atingido, ele retornará e armazenará o resultado em cache no `L1`.
- Se o `L2` também não for atingido ou o cache for invalidado, uma função de retorno será chamada para buscar os dados da fonte de dados e gravá-los no cache `L2`, que é a função da camada de dados `L3`.

Você também pode ver a partir deste processo que as atualizações de cache são acionadas passivamente por solicitações de endpoint. Mesmo que uma solicitação falhe ao buscar o cache, solicitações subsequentes ainda podem acionar a lógica de atualização para maximizar a segurança do cache.

No entanto, embora o `mlcache` tenha sido implementado de forma perfeita, ainda há um ponto de dor - a serialização e desserialização de dados. Isso não é um problema do `mlcache`, mas a diferença entre `lrucache` e `shared dict`, que mencionamos repetidamente. No `lrucache`, podemos armazenar vários tipos de dados Lua, incluindo `table`; mas no `shared dict`, só podemos armazenar strings.

O cache `L1`, o `lrucache`, é a camada de dados que os usuários tocam, e queremos armazenar em cache todos os tipos de dados nele, incluindo `string`, `table`, `cdata`, e assim por diante. O problema é que o `L2` só pode armazenar strings, e quando os dados são elevados do `L2` para o `L1`, precisamos fazer uma camada de conversão de strings para tipos de dados que possamos entregar diretamente ao usuário.

Felizmente, o `mlcache` levou essa situação em consideração e fornece funções opcionais `l1_serializer` nas interfaces `new` e `get`, especificamente projetadas para lidar com o processamento de dados quando o `L2` é elevado para o `L1`. Podemos ver o seguinte código de exemplo, que extraí do meu conjunto de casos de teste.

```
local mlcache = require "resty.mlcache"

local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(i)
    return i + 2
end,
})

local function callback()
    return 123456
end

local data = assert(cache:get("number", nil, callback))
assert(data == 123458)
```

Deixe-me explicar rapidamente. Neste caso, a função de retorno retorna o número `123456`; no `new`, a função `l1_serializer` que definimos adicionará `2` ao número recebido antes de definir o cache `L1`, que se torna `123458`. Com uma função de serialização como essa, os dados podem ser mais flexíveis ao converter entre `L1` e `L2`.

## Resumo

Com várias camadas de cache, o desempenho do lado do servidor pode ser maximizado, e muitos detalhes estão ocultos entre elas. Neste ponto, uma biblioteca de encapsulamento estável e eficiente nos poupa muito esforço. Eu também espero que essas duas bibliotecas de encapsulamento apresentadas hoje ajudem você a entender melhor o cache.

Por fim, pense nesta questão: A camada de dicionário compartilhado do cache é necessária? É possível usar apenas o `lrucache`? Sinta-se à vontade para deixar um comentário e compartilhar sua opinião comigo, e você também é bem-vindo a compartilhar este artigo com mais pessoas para se comunicar e progredir juntos.

Share article link

Share

Related Posts

[![FAQ do OpenResty | Como o OpenResty é usado na prática](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [FAQ do OpenResty | Como o OpenResty é usado na prática](https://api7.ai/learning-center/openresty/faq-5)

[![Parte 3: Como Construir um API Gateway de Microservices Usando OpenResty](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [Parte 3: Como Construir um API Gateway de Microservices Usando OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [`lua-resty-memcached-shdict`](#lua-resty-memcached-shdict)
- [`lua-resty-mlcache`](#lua-resty-mlcache)
- [Resumo](#resumo)
