Memcached插件:MySQL中的NoSQL



你好 我叫Maxim Matyukhin,我是Badoo的PHP程序员。 在我们的工作中,我们积极使用MySQL。 但是有时我们缺乏它的性能,因此我们一直在寻找加快其工作速度的方法。

2010年,Yoshinori Matsunobu推出了NoSQL MySQL插件HandlerSocket。 据称,该插件允许您每秒执行超过750,000个请求。 我们变得好奇,几乎马上就开始使用此解决方案。 我们非常喜欢此结果,因此我们开始进行演示并撰写文章来推广HandlerSocket。

显然,我们是该插件的少数用户之一-自MySQL 5.7起它停止工作。 但是在此版本中,出现了另一个来自Oracle的插件-InnoDB memcached插件,它承诺具有类似的功能。

尽管memcached插件于2013年出现在MySQL 5.6中,但关于它的文章并不多,并且在大多数情况下,它们会重复该文档:创建了一个简单标签,并通过memcached客户端对其进行了请求。

我们在Memcached方面拥有丰富的经验,并且习惯于与它进行交互的便捷性。 我们希望InnoDB memcached插件具有相同的简单性。 但实际上,事实证明,如果使用插件的模式与文档和文章中描述的模式至少略有不同,则会弹出很多细微差别和限制,如果您打算使用该插件,则绝对值得考虑。

MySQL HandlerSocket


在本文中,我们将以一种或另一种方式将新的memcached插件与旧的HandlerSocket进行比较。 因此,我记得是后者。

安装HandlerSocket插件后,MySQL开始监听另外两个端口:

  1. 第一个端口接收客户端请求读取数据。
  2. 第二个端口接收到客户端的数据记录请求。

客户端必须在这些端口之一上建立正常的TCP连接(不支持身份验证),然后必须发送“开放索引”命令(一种特殊的命令,客户端可以通过该命令告知要访问哪个表的哪个索引字段读取(或写入))。

如果“打开索引”命令成功运行,则可以根据建立连接的端口发送GET或INSERT / UPDATE / DELETE命令。

HandlerSocket不仅允许在主键上执行GET,而且还可以执行来自非唯一索引的简单示例,范围示例,受支持的multiget和LIMIT。 同时,可以从普通SQL和通过插件使用表。 例如,这允许您通过SQL在事务中进行一些更改,然后通过HandlerSocket读取此数据。

HandlerSocket通过epoll处理具有有限线程池的所有连接非常重要,因此很容易支持成千上万个连接,而在MySQL本身中,为每个连接创建了一个线程,并且线程数量非常有限。

同时,它仍然是普通的MySQL服务器-我们熟悉的技术。 我们知道如何复制和监视它。 监视HandlerSocket很困难,因为它不提供任何特定的指标。 但是,一些标准的MySQL和InnoDB指标很有用。

当然有一些不便之处,特别是此插件不支持使用时间戳类型。 好吧,HandlerSocket协议更难阅读,因此更难调试。

在此处阅读有关HandlerSocket的更多信息。 您也可以观看我们的演示文稿之一

InnoDB memcached插件


新的memcached插件为我们提供了什么?

顾名思义,他的想法是使用memcached客户端与MySQL一起使用,并通过memcached命令接收和保存数据。

您可以在此处了解该插件的主要优点。

我们对以下内容最感兴趣:

  1. 低CPU消耗。
  2. 数据存储在InnoDB中,这提供了一定的保证。
  3. 您可以通过Memcached和SQL处理数据。 可以使用MySQL内置工具复制它们。

您可以将如下加号添加到此列表中:

  1. 快速廉价的连接。 常规的MySQL连接由一个线程处理,并且线程数受到限制,并且在memcached插件中,一个线程处理事件循环中的所有连接。
  2. 通过一个GET请求请求多个密钥的能力。
  3. 如果与MySQL HandlerSocket进行比较,则在memcached插件中,您无需使用“ Open Table”命令,所有读取和写入操作都在同一端口上进行。


有关该插件的更多详细信息,请参见官方文档 。 对我们来说,最有用的页面是:

  1. InnoDB内存缓存架构
  2. InnoDB memcached插件内部

安装插件后,MySQL开始在端口11211(标准memcached端口)上接受连接。 还将显示一个特殊的数据库(schema)innodb_memcache,您将在其中配置对表的访问。

简单的例子


假设您已经有了要通过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 

并且您想要接收和修改主键上的数据。

您需要首先在innodb_memcache.containers表中描述memcached键和SQL表之间的对应关系。 该表如下所示(我删除了编码说明,以使其更易于阅读):

 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 

最重要的领域:

  • 名称-Memcached键的前缀;
  • db_schema-基础名称(电路);
  • db_table是您的表;
  • key_columns-我们将搜索的表中字段的名称(通常这是您的主键);
  • value_columns-表中可用于memcached插件的字段列表;
  • unique_idx_name_on_key是要查找的索引(尽管您已经指定了key_columns,但是它们可以在不同的索引中,并且您需要显式指定索引)。

其余字段对于开始不是很重要。

在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'; 

在此示例中,name ='auth'是我们的内存缓存密钥的前缀。 在文档中,它通常称为table_id,在本文的后面,我将使用此术语。

现在,TELNET连接到memcached插件,并尝试保存并获取数据:

 [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 

首先,我们发送了GET请求,但没有返回任何信息。 然后,我们使用SET请求保存数据,然后使用GET将其取回。

GET返回以下行:1234567 | 89。 这些是“密码”和“类型”字段的值,以“ |”符号分隔。 字段以innodb_memcache.containers.value_columns中描述的顺序返回。

也许您现在想知道:“如果在“密码”中遇到符号“ |”,将会发生什么?” 我将在下面讨论。

通过SQL,此数据也可用:

 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


还有这样一种操作模式:

 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 

在此示例中,使用get @@ auth,使table_id auth成为此连接的默认前缀。 之后,无需指定table_id即可完成所有后续查询。

到目前为止,一切都是简单而合乎逻辑的。 但是,如果您开始理解,那么会有很多细微差别。 我告诉你我们发现了什么。

细微差别


缓存innodb_memcache.containers表


memcached插件在启动时会读取innodb_memcache.containers表。 此外,如果未知的table_id通过Memcached协议到达,则插件会在表中搜索它。 因此,您可以轻松添加新键(table_id),但是如果要更改现有table_id的设置,则必须重新启动memcached插件:

 mysql> UNINSTALL PLUGIN daemon_memcached; mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so"; 

在这两个请求之间,Memcached接口将不起作用。 因此,创建新的table_id通常比更改现有的table_id并重新启动插件要容易。

令我们惊讶的是,在为InnoDB的Memcached插件改编Memcached应​​用程序页面上描述了插件操作的如此重要的细微差别,对于这些信息而言,这并不是很合理的地方。

标志,cas_column,expire_time_column


这些字段是模拟Memcached的某些功能所必需的。 它们的文档不一致。 其中的大多数示例说明了使用这些字段所在的表的情况。 您可能需要担心将它们添加到表中(这些是至少三个INT字段)。 但是没有 如果表中没有此类字段,并且不打算使用诸如CAS,到期或标志之类的Memcached功能,则无需将这些字段添加到表中。

在innodb_memcache.containers中配置表时,您需要在以下字段中输入“ 0”,使行精确为零:

 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'; 

令人讨厌的是cas_column和expire_time_column的默认值为NULL,如果在不为这些字段指定值“ 0”的情况下执行INSERT INTO innodb_memcache.containers,则NULL将存储在其中,并且此memcache前缀将完全不起作用。

资料类型


从文档中,并不清楚使用该插件时可以使用哪些数据类型。 在某些地方,据说该插件只能与文本字段(CHAR,VARCHAR,BLOB)一起使用。 此处: 为InnoDB memcached插件改编现有的MySQL模式以将数字存储在字符串字段中,然后,如果您需要使用SQL中的这些数字字段,则创建一个VIEW,在其中将具有数字的VARCHAR字段转换为INTEGER字段:

 CREATE VIEW numbers AS SELECT c1 KEY, CAST(c2 AS UNSIGNED INTEGER) val FROM demo_test WHERE c2 BETWEEN '0' and '9999999999'; 

但是,在文档的某些地方,仍然写有可以使用数字的内容。 到目前为止,我们只有真正的文本领域生产经验,但是实验结果表明该插件还可以处理数字:

 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'; 

之后,通过Memcached协议:

 get @@numbers.1 END set @@numbers.1 0 0 2 12 STORED get @@numbers.1 VALUE @@numbers.1 0 2 12 END 

我们看到memcached插件可以返回任何数据类型。 但是他以它们位于InnoDB中的形式返回它们,因此,例如,在时间戳记/日期时间/浮点数/十进制/ JSON的情况下,将返回二进制字符串。 但是,正如我们通过SQL看到的那样,返回整数。

多点


使用memcached协议,您可以通过一个请求来请求多个密钥:

 get @@numbers.2 @@numbers.1 VALUE @@numbers.2 0 2 12 VALUE @@numbers.1 0 2 13 END 

multiget有效的事实已经很好。 但它在一个table_id的框架内工作:

 get @@auth.ivan@example.com @@numbers.2 VALUE @@auth.ivan@example.com 0 10 qwerty|xxx END 

这一点在此处的文档中进行了描述: https : //dev.mysql.com/doc/refman/8.0/en/innodb-memcached-multiple-get-range-query.html 。 事实证明,在multiget中,如果所有其他键均来自默认的table_id(例如来自文档的示例),则只能为第一个键指定table_id:

 get @@aaa.AA BB VALUE @@aaa.AA 8 12 HELLO, HELLO VALUE BB 10 16 GOODBYE, GOODBYE END 

在此示例中,第二个键来自默认的table_id。 我们可以从默认的table_id中指定更多的键,并且对于第一个键,我们指定一个单独的table_id,只有在第一个键的情况下才有可能。

我们可以说multiget在一个表的框架内工作,因为您不想在生产代码中依赖这种逻辑:这并不明显,很容易忘记它,犯错了。

如果与HandlerSocket进行比较,则multiget也在同一表中工作。 但是这种限制看起来很自然:客户端打开表中的索引并从中请求一个或多个值。 但是,当在具有不同前缀的多个键上使用multiget memcached插件时,这是正常做法。 您也希望MySQL memcached插件具有相同的功能。 但是没有:(

INCR,DEL


我已经给出了GET / SET请求的示例。 INCR和DEL查询具有功能。 这是因为它们仅在使用默认table_id时才起作用:

 DELETE @@numbers.1 ERROR get @@numbers VALUE @@numbers 0 24 test/numbers END delete 1 DELETED 

Memcached协议限制


Memcached具有文本协议,该协议具有一些限制。 例如,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 

这意味着在“电子邮件”字段中不应包含此类字符。

此外,内存缓存密钥必须小于250个字节(字节,而不是字符)。 如果发送更多,则会出现错误:

 "CLIENT_ERROR bad command line format" 

此外,必须考虑到以下事实,即memcached插件在memcached协议中添加了自己的语法。 例如,它使用字符“ |” 作为响应中的字段分隔符。 您需要确保在表中未使用此符号。 可以配置分隔符,但是设置将应用于整个MySQL服务器上的所有表。

字段定界符value_columns


如果您需要通过memcached协议返回几列,如第一个示例所示:

 get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END 

然后用标准分隔符“ |”分隔列值。 问题出现了:“例如,如果字符“ |”在该行的第一个字段中,将会发生什么?” 在这种情况下,memcached插件将按原样返回字符串,如下所示:1234 | 567 | 89。 在一般情况下,无法理解哪里有字段。

因此,立即选择正确的分隔符很重要。 而且,由于它将用于所有表的所有键,因此它应该是一个通用符号,在通过memcached协议使用的任何字段中都不会找到。

总结


这并不是说memcached插件不好。 但是,似乎它是为特定的工作方案编写的:一个具有一个可以使用memcached协议访问的表的MySQL服务器,并且该table_id设置为默认值。 客户端与Memcached插件建立持久连接,并向默认的table_id发出请求。 大概,在这样的方案中,一切都会完美地工作。 如果您离开它,则会遇到各种不便之处。

您可能希望看到一些插件性能报告。 但是我们尚未决定在高负载的地方使用它。 我们仅在很少加载的系统中使用它,并且它的运行速度与HandlerSocket大致相同,但我们并未制定诚实的基准测试。 但是,该插件提供了这样的接口,程序员可以通过该接口轻松地犯错-您需要牢记许多细微差别。 因此,我们尚未准备好批量使用此插件。

我们在MySQL Bug Tracker中提出了一些功能请求:

https://bugs.mysql.com/bug.php?id=95091
https://bugs.mysql.com/bug.php?id=95092
https://bugs.mysql.com/bug.php?id=95093
https://bugs.mysql.com/bug.php?id=95094

我们希望memcached插件开发团队能够改进其产品。

Source: https://habr.com/ru/post/zh-CN453742/


All Articles