Operações em Memcached e Redis com Python

Memcached

Memcached é um sistema de cache de objetos distribuído de alto desempenho, utilizado em aplicações web dinâmicas para reduzir a carga no banco de dados. Ele armazena dados e objetos na memória RAM, diminuindo o número de consultas ao banco e acelerando sites orientados a dados. Baseia-se em um mapa hash de pares chave/valor. O daemon é escrito em C, mas os clientes podem ser implementados em várias linguagens, comunicando-se via protocolo memcached.

Neste artigo, focaremos no uso prático do memcached com Python. A instalação do servidor e do módulo python-memcached não será abordada.

1. Criando e Obtendo Valores

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
cliente.set('nome', 'joao')
resultado = cliente.get('nome')
print(resultado)  # Saída: joao

O módulo python-memcached suporta clusters nativamente. Internamente, mantém uma lista de servidores onde a frequência de repetição é proporcional ao peso de cada host. Por exemplo:

# Host   Peso
# 1.1.1.1  1
# 1.1.1.2  2
# 1.1.1.3  1

# Lista resultante na memória:
host_list = ["1.1.1.1", "1.1.1.2", "1.1.1.2", "1.1.1.3"]

2. Operações Comuns com python-memcached

add

Adiciona um par chave/valor. Se a chave já existir, lança uma exceção.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
cliente.add('nome', 'MARIA')  # Se 'nome' já existir, ocorrerá erro

replace

Substitui o valor de uma chave existente. Se a chave não existir, lança exceção.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
cliente.replace('nome', 'carlos')
valor = cliente.get('nome')
print(valor)  # Saída: carlos

set e set_multi

set define um par chave/valor; cria se não existir, atualiza se existir.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
cliente.set('nome', 'PAULA')
print(cliente.get('nome'))  # Saída: PAULA

set_multi define vários pares de uma vez (dicionário).

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
dados = {'nome': 'PAULA', 'idade': '25'}
cliente.set_multi(dados)
print(cliente.get('nome'), cliente.get('idade'))

delete e delete_multi

delete remove um par específico.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
cliente.set('nome', 'joao')
print(cliente.get('nome'))  # joao
cliente.delete('nome')
print(cliente.get('nome'))  # None

delete_multi remove múltiplas chaves (lista).

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
dados = {'nome': 'ana', 'idade': '30'}
cliente.set_multi(dados)
cliente.delete_multi(['nome', 'idade'])

get e get_multi

get retorna o valor de uma chave. get_multi retorna um dicionário com várias chaves.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
dados = {'nome': 'carlos', 'idade': '28', 'sexo': 'M'}
cliente.set_multi(dados)
nome = cliente.get('nome')
outros = cliente.get_multi(['idade', 'sexo'])
print(nome, outros)  # Saída: carlos {'sexo': 'M', 'idade': '28'}

append e prepend

append adiciona conteúdo ao final do valor atual.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
# Supondo que 'nome' exista com valor 'pedro'
cliente.append('nome', '-sufixo')
print(cliente.get('nome'))  # pedro-sufixo

prepend insere conteúdo no início.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
# Supondo que 'nome' seja 'pedro-sufixo'
cliente.prepend('nome', 'prefixo-')
print(cliente.get('nome'))  # prefixo-pedro-sufixo

decr e incr

incr incrementa o valor numérico em N (padrão 1). decr decrementa.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True)
cliente.set('contador', '10')
cliente.incr('contador')
print(cliente.get('contador'))  # 11
cliente.decr('contador')
print(cliente.get('contador'))  # 10

gets e cas

Evitam condições de corrida em operações de leitura-modificação-escrita. Exemplo: estoque de produto.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True, cache_cas=True)
cliente.set('estoque', '90000')
cliente.gets('estoque')
sucesso = cliente.cas('estoque', '89999')
print(sucesso)  # True

Se outro cliente modificar o valor entre gets e cas, a operação falha.

import memcache

cliente = memcache.Client(['10.0.0.8:12000'], debug=True, cache_cas=True)
cliente.set('estoque', '90000')
cliente.gets('estoque')
cliente.decr('estoque')  # Outra modificação
sucesso = cliente.cas('estoque', '89999')
print(sucesso)  # False, pois o valor foi alterado

Redis

Redis é um sistema de armazenamento chave-valor avançado, semelhante ao Memcached, mas suporta tipos de dados mais ricos: strings, listas, conjuntos, conjuntos ordenados e hashes. Todas as operações são atômicas. Redis oferece persistência em disco, replicação mestre-escravo, e suporte a pub/sub.

Comparado ao Memcached, Redis permite estruturas de dados mais complexas e operações atômicas do lado do servidor, como interseção de conjuntos, classificação, etc.

Instalação e Conexão Básica

import redis

cliente = redis.Redis(host='10.0.0.8', port=6379)
cliente.set('nome', 'joao')
print(cliente.get('nome'))  # b'joao' (bytes)

Pool de Conexões

Utilizar um pool evita o custo de abrir/fechar conexões repetidamente.

import redis

pool_conexao = redis.ConnectionPool(host='10.0.0.8', port=6379)
cliente = redis.Redis(connection_pool=pool_conexao)
cliente.set('idade', '30')
print(cliente.get('idade'))  # b'30'

Pipelining

Executa múltiplos cmoandos de uma só vez, reduzindo latência.

import redis

pool = redis.ConnectionPool(host='10.0.0.8', port=6379)
r = redis.Redis(connection_pool=pool)
pipe = r.pipeline(transaction=True)
pipe.set('fruta', 'maca')
pipe.set('cor', 'vermelho')
pipe.execute()
print(r.get('fruta'), r.get('cor'))

Operações com Strings

Redis armazena strings binárias. Principais comandos:

  • set(name, value, ex=None, px=None, nx=False, xx=False)
  • setnx(name, value) – cria apenas se não existir.
  • setex(name, value, time) – com expiração em segundos.
  • mset(mapping) – múltiplos valores.
  • getset(name, value) – retorna o valor anterior e atualiza.
  • getrange(key, start, end) – fatia a string (bytes).
  • setrange(name, offset, value) – substitui parte da string.
  • strlen(name) – comprimento em bytes.
  • incr/decr – encremento/decrmeento.
  • append(key, value) – concatena ao final.

Exemplos:

import redis

pool = redis.ConnectionPool(host='10.0.0.8', port=6379)
r = redis.Redis(connection_pool=pool)

# mset e mget
r.mset({'nome': 'ana', 'cidade': 'sp'})
valores = r.mget('nome', 'cidade')
print(valores)  # [b'ana', b'sp']

# getset
antigo = r.getset('nome', 'MARIA')
print(antigo)  # b'ana'
print(r.get('nome'))  # b'MARIA'

# getrange
r.set('palavra', 'python')
print(r.getrange('palavra', 1, 3))  # b'yth'

# setrange
r.setrange('palavra', 0, 'Java')
print(r.get('palavra'))  # b'Javathon'

# incr/decr
r.set('contador', 100)
r.incr('contador')
print(r.get('contador'))  # b'101'
r.decr('contador', 5)
print(r.get('contador'))  # b'96'

# append
r.append('palavra', ' é legal')
print(r.get('palavra'))  # b'Java é legal'

Operações com Hashes

Armazena pares campo-valor dentro de uma chave.

Estrutura de hash

  • hset(name, key, value)
  • hmset(name, mapping)
  • hget(name, key)
  • hmget(name, keys)
  • hgetall(name)
  • hlen(name), hkeys(name), hvals(name)
  • hexists(name, key)
  • hdel(name, *keys)
  • hincrby(name, key, amount)
  • hincrbyfloat(name, key, amount)
  • hscan(name, cursor=0, match=None, count=None)
  • hscan_iter(name, match=None, count=None)

Exemplo:

import redis

pool = redis.ConnectionPool(host='10.0.0.8', port=6379)
r = redis.Redis(connection_pool=pool)

r.hset('usuario:1000', 'nome', 'joao')
r.hset('usuario:1000', 'idade', 30)
print(r.hget('usuario:1000', 'nome'))  # b'joao'
print(r.hgetall('usuario:1000'))        # {b'nome': b'joao', b'idade': b'30'}

# scan iterativo
for campo in r.hscan_iter('usuario:1000'):
    print(campo)

Operações com Listas

Listas são sequências ordenadas.

  • lpush(name, *values) – insere à esquerda.
  • rpush(name, *values) – insere à direita.
  • lpushx(name, value) – só se a lista existir.
  • llen(name)
  • linsert(name, where, refvalue, value) – BEFORE ou AFTER.
  • lset(name, index, value)
  • lrem(name, value, num)
  • lpop(name), rpop(name)
  • lindex(name, index)
  • lrange(name, start, end)
  • ltrim(name, start, end)
  • rpoplpush(src, dst)
  • blpop(keys, timeout), brpop(keys, timeout)
  • brpoplpush(src, dst, timeout)

Exemplo:

import redis

pool = redis.ConnectionPool(host='10.0.0.8', port=6379)
r = redis.Redis(connection_pool=pool)

r.lpush('lista', 'c', 'b', 'a')  # ordem: a, b, c (esquerda)
r.rpush('lista', 'd', 'e')       # ordem: a, b, c, d, e
print(r.lrange('lista', 0, -1))   # [b'a', b'b', b'c', b'd', b'e']
r.lpop('lista')                   # remove 'a'
print(r.lrange('lista', 0, -1))   # [b'b', b'c', b'd', b'e']

# Iteração personalizada (incremental)
def iterar_lista(nome):
    tamanho = r.llen(nome)
    for i in range(tamanho):
        yield r.lindex(nome, i)

for item in iterar_lista('lista'):
    print(item)

Operações com Conjuntos (Sets)

Conjuntos não permitem elementos duplicados.

  • sadd(name, *values)
  • scard(name)
  • sdiff(keys, *args)
  • sdiffstore(dest, keys, *args)
  • sinter(keys, *args)
  • sinterstore(dest, keys, *args)
  • sismember(name, value)
  • smembers(name)
  • smove(src, dst, value)
  • spop(name)
  • srandmember(name, numbers)
  • srem(name, *values)
  • sunion(keys, *args)
  • sunionstore(dest, keys, *args)
  • sscan e sscan_iter

Exemplo:

import redis

pool = redis.ConnectionPool(host='10.0.0.8', port=6379)
r = redis.Redis(connection_pool=pool)

r.sadd('conjunto1', 'a', 'b', 'c')
r.sadd('conjunto2', 'c', 'd', 'e')
print(r.sinter('conjunto1', 'conjunto2'))  # {b'c'}
print(r.sunion('conjunto1', 'conjunto2'))  # {b'a', b'b', b'c', b'd', b'e'}

Conjuntos Ordenados (Sorted Sets)

Cada elemento possui uma pontuação (score) usada para ordenação.

  • zadd(name, *args, **kwargs)
  • zrange(name, start, end, desc=False, withscores=False)
  • zrevrange(name, start, end, withscores=False)
  • zrangebyscore(name, min, max, start=None, num=None, withscores=False)
  • zrevrangebyscore(name, max, min, ...)
  • zrank(name, value), zrevrank
  • zrangebylex(name, min, max, start=None, num=None)
  • zrem(name, *values)
  • zcard(name), zcount(name, min, max)
  • zincrby(name, value, amount)
  • zremrangebyrank, zremrangebyscore, zremrangebylex
  • zscore(name, value)
  • zinterstore(dest, keys, aggregate=None)
  • zunionstore(dest, keys, aggregate=None)
  • zscan, zscan_iter

Exemplo:

import redis

pool = redis.ConnectionPool(host='10.0.0.8', port=6379)
r = redis.Redis(connection_pool=pool)

r.zadd('ranking', {'joao': 100, 'maria': 200, 'carlos': 150})
print(r.zrange('ranking', 0, -1, withscores=True))
# [(b'joao', 100.0), (b'carlos', 150.0), (b'maria', 200.0)]

print(r.zrank('ranking', 'maria'))  # 2 (0-based)
r.zincrby('ranking', 50, 'joao')
print(r.zscore('ranking', 'joao'))  # 150.0

Outras Operações Úteis

  • delete(*names) – remove qualquer tipo de chave.
  • exists(name)
  • expire(name, time) – define tempo de expiração.
  • rename(src, dst)
  • move(name, db)
  • randomkey()
  • type(name)
import redis

pool = redis.ConnectionPool(host='10.0.0.8', port=6379)
r = redis.Redis(connection_pool=pool)

r.set('chave', 'valor')
print(r.exists('chave'))  # True
r.expire('chave', 60)     # expira em 60 segundos
print(r.type('chave'))    # b'string'

Publicação/Subscrição (Pub/Sub)

Redis suporta o padrão publicador/assinante.

Pub/Sub

Exemplo de uma classe auxiliar RedisHelper:

# redis_helper.py
import redis

class RedisHelper:
    def __init__(self):
        self.conexao = redis.Redis(host='10.211.55.4')
        self.canal_sub = 'fm104.5'
        self.canal_pub = 'fm104.5'

    def publicar(self, mensagem):
        self.conexao.publish(self.canal_pub, mensagem)
        return True

    def assinar(self):
        pub = self.conexao.pubsub()
        pub.subscribe(self.canal_sub)
        pub.parse_response()
        return pub

Assinante:

from redis_helper import RedisHelper

helper = RedisHelper()
sub = helper.assinar()

while True:
    mensagem = sub.parse_response()
    print(mensagem)

Publicador:

from redis_helper import RedisHelper

helper = RedisHelper()
helper.publicar('Olá do publicador')

Comparação entre Memcached e Redis

Característica Memcached Redis
Performance Alta, usa múltiplos núcleos Alta, mas tende a usar um único núcleo; para dados pequenos é mais rápido por núcleo
Tipos de dados Apenas strings Strings, listas, conjuntos, conjuntos ordenados, hashes
Persistência Não nativa Suporte a persistência em disco (RDB, AOF)
Operações atômicas Básicas Operações complexas do lado do servidor
Replicação Não nativa Mestre-escravo, alta disponibilidade
Uso de memória Eficiente para valores grandes (>100KB) Eficiente para valores pequenos com estruturas compactas

Escolha: Para dados grandes (>100KB) e simplicidade, prefira Memcached. Para estruturas complexas, persistência e operações atômicas, Redis é a melhor opção.

Comparação visual

Tags: Memcached Redis Python conexão pool

Publicado em 6-24 04:48