
你好 我叫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开始监听另外两个端口:
- 第一个端口接收客户端请求读取数据。
- 第二个端口接收到客户端的数据记录请求。
客户端必须在这些端口之一上建立正常的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命令接收和保存数据。
您可以在
此处了解该插件的主要优点。
我们对以下内容最感兴趣:
- 低CPU消耗。
- 数据存储在InnoDB中,这提供了一定的保证。
- 您可以通过Memcached和SQL处理数据。 可以使用MySQL内置工具复制它们。
您可以将如下加号添加到此列表中:
- 快速廉价的连接。 常规的MySQL连接由一个线程处理,并且线程数受到限制,并且在memcached插件中,一个线程处理事件循环中的所有连接。
- 通过一个GET请求请求多个密钥的能力。
- 如果与MySQL HandlerSocket进行比较,则在memcached插件中,您无需使用“ Open Table”命令,所有读取和写入操作都在同一端口上进行。
有关该插件的更多详细信息,请参见官方
文档 。 对我们来说,最有用的页面是:
- InnoDB内存缓存架构 。
- 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=95091https://bugs.mysql.com/bug.php?id=95092https://bugs.mysql.com/bug.php?id=95093https://bugs.mysql.com/bug.php?id=95094我们希望memcached插件开发团队能够改进其产品。