
Olá Meu nome é Maxim Matyukhin, sou programador de PHP no
Badoo . Em nosso trabalho, usamos ativamente o MySQL. Mas, às vezes, não temos seu desempenho, por isso estamos constantemente procurando maneiras de acelerar seu trabalho.
Em 2010, Yoshinori Matsunobu introduziu o plug-in NoSQL MySQL chamado HandlerSocket. Foi alegado que este plug-in permite executar mais de 750.000 solicitações por segundo. Ficamos curiosos e quase imediatamente começamos a usar esta solução. Gostamos tanto do resultado que começamos a fazer
apresentações e a escrever
artigos promovendo o HandlerSocket.
Aparentemente, éramos um dos poucos usuários deste plugin - desde o MySQL 5.7 ele parou de funcionar. Mas nesta versão apareceu outro plug-in da Oracle - o plugin InnoDB memcached, que prometia funcionalidade semelhante.
Apesar do fato de o plugin memcached ter aparecido no MySQL 5.6 em 2013, não há muitos artigos sobre o assunto e, na maioria das vezes, eles repetem a documentação: um rótulo simples é criado e solicitações são feitas através do cliente memcached.
Temos uma vasta experiência com o Memcached e estamos acostumados à facilidade de interagir com ele. No plugin memcached do InnoDB, esperávamos a mesma simplicidade. Porém, na verdade, se os padrões de uso do plug-in forem pelo menos um pouco diferentes dos descritos na documentação e nos artigos, muitas nuances e limitações aparecerão, o que definitivamente vale a pena considerar se você usará o plug-in.
MySQL HandlerSocket
Neste artigo, compararemos, de uma forma ou de outra, o novo plugin do memcached com o antigo HandlerSocket. Portanto, lembro que foi o último.
Após instalar o plugin HandlerSocket, o MySQL começou a ouvir duas portas adicionais:
- A primeira porta recebeu solicitações do cliente para leitura de dados.
- A segunda porta recebeu solicitações do cliente para gravação de dados.
O cliente teve que estabelecer uma conexão TCP regular em uma dessas portas (nenhuma autenticação foi suportada) e, depois disso, foi necessário enviar o comando “open index” (um comando especial com o qual o cliente informou qual tabela de qual índice de quais campos estávamos indo) leia (ou escreva)).
Se o comando "índice aberto" funcionou com êxito, você pode enviar comandos GETs ou INSERT / UPDATE / DELETE, dependendo da porta em que a conexão foi estabelecida.
O HandlerSocket permitiu executar não apenas GETs na chave primária, mas também amostras simples de um índice não exclusivo, amostras de intervalo, multigets suportados e LIMIT. Ao mesmo tempo, foi possível trabalhar com a tabela a partir do SQL comum e através do plug-in. Isso, por exemplo, permitiu fazer algumas alterações nas transações através do SQL e, em seguida, ler esses dados através do HandlerSocket.
É importante que o HandlerSocket manipule todas as conexões com um pool limitado de threads através do epoll, por isso foi fácil suportar dezenas de milhares de conexões, enquanto no MySQL foi criado um thread para cada conexão e seu número era muito limitado.
Ao mesmo tempo, ainda é um servidor MySQL comum - uma tecnologia familiar para nós. Nós sabemos como replicá-lo e monitorá-lo. Monitorar HandlerSocket é difícil porque não fornece nenhuma métrica específica; no entanto, algumas das métricas padrão do MySQL e InnoDB são úteis.
Obviamente, havia inconvenientes, em particular, este plug-in não suportava o trabalho com o tipo de carimbo de data / hora. Bem, o protocolo HandlerSocket é mais difícil de ler e, portanto, mais difícil de depurar.
Leia mais sobre HandlerSocket
aqui . Você também pode assistir a
uma de nossas apresentações .
Plugin memcached do InnoDB
O que o novo plugin do memcached nos oferece?
Como o nome indica, sua idéia é usar o cliente memcached para trabalhar com o MySQL e receber e salvar dados através de comandos memcached.
Você pode
ler sobre as principais vantagens do plugin
aqui .
Estamos mais interessados no seguinte:
- Baixo consumo de CPU.
- Os dados são armazenados no InnoDB, o que dá certas garantias.
- Você pode trabalhar com dados por meio do Memcached e do SQL; eles podem ser replicados usando as ferramentas internas do MySQL.
Você pode adicionar vantagens a esta lista como:
- Conexão rápida e barata. Uma conexão MySQL regular é processada por um thread, e o número de threads é limitado e, no plugin memcached, um thread processa todas as conexões no loop de eventos.
- A capacidade de solicitar várias chaves com uma solicitação GET.
- Se comparar com o MySQL HandlerSocket, no plugin memcached não é necessário usar o comando “Open Table” e todas as operações de leitura e gravação ocorrem na mesma porta.
Mais detalhes sobre o plugin podem ser encontrados na
documentação oficial. Para nós, as páginas mais úteis foram:
- Arquitetura memcached do InnoDB .
- Interno de plug-in armazenado em memória do InnoDB .
Depois de instalar o plugin, o MySQL começa a aceitar conexões na porta 11211 (porta memcached padrão). Um banco de dados especial (esquema) innodb_memcache também aparece, no qual você configurará o acesso às suas tabelas.
Exemplo simples
Suponha que você já tenha uma tabela com a qual deseja trabalhar através do protocolo memcached:
CREATE TABLE `auth` ( `email` varchar(96) NOT NULL, `password` varchar(64) NOT NULL, `type` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
e você deseja receber e modificar dados na chave primária.
Você precisa primeiro descrever a correspondência entre a chave memcached e a tabela SQL na tabela innodb_memcache.containers. Esta tabela é mais ou menos assim (removi a descrição da codificação para facilitar a leitura):
CREATE TABLE `containers` ( `name` varchar(50) NOT NULL, `db_schema` varchar(250) NOT NULL, `db_table` varchar(250) NOT NULL, `key_columns` varchar(250) NOT NULL, `value_columns` varchar(250) DEFAULT NULL, `flags` varchar(250) NOT NULL DEFAULT '0', `cas_column` varchar(250) DEFAULT NULL, `expire_time_column` varchar(250) DEFAULT NULL, `unique_idx_name_on_key` varchar(250) NOT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT
Os campos mais importantes:
- nome - prefixo da sua chave do Memcached;
- db_schema - nome da base (circuito);
- db_table é sua mesa;
- key_columns - o nome do campo na tabela pela qual procuraremos (geralmente essa é sua chave primária);
- value_columns - uma lista de campos da tabela que estarão disponíveis para o plugin memcached;
- unique_idx_name_on_key é o índice a ser pesquisado (embora você já tenha especificado key_columns, eles podem estar em índices diferentes e você precisa especificar o índice explicitamente).
Os campos restantes não são muito importantes para começar.
Adicione uma descrição da nossa tabela ao innodb_memcache.containers:
INSERT INTO innodb_memcache.containers SET name='auth', db_schema='test', db_table='auth', key_columns='email', value_columns='password|type', flags='0', cas_column='0', expire_time_column='0', unique_idx_name_on_key='PRIMARY';
Neste exemplo, name = 'auth' é o prefixo da nossa chave do memcached. Na documentação, geralmente é chamado table_id e, posteriormente, neste artigo, usarei esse termo.
Agora, o TELNET conecta-se ao plug-in memcached e tenta salvar e obter os dados:
[21:26:22] maxm@localhost: ~> telnet memchached-mysql.dev 11211 Trying 127.0.0.1... Connected to memchached-mysql.dev. Escape character is '^]'. get @@auth.max@example.com END set @@auth.max@example.com 0 0 10 1234567|89 STORED get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END
Primeiro, enviamos uma solicitação GET, ela não retornou nada para nós. Em seguida, salvamos os dados com uma solicitação SET, após a qual recuperamos com um GET.
GET retornou a seguinte linha: 1234567 | 89. Esses são os valores dos campos "senha" e "tipo", separados pelo símbolo "|". Os campos são retornados na ordem em que foram descritos em innodb_memcache.containers.value_columns.
Talvez você esteja se perguntando agora: "O que acontecerá se o símbolo" | "for encontrado na" senha "?" Vou falar sobre isso abaixo.
Através do SQL, esses dados também estão disponíveis:
MySQL [(none)]> select * from auth where email='max@example.com'; +-----------------+----------+------+ | email | password | type | +-----------------+----------+------+ | max@example.com | 1234567 | 89 | +-----------------+----------+------+ 1 row in set (0.00 sec)
Table_id padrão
Há também esse modo de operação:
get @@auth VALUE @@auth 0 21 test/auth END get max@example.com VALUE max@example.com 0 10 1234567|99 END set ivan@example.com 0 0 10 qwerty|xxx STORED get ivan@example.com VALUE ivan@example.com 0 10 qwerty|xxx END
Neste exemplo, com get @@ auth, tornamos table_id auth o prefixo padrão para esta conexão. Depois disso, todas as consultas subseqüentes podem ser feitas sem especificar table_id.
Até agora, tudo é simples e lógico. Mas se você começar a entender, há muitas nuances. Vou te contar o que encontramos.
Nuances
Armazenando em cache a tabela innodb_memcache.containers
O plug-in memcached lê a tabela innodb_memcache.containers uma vez na inicialização. Além disso, se um table_id desconhecido chegar através do protocolo Memcached, o plug-in procurará por ele na tabela. Portanto, você pode adicionar facilmente novas chaves (id_tabela), mas se desejar alterar as configurações de um id_tabela existente, precisará reiniciar o plug-in memcached:
mysql> UNINSTALL PLUGIN daemon_memcached; mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so";
Entre esses dois pedidos, a interface do Memcached não funcionará. Por isso, geralmente é mais fácil criar um novo table_id do que alterar o existente e reiniciar o plug-in.
Foi uma surpresa para nós que uma nuance tão importante da operação de plug-in seja descrita na página
Adaptando um Aplicativo Memcached para o InnoDB , o que não é um lugar muito lógico para essas informações.
Sinalizadores, cas_column, expire_time_column
Esses campos são necessários para simular alguns recursos do Memcached. A documentação para eles é inconsistente. A maioria dos exemplos ilustra o trabalho com tabelas nas quais esses campos estão. Pode haver uma preocupação de que você precisará adicioná-los às suas tabelas (e esses são pelo menos três campos INT). Mas não. Se você não tiver esses campos nas tabelas e não usará a funcionalidade Memcached como CAS, expiração ou sinalizadores, não será necessário adicionar esses campos às tabelas.
Ao configurar a tabela em innodb_memcache.containers, é necessário inserir '0' nesses campos, fazer exatamente a linha com zero:
INSERT INTO innodb_memcache.containers SET name='auth', db_schema='test', db_table='auth', key_columns='email', value_columns='password|type', flags='0', cas_column='0', expire_time_column='0', unique_idx_name_on_key='PRIMARY';
É irritante que cas_column e expire_time_column tenham um valor padrão NULL, e se você executar INSERT INTO innodb_memcache.containers sem especificar o valor '0' para esses campos, NULL será armazenado neles e esse prefixo de memcache simplesmente não funcionará.
Tipos de dados
Na documentação, não está muito claro quais tipos de dados podem ser usados ao trabalhar com o plug-in. Em vários lugares, diz-se que o plugin só pode funcionar com campos de texto (CHAR, VARCHAR, BLOB). Aqui: A
adaptação de um esquema MySQL existente para o plug-in memcached do InnoDB oferece o armazenamento de números em campos de string e, se você precisar trabalhar com esses campos numéricos do SQL, crie uma VIEW na qual os campos VARCHAR com números serão convertidos em campos INTEGER :
CREATE VIEW numbers AS SELECT c1 KEY, CAST(c2 AS UNSIGNED INTEGER) val FROM demo_test WHERE c2 BETWEEN '0' and '9999999999';
No entanto, em alguns lugares, a documentação ainda diz que você pode trabalhar com números. Até agora, temos apenas uma experiência real de produção com campos de texto, mas os resultados experimentais mostram que o plug-in também funciona com números:
CREATE TABLE `numbers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `counter` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB INSERT INTO innodb_memcache.containers SET name='numbers', db_schema='test', db_table='numbers', key_columns='id', value_columns='counter', flags='0', cas_column='0',expire_time_column='0',unique_idx_name_on_key='PRIMARY';
Depois disso, através do protocolo Memcached:
get @@numbers.1 END set @@numbers.1 0 0 2 12 STORED get @@numbers.1 VALUE @@numbers.1 0 2 12 END
Vemos que o plugin memcached pode retornar qualquer tipo de dados. Mas ele os retorna na forma em que estão no InnoDB; portanto, por exemplo, no caso de carimbo de data / hora / data / hora / flutuação / decimal / JSON, uma string binária é retornada. Mas números inteiros são retornados como os vemos através do SQL.
Multiget
O protocolo memcached permite solicitar várias chaves com uma única solicitação:
get @@numbers.2 @@numbers.1 VALUE @@numbers.2 0 2 12 VALUE @@numbers.1 0 2 13 END
O fato de o multiget funcionar já é bom. Mas funciona dentro da estrutura de um table_id:
get @@auth.ivan@example.com @@numbers.2 VALUE @@auth.ivan@example.com 0 10 qwerty|xxx END
Este ponto está descrito na documentação aqui:
https://dev.mysql.com/doc/refman/8.0/en/innodb-memcached-multiple-get-range-query.html . Acontece que no multiget você pode especificar table_id apenas para a primeira chave, se todas as outras chaves forem obtidas do table_id padrão (exemplo da documentação):
get @@aaa.AA BB VALUE @@aaa.AA 8 12 HELLO, HELLO VALUE BB 10 16 GOODBYE, GOODBYE END
Neste exemplo, a segunda chave é retirada do table_id padrão. Poderíamos especificar muito mais chaves do table_id padrão e, para a primeira chave, especificamos um table_id separado, e isso só é possível no caso da primeira chave.
Podemos dizer que o multiget funciona na estrutura de uma tabela, porque você não deseja confiar nessa lógica no código de produção: não é óbvio, é fácil esquecê-lo, cometer um erro.
Se comparado com HandlerSocket, também haverá multiget na mesma tabela. Mas essa restrição parecia natural: o cliente abre o índice na tabela e solicita um ou mais valores. Porém, ao trabalhar com o plug-in memcached multiget em várias chaves com prefixos diferentes, essa é uma prática normal. E você espera o mesmo do plugin memcached do MySQL. Mas não :(
INCR, DEL
Eu já dei exemplos de solicitações GET / SET. As consultas INCR e DEL têm um recurso. Está no fato de que eles só funcionam ao usar o table_id padrão:
DELETE @@numbers.1 ERROR get @@numbers VALUE @@numbers 0 24 test/numbers END delete 1 DELETED
Limitações do protocolo Memcached
O Memcached possui um protocolo de texto, que impõe algumas limitações. Por exemplo, chaves com cache de memórias não devem conter caracteres de espaço em branco (espaço, avanço de linha). Se você olhar novamente para a descrição da tabela do nosso exemplo:
CREATE TABLE `auth` ( `email` varchar(96) NOT NULL, `password` varchar(64) NOT NULL, `type` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
isso significa que no campo "email" não devem existir esses caracteres.
Além disso, as chaves em cache do memcached devem ter menos de 250 bytes (bytes, não caracteres). Se você enviar mais, você receberá um erro:
"CLIENT_ERROR bad command line format"
Além disso, é preciso levar em consideração o fato de que o plugin memcached adiciona sua própria sintaxe ao protocolo memcached. Por exemplo, ele usa o caractere "|" como um separador de campo na resposta. Você precisa garantir que esse símbolo não seja usado na sua tabela. O separador pode ser configurado, mas as configurações serão aplicadas a todas as tabelas em todo o servidor MySQL.
Delimitador de campo value_columns
Se você precisar retornar várias colunas através do protocolo memcached, como em nosso primeiro exemplo:
get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END
os valores da coluna são separados pelo separador padrão "|". Surge a pergunta: "O que acontecerá se, por exemplo, o caractere" | "estiver no primeiro campo da linha?" O plugin memcached nesse caso retornará a string como está, algo como isto: 1234 | 567 | 89. No caso geral, é impossível entender onde há um campo.
Portanto, é importante escolher o separador certo imediatamente. E como será usado para todas as chaves de todas as tabelas, deve ser um caractere universal que não será encontrado em nenhum campo com o qual você trabalhará através do protocolo memcached.
Sumário
Isso não quer dizer que o plugin do memcached esteja incorreto. Mas parece que foi escrito para um esquema de trabalho específico: um servidor MySQL com uma tabela que pode ser acessada usando o protocolo memcached, e este table_id é padronizado. Os clientes estabelecem uma conexão persistente com o plugin Memcached e fazem solicitações para o table_id padrão. Provavelmente, nesse esquema, tudo funcionará perfeitamente. Se você se afastar, você encontrará vários inconvenientes.
Você pode esperar ver alguns relatórios de desempenho do plug-in. Mas ainda não decidimos usá-lo em locais altamente carregados. Nós o usamos apenas em alguns sistemas não muito carregados e lá funciona aproximadamente na mesma velocidade que o HandlerSocket, mas não fizemos benchmarks honestos. Mas, no entanto, o plug-in fornece uma interface com a qual o programador pode facilmente cometer um erro - você precisa manter muitas nuances em mente. Portanto, ainda não estamos prontos para usar esse plug-in em massa.
Fizemos algumas solicitações de recursos no rastreador de erros do MySQL:
https://bugs.mysql.com/bug.php?id=95091https://bugs.mysql.com/bug.php?id=95092https://bugs.mysql.com/bug.php?id=95093https://bugs.mysql.com/bug.php?id=95094Esperamos que a equipe de desenvolvimento de plugins do memcached melhore seu produto.