Fala pessoal,
No artigo de hoje irei mostrar a vocês como realizar uma integração em AdvPL com o Redis.
O que é o Redis?
O Redis é um armazenador de dados no formato chave e valor disponibilizado pela AWS. Seu diferencial está relacionado ao fato de armazenar essas informações na memória RAM da máquina servidora, tornando a busca de seus dados muito mais ágil que um banco de dados convencional. E ao contrário do que muitos pensam, apesar de estarmos falando de dados volátil, é possível criar snapshots com o Redis para salvar o estado das informações salvas na memória até um determinado momento.
Por que eu devo utilizar o Redis?
Imaginem que você é mantenedor de um blog que possui mais de 100.000 acessos simultâneos por dia, e queira disponibilizar essas informações de uma maneira mais ágil para seus usuários. Com o Redis você pode armazenar o conteúdo do blog em um tipo de cache, e a partir desse momento ao invés dos usuários terem que aguardar o retorno de sua API para retornar os dados, apenas a primeira requisição deverá esperar a conclusão do processo. Os demais usuários terão os dados retornados a partir do cacheamento feito com o Redis. Vejam abaixo uma ilustração:
Notem que nesse modelo é seguido o fluxo convencional, onde a requisição feita pelo client vai até a rota da API, e essa por sua vez fica responsável por conversar com o banco de dados para retornar o que lhe foi solicitado. No processo de devolução dos dados temos uma etapa a mais, onde o conteúdo encontrado pela API é armazenado no Redis no momento em que é devolvido a informação para o primeiro requisitante. Mas o que está sendo armazenado no Redis? Nada mais nada menos que JSON, afinal como dissemos anteriormente trata-se de um armazenador de chave e valor.
Vejam como nesse segundo exemplo o mesmo processo fica mais enxuto. Isso porque, não é mais necessário ir até a API buscar as informações, uma vez que todos os dados necessários para o usuário estão cacheados no Redis.
Vantagens e Desvantagens
Uma das vantagens de usar o Redis é poder listar informações para o usuário num tempo muito mais ágil que buscando de um banco de dados através de uma API. Entretanto, como tudo na vida temos que olhar para alguns pontos:
A ideia da utilização do Redis nesse artigo é utilizar o Redis para armazenar informações que não mudem com uma periodicidade alta. No caso do blog não teremos postagens sendo criadas a todo minuto, então podemos armazenar suas informações num espaço de tempo bem maior.
Agora imaginem uma aplicação responsável por devolver dados relacionados a bolsa de valores que estão em constante mudança a cada segundo. A ideia de cacheamento para esse cenário não seria aproveitada como se deve, pois a informação devolvida pelo cache já não seria mais tão atual para o solicitante, podendo gerar grande transtorno (leia-se perda de dinheiro) por parte de seus clientes.
Procurem sempre avaliar se o cenário em que se encontra cabe a aplicação do Redis.
Uso no AdvPL
Abaixo deixo para vocês um exemplo que criei de uma API que realiza a listagem de filmes para um usuário. Uma vez que, para a primeira requisição feita os dados serão devolvidas pelo banco e as subsequentes pelo Redis.
#INCLUDE "TOTVS.CH"
#INCLUDE "RESTFUL.CH"
/*/{Protheus.doc} IBGENA01
Restful desenvolvido para realizar listagem de filmes com Redis
@type class
@author Wallace
@since 14/02/2022
/*/
WSRESTFUL IBGENA01 DESCRIPTION "Listar Filmes utilizando Redis para cacheamento"
WSMETHOD GET DESCRIPTION "Realiza listagem de Filmes com Redis" WSSYNTAX "/"
END WSRESTFUL
/*/{Protheus.doc} IBGENA01
Método GET responsável por listar os filmes, ora vindo do banco, ora vindo do Redis
@type method
@author Wallace
@since 14/02/2022
/*/
WSMETHOD GET WSSERVICE IBGENA01
Local lRedisConnected := .F.
Local cMoviesJson := ""
Local cKey := ""
Private oRedisClient := Nil
Private cAlias := getNextAlias()
lRedisConnected := connectRedis()
If lRedisConnected
cKey := "movies"
cMoviesJson := getRedis( cKey )
If Empty( cMoviesJson )
listMoviesDatabase()
setMoviesInJson( @cMoviesJson )
setExRedis( cKey, cMoviesJson, 20 )
EndIf
closeConnections()
EndIf
::setResponse( cMoviesJson )
Return .T.
/*
Conecta ao Redis
*/
Static Function connectRedis()
Local cHost := "localhost"
Local nPort := 6379
Local lIsConnected := .F.
oRedisClient := tRedisClient():New()
oRedisClient:connect( cHost, nPort )
If oRedisClient:lConnected
lIsConnected := .T.
EndIf
Return lIsConnected
/*
Lista os filmes vindos do banco de dados
*/
Static Function listMoviesDatabase()
Local cSQL := ""
cSQL := "SELECT " + CRLF
cSQL += " ZZ4_CODIGO " + CRLF
cSQL += " , ZZ4_NOME " + CRLF
cSQL += " , ZZ4_GENERO " + CRLF
cSQL += "FROM " + CRLF
cSQL += " " + RetSQLName("ZZ4") + " ZZ4 " + CRLF
cSQL += "WHERE " + CRLF
cSQL += " 1 = 1 " + CRLF
cSQL += " AND D_E_L_E_T_ = ' ' " + CRLF
plsQuery( cSQL, cAlias )
Return
/*
Armazena os filmes em uma estrutura JSON
*/
Static Function setMoviesInJson( cMoviesJson )
Local oMoviesJson := JsonObject():new()
Local aListMovies := {}
Local nIndex := 1
Default cMoviesJson := ""
While !( cAlias )->( EoF() )
aAdd( aListMovies, JsonObject():new() )
nIndex := Len( aListMovies )
aListMovies[nIndex]['code'] := allTrim( ( cAlias )->ZZ4_CODIGO )
aListMovies[nIndex]['name'] := allTrim( ( cAlias )->ZZ4_NOME )
aListMovies[nIndex]['gender'] := allTrim( ( cAlias )->ZZ4_GENERO )
( cAlias )->( dbSkip() )
EndDo
oMoviesJson:set( aListMovies )
cMoviesJson := oMoviesJson:toJSON()
Return
/*
Busca o conteúdo de uma chave no Redis
*/
Static Function getRedis( cKey )
Local cCommand := ""
Local cValue := ""
Local xValueReturn := Nil
Default cKey := ""
cCommand := "GET " + cKey
oRedisClient:exec( cCommand, @xValueReturn )
If oRedisClient:lOk
cValue := xValueReturn
If valType( xValueReturn ) != 'C'
cValue := cValToChar( xValueReturn )
EndIf
EndIf
Return cValue
/*
Armazena o conteúdo em uma chave dentro do Redis
*/
Static Function setExRedis( cKey, cValue, nExpiresInSeconds )
Local xValueReturn := Nil
Default cKey := ""
Default cValue := ""
Default nExpiresIn := 0
cCommand := "SETEX " + cKey + " " + cValToChar( nExpiresInSeconds ) + " '" + cValue + "' "
oRedisClient:exec( cCommand, @xValueReturn )
If !oRedisClient:lOk
conOut("Falha ao salvar o JSON na key do Redis")
EndIf
Return
/*
Encerrando as conexões em aberto
*/
Static Function closeConnections()
oRedisClient:disconnect()
If Select( cAlias ) > 0
( cAlias )->( dbCloseArea() )
EndIf
Return
No exemplo acima, temos uma API que lista todos os filmes de uma tabela. Notem que na linha 39, após feita a conexão com o Redis utilizamos uma função para verificar se tem algum conteúdo dentro da chave “movies“. Caso o retorno seja vazio significa que estamos fazendo a primeira requisição, e com isso devemos armazenar o JSON retornado dentro do Redis. Para todo conteúdo salvo no Redis passamos o tempo que seu conteúdo ficará disponível antes de expirar (vide função setRedisEx).
Abaixo temos ilustrado a diferença de performance entre uma requisição retornada pelo banco e outra pelo Redis:
Percebam a diferença entre cada requisição: na primeira imagem o conteúdo todo foi retornado pelo BD após 891ms, enquanto na requisição feita pelo Redis o mesmo conteúdo foi retornado após 95ms. Essa é apenas uma dentre várias utilidades que o Redis pode nos proporcionar.
Caso queiram se aprofundar no assunto seguem links utilizados como referência na montagem desse post:
https://redis.io/commands
https://tdn.totvs.com/display/tec/Classe+tRedisClient
Para utilização do Redis junto ao Advpl será necessário a instalação da dll rdwincli.dll, disponível para download no link do TDN informado acima, na seção de Observações.
O objetivo desse post foi trazer alternativas para melhorar a performance de listagem de dados utilizando o conceito de cacheamento.
Espero que tenham gostado.
Até a proxima!