Configurando memcached com replicação no Ubuntu 12.04LTS

Introdução

Neste artigo vamos aprender como implementar uma solução de replicação do memcached. Ideal para quando precisamos de um ambiente de alta disponibilidade nos dados armazenados nele.

No caso deste tutorial, vamos nos basear no cenário onde precisamos armazenar os dados de sessão do PHP no memcached e que essa infraestrutura esteja em um ambiente de alta disponibilidade.

Cenário

O memcached é um sistema de cache de dados distribuído de alta performance. Os dados são armazenados em memória. Normalmente é utilizado para acelerar aplicações web dinâmicas, realizando cache de informações, e dessa forma, aliviando a carga em banco de dados.

Em um outro cenário, você pode configurar o armazenamento de sessões do PHP diretamente em um servidor memcached, ajudando no objetivo de centralizar os dados de sessão de vários servidores PHP e um único lugar.

Veja na figura abaixo o exemplo de uma solução onde centralizamos os dados de sessão de 3 servidores PHP em um único lugar. Neste caso, queremos fazer um balanceamento de carga de uma aplicação PHP, portanto precisamos centralizar os dados de sessão.

php-sessao-memcached

O problema com este cenário inicial é que temos um ponto de falha crítico. Caso o servidor onde o memcached esta instalado corromper ou ficar fora do ar teremos um indisponibilidade geral no sistema.

Um solução mais inteligente seria configurar um ambiente de failover para o servidor do memcached. Dessa forma, caso o servidor A cair, um servidor B assumiria o seu lugar e o sistema continuará funcionando normalmente.

Veja figura abaixo representando a solução ideal:

php-sessao-repcached

Para que a solução acima funciona da melhor forma, precisamos que o servidor do memcached faça a replicação de dados do Servidor A para o Servidor B e vice-versa. Dessa forma, quando o servidor A cair por alguma falha, o Servidor B ao assumir, terá conhecimento de todas as sessões que foram registradas até o momento no Servidor A.

Por padrão, o serviço do memcached não tem suporte à replicação. Será preciso aplicar um patch que adiciona o suporte de replicação. O projeto que disponibiliza o memcached com o patch de replicação, chama-se repcached e pode ser acessado através do link:
http://repcached.lab.klab.org/

Neste tutorial vamos aprender como configurar o repcached utilizando um ambiente onde temos 2 servidores Ubuntu. No caso, este artigo foi escrito baseado no Ubuntu Server 12.04LTS

Caso queira aprender mais sobre configuração de failover, aconselho a leitura de um outro artigo meu: Alta disponibilidade no Linux com heartbeat

Preparando o Ambiente

O primeiro passo será preparar a sua distribuição Ubuntu para podermos compilar, configurar e instalar o repcached.

Instale os seguintes pacotes nos dois servidores, através do comando:

# apt-get install build-essential libevent-dev

Compilando

Em seguida, vamos compilar o pacote do repcached. O procedimento deve ser efetuado nos dois servidores.

Faça o download do código fonte do projeto pelo link abaixo:
http://sourceforge.net/projects/repcached/files/

No momento atual em que o artigo foi escrito, o nome do pacote stable é memcached-1.2.8-repcached-2.2.1.tar.gz

Observe que estamos baixando a versão completa do memcached com o patch já aplicado. E não somente aplicando o patch de replicação à um código fonte do memcached já existente.

Segue o procedimento para compilação do repcached:

# tar xvf memcached-1.2.8-repcached-2.2.1.tar.gz
# cd memcached-1.2.8-repcached-2.2.1/
# ./configure --enable-replication
# make
# make install

Ao executar o comando make é bem provável que ocorra o seguinte erro de compilação:

gcc -DHAVE_CONFIG_H -I.  -DNDEBUG   -g -O2 -MT memcached-memcached.o -MD -MP -MF .deps/memcached-memcached.Tpo -c -o memcached-memcached.o `test -f 'memcached.c' || echo './'`memcached.c
memcached.c: In function ‘add_iov’:
memcached.c:697:30: error: ‘IOV_MAX’ undeclared (first use in this function)
memcached.c:697:30: note: each undeclared identifier is reported only once for each function it appears in
memcached.c: In function ‘process_stat’:
...

Para solucionar a questão, edite or arquivo memcached.c e encontre o bloco abaixo:

/* FreeBSD 4.x doesn't have IOV_MAX exposed. */
#ifndef IOV_MAX
#if defined(__FreeBSD__) || defined(__APPLE__)
# define IOV_MAX 1024
#endif
#endif

Substitua por:

#ifndef IOV_MAX
# define IOV_MAX 1024
#endif

Execute o make novamente:

# make
# make install

Configurando o repcached

Os procedimentos abaixo devem ser executados nos dois servidores: A e B

Por padrão, a nova versão do memcached foi instalado em /usr/local/bin/memcached
Dando continuidade ao processo, vamos criar um usuário chamado memcache que será utilizado para rodar o processo da nova instalação:

# useradd -s /bin/false -d /nonexistent memcache

Em seguida vamos criar um arquivo de configuração, em /usr/local/etc/repcached, com os parâmetros de inicialização para o repcached:

DAEMON_ARGS="-m 64 -p 11211 -u memcache -P /var/run/repcached.pid -d -x 192.168.0.2"

Onde:

  • -m 64 (Tamanho máximo de memória (MB) para um objeto memcached)
  • -p 11211 (Porta que o memcached fará o Listen)
  • -u memcache (Usuário do sistema que o processo do memcached utilizará)
  • -P /var/run/repcached.pid (Localização do arquivo pid)
  • -d (Roda o memcached como daemon)
  • -x 192.168.0.2 (IP do servidor master de replicação – No servidor A, configurar o IP do Servidor B e no Servidor B configurar o ip do Servidor A)

Configurando script de inicialização

Os procedimentos abaixo devem ser executados nos dois servidores: A e B

Crie um novo arquivo em /etc/init.d/repcached com o conteúdo abaixo:

#! /bin/sh
### BEGIN INIT INFO
# Provides:             memcached
# Required-Start:       $syslog
# Required-Stop:        $syslog
# Should-Start:         $local_fs
# Should-Stop:          $local_fs
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    memcached - Memory caching daemon replicated
# Description:          memcached - Memory caching daemon replicated
### END INIT INFO
# Author: Michael 
# Modified: Douglas
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="repcached"
NAME=memcached
DAEMON=/usr/local/bin/$NAME
DAEMON_ARGS="--options args"
PIDFILE=/var/run/repcached.pid
SCRIPTNAME=/etc/init.d/$DESC
VERBOSE="yes"
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /usr/local/etc/$DESC ] && . /usr/local/etc/$DESC
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
	# Return
	#   0 if daemon has been started
	#   1 if daemon was already running
	#   2 if daemon could not be started
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
		|| return 1
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
		$DAEMON_ARGS \
		|| return 2
	# Add code here, if necessary, that waits for the process to be ready
	# to handle requests from services started subsequently which depend
	# on this one.  As a last resort, sleep for some time.
}
#
# Function that stops the daemon/service
#
do_stop()
{
	# Return
	#   0 if daemon has been stopped
	#   1 if daemon was already stopped
	#   2 if daemon could not be stopped
	#   other if a failure occurred
    start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
    RETVAL="$?"
    [ "$RETVAL" = 2 ] && return 2
	# Wait for children to finish too if this is a daemon that forks
	# and if the daemon is only ever run from this initscript.
	# If the above conditions are not satisfied then add some other code
	# that waits for the process to drop all resources that could be
	# needed by services started subsequently.  A last resort is to
	# sleep for some time.
	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
	[ "$?" = 2 ] && return 2
	# Many daemons don't delete their pidfiles when they exit.
	rm -f $PIDFILE
	return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
	#
	# If the daemon can reload its configuration without
	# restarting (for example, when it is sent a SIGHUP),
	# then implement that here.
	#
	start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
	return 0
}
case "$1" in
  start)
	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
	do_start
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  stop)
	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
	do_stop
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  restart|force-reload)
	#
	# If the "reload" option is implemented then remove the
	# 'force-reload' alias
	#
	log_daemon_msg "Restarting $DESC" "$NAME"
	do_stop
	case "$?" in
	  0|1)
		do_start
		case "$?" in
			0) log_end_msg 0 ;;
			1) log_end_msg 1 ;; # Old process is still running
			*) log_end_msg 1 ;; # Failed to start
		esac
		;;
	  *)
	  	# Failed to stop
		log_end_msg 1
		;;
	esac
	;;
  *)
	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
	exit 3
	;;
esac
:

Instale o script de init recém-criado para ser executado na inicialização:

# update-rc.d repcached defaults

Inicialize o processo rodando:

# /etc/init.d/repcached start

Testando a replicação

Para testar a replicação vamos inserir no repcached a variável de nome ‘chave’ com o valor ‘teste’ no Servidor A. Veja exemplo:

# telnet 127.0.0.1 11211
 set chave 0 0 5
 teste
 quit

Após digitar o valor ‘teste’ (acima), o repcached deve retornar a string ‘STORED’ – confirmando que o valor foi armazenado com sucesso no cache. O número 5 no exemplo acima, indica o tamanho (length) da string a ser armazenada no cache.

Agora vamos tentar recuperar o valor armazenado no Servidor B:

 # telnet 127.0.0.1 11211
 get chave

O procedimento acima deverá retornar o valor ‘teste‘. Com este resultado, podemos dizer que a replicação esta funcionando como esperado.

Você pode realizar outro teste alternando a ordem dos servidores, para garantir que a replicação esta funcionando nas duas direções.

Configurando a sessão do PHP para usar o memcache

(Este procedimento deve ser feito em todos servidores PHP que irão compartilhar da mesma sessão)

Por fim vamos configurar o php para armazenar os dados da sessão no servidor do memcache. O ip que deve ser configurado neste passo é o IP vip utilizado na solução do failover de acordo com o exemplo ilustrado no início deste artigo. (Para maiores detalhes sobre failover, ler o artigo Alta disponibilidade no Linux com heartbeat)

Antes de mais nada é necessário ter instalado o pacote php5-memcache:

# apt-get install php5-memcache

No php.ini, normalmente localizado em /etc/php5/apache/php.ini, identifique a bloco de configuração abaixo:

[Session]
session.save_handler = files

Substitua por:

[Session]
session.save_handler = memcache
session.save_path = "tcp://192.168.0.3:11211"

Onde o IP 192.168.0.3 é o ip vip do failover dos servidores memcached.

Conclusão

Neste artigo aprendemos como configurar o serviço memcached com suporte à replicação. Para ajudar no entendimento dos conceitos, utilizamos um cenário de exemplo onde temos 3 servidores web rodando PHP em um ambiente de balanceamento. Devido ao ambiente, surgiu a necessidade de centralizar os dados de sessão do PHP.

Este artigo também é útil em qualquer outro cenário onde dados críticos precisam ser armazenados no memcached. Fique à vontade para sugerir outros exemplos de cenários semelhantes.

Please follow and like us:

Comments

    • mm By Douglas V. Pasqua

Follow

Get every new post on this blog delivered to your Inbox.

Join other followers: