NoSQL to MySQL with Memcached

转载 2013年12月07日 09:37:14
前些年,HandlerSocket的横空出世让人们眼前一亮,当时我还写了一篇文章介绍了其用法梗概,时至今日,由于种种原因,HandlerSocket并没有真正流行起来,不过庆幸的是MySQL官方受其启发,研发了基于InnoDB的Memcached插件,总算是在MySQL中延续了NoSQL的香火,以前单独架设Memcached服务器不仅浪费了内存,而且还必须自己维护数据的不一致问题,有了Memcached插件,这些问题都不存在了,而且借助MySQL本身的复制功能,我们可以说是变相的实现了Memcached的复制,这更是意外之喜。
 
 

InnoDB with Memcached是在提供MySQL服务的同一进程中提供Memcached服务。memcached是作为MySQL的插件程式,通过访问本地的InnoDB API直接访问innodb数据。
架构图如下所示:

innodb
当前版本提供的功能有:
1. memcached作为mysqld的守护插件:mysqld和memcached是在同一进程空间中运行,对数据的访问具有非常低的延迟
2.直接访问InnoDB:绕过SQL解析器和优化
3. 支持memcapable的标准协议:同时支持memcached的基于文本的协议和二进制协议,所有的55 memcapable测试都通过
4. 支持多列:用户可以通过value映射到多个列,该值是分离预先定义的“分离器”。
5. 可选的本地缓存:“cache-only”, “innodb-only”, 和 “caching”, 适用于Memcached的set,get,delete和flush操作. 不但可以省去开发中使用Memcached来缓存数据的麻烦,并且具有更好的可靠性和数据一致性
6.批量操作:用户可以通过指定daemon_memcached_r_batch_size和daemon_memcached_w_batch_size大小来进行批量提交
7.支持所有的memcached配置选项,通过daemon_memcached_option参数设置

安装

为了让文章更具完整性,我们选择从源代码安装MySQL,需要注意的是早期的版本有内存泄漏,所以推荐安装最新的稳定版,截至本文发稿时为止,最新的稳定版是5.6.13,我们就以此为例来说明,过程很简单,只要激活了WITH_INNODB_MEMCACHED即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell> groupadd mysql
shell> useradd -r -g mysql mysql
shell> tar zxvf mysql-5.6.13.tar.gz
shell> cd mysql-5.6.13
shell> cmake . -DWITH_INNODB_MEMCACHED=ON
shell> make
shell> make install
shell> cd /usr/local/mysql
shell> chown -R mysql .
shell> chgrp -R mysql .
shell> scripts/mysql_install_db --user=mysql
shell> chown -R root .
shell> chown -R mysql data
shell> bin/mysqld_safe --user=mysql &
shell> cp support-files/mysql.server /etc/init.d/mysql.server

MySQL安装完毕后,在插件目录我们能看到innodb_engine.so和libmemcached.so:

1
2
3
4
5
6
mysql> SELECT @@plugin_dir;
+------------------------------+
| @@plugin_dir                 |
+------------------------------+
| /usr/local/mysql/lib/plugin/ |
+------------------------------+

此外还需要导入Memcached插件所需要的表结构:

1
mysql> SOURCE /usr/local/mysql/share/innodb_memcached_config.sql

一切就绪后就可以激活Memcached插件了(当然如果需要的话也可以禁止):

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

说明:如果要重启插件的话,可以先uninstall,再install。

Memcached插件相关的配置信息如下,具体介绍可以参考官方文档

1
2
3
4
5
6
7
8
9
10
11
mysql> SHOW VARIABLES LIKE '%memcached%';
+----------------------------------+------------------+
| Variable_name                    | Value            |
+----------------------------------+------------------+
| daemon_memcached_enable_binlog   | OFF              |
| daemon_memcached_engine_lib_name | innodb_engine.so |
| daemon_memcached_engine_lib_path |                  |
| daemon_memcached_option          |                  |
| daemon_memcached_r_batch_size    | 1                |
| daemon_memcached_w_batch_size    | 1                |
+----------------------------------+------------------+

注意:daemon_memcached_r_batch_sizedaemon_memcached_w_batch_size,这两个选项对性能影响较大,简单点说就是控制事务提交的频率,MySQL的缺省值均为1,也就是说每次都提交,这主要是从安全性考虑的,大家可以依照自己的情况来调整。

差不多了,此时Memcached端口应该准备就绪了,你可以试试看:

1
shell> echo "stats" | nc localhost 11211

换句话说,MySQL已经兼容Memcached协议,可以直接使用Memcached命令。

配置

在安装步骤里,我们导入了一个名为innodb_memcached_config.sql的脚本,它创建了一库(innodb_memcache)三表(cache_policies, config_options, containers):

1
2
3
4
5
6
7
8
9
mysql> USE innodb_memcache
mysql> SHOW TABLES;
+---------------------------+
| Tables_in_innodb_memcache |
+---------------------------+
| cache_policies            |
| config_options            |
| containers                |
+---------------------------+

cache_policies定义了缓存策略,包含如下选择:

  • innodb_only:只使用InnoDB作为数据存储。
  • cache-only:只使用传统的Memcached引擎作为后端存储。
  • caching:二者皆使用,如果在Memcached里找不到,就查询InnoDB。

config_options定义了分隔符号:

  • separator:Memcached只识别单值,使用此分隔符(|)来连接多个字段的值。
  • table_map_delimiter:通过此分隔符(.)来确认表和键,如:@@table.key。

如果我们想通过Memcached协议来访问一个表,需要先在containers中配置它:

1
2
3
4
5
6
7
8
9
10
11
mysql> SELECT * FROM containers\G
*************************** 1. row ***************************
                  name: aaa
             db_schema: test
              db_table: demo_test
           key_columns: c1
         value_columns: c2
                 flags: c3
            cas_column: c4
    expire_time_column: c5
unique_idx_name_on_key: PRIMARY

如上已经有了test数据库的demo_test表,通过c1查询c2的值,表结构如下所示:

1
2
3
4
5
6
7
8
9
10
mysql> DESC test.demo_test;
+-------+---------------------+------+-----+---------+-------+
| Field | Type                | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| c1    | varchar(32)         | NO   | PRI |         |       |
| c2    | varchar(1024)       | YES  |     | NULL    |       |
| c3    | int(11)             | YES  |     | NULL    |       |
| c4    | bigint(20) unsigned | YES  |     | NULL    |       |
| c5    | int(11)             | YES  |     | NULL    |       |
+-------+---------------------+------+-----+---------+-------+

缺省情况下有一行数据:

1
2
3
4
5
6
mysql> SELECT * FROM test.demo_test;
+----+--------------+------+------+------+
| c1 | c2           | c3   | c4   | c5   |
+----+--------------+------+------+------+
| AA | HELLO, HELLO |    8 |    0 |    0 |
+----+--------------+------+------+------+

让我们用Memcached协议来访问看看:

1
2
3
4
shell> echo "get @@aaa.AA" | nc localhost 11211
VALUE @@aaa.AA 8 12
HELLO, HELLO
END

我们还可以先设定缺省访问的表,然后后续的查询就只写键名就可以了:

1
2
3
4
5
6
7
shell> (echo "get @@aaa"; echo "get AA") | nc localhost 11211
VALUE @@aaa 0 14
test/demo_test
END
VALUE AA 8 12
HELLO, HELLO
END

虽然我的例子都是通过命令行执行的,但是大家很容易就更改写成PHP之类的方法。

限制

Memcached插件用起来非常简单,不过并不是一切都很完美,比如说:当我们配置表的时候,containers表的字段,除了key_columns和value_columns以外,其它的字段,如:flags,cas_column,expire_time_column等也必须设定,可是很多时候,我们在原表中找不到贴切的字段,此时就只能对应新建三个字段,味道很恶心。

此外,containers表还有如下限制

  • key_columns字段的类型必须是CHAR或VARCHAR,且最大长度是250个字符。
  • value_columns字段的类型必须是CHAR或VARCHAR或BLOB,长度不限。
  • cas_column字段的类型必须是BIGINT。
  • expiration_time_column字段的类型必须是INT。
  • flags字段的类型必须是INT。

说明:随着MySQL版本的更新,这些限制可能会发生变化,请大家以实际情况为准。

实战

让我们以一个用户登录的例子来检验一下学习成果:

首先在测试数据库创建一个用户表:

1
2
3
4
5
6
7
8
9
10
11
12
13
USE `test`
 
CREATE TABLE `users` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(15) NOT NULL,
    `password` VARCHAR(32) NOT NULL,
    `email` VARCHAR(50) NOT NULL,
    `flags` INT(10) UNSIGNED DEFAULT '0',
    `cas_column` BIGINT(20) UNSIGNED DEFAULT '0',
    `expire_time_column` INT(10) UNSIGNED DEFAULT '0',
    PRIMARY KEY (`id`),
    UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB;

然后添加几行测试数据:

1
2
3
4
INSERT INTO `users` (`username`, `password`, `email`)
VALUES
('foo', 'ffffffffffffffffffffffffffffffff', 'foo@domain.com'),
('bar', 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', 'bar@domain.com');

接着在containers里配置这个表:

1
2
3
4
5
6
7
INSERT INTO innodb_memcache.containers (
    name, db_schema, db_table, key_columns, value_columns,
    flags, cas_column, expire_time_column, unique_idx_name_on_key
) VALUES (
    'default', 'test', 'users', 'username', 'password|email',
    'flags', 'cas_column', 'expire_time_column', 'username'
);

这里我们定义了多个字段(password和email)作为value_columns,并且使用竖线作为分隔符,实际上使用空格,逗号之类分隔符也可以,在innodb_config.c文件的源代码中能查到如下关于分隔符的定义,文档里并没有涵盖这些信息:

1
static const char* sep = " ;,|\n";

最后使用Memcached协议来访问一下,这里我们换个花样,执行一个MGET操作:

1
2
3
4
5
6
shell> echo "get foo bar" | nc localhost 11211
VALUE foo 0 47
ffffffffffffffffffffffffffffffff|foo@domain.com
VALUE bar 0 47
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|bar@domain.com
END

既然我定义value_columns的时候设置了多个字段,那么返回数据的时候自然也返回多个字段的数据,并且它们依照innodb_memcache.config_options表中的separator字段来分隔,缺省情况下是一个竖线,如果你的字段内容里包含了竖线,那么就会和缺省值发生冲突,此时你可以更新separator的定义,比如改成三个竖线等等,需要提醒的是,修改后别忘了重启Memcached插件。

说明:因为们在配置的时候把表命名为default,所以在请求的时候不用传递表名。如果不存在default,那么会把名字按照字母顺序正序排列,排在第一位的就是缺省。

本文在使用Memcached插件的时候,所有例子均使用的是读操作,实际上写操作也是支持的,不过在实际使用时,我更倾向于写操作都通过SQL来执行,而Memcached插件仅处理KV形式的读操作,实际压力测试的结果显示,Memcached插件比SQL的方式,性能提升了百分之一百左右,虽然仍不及独立的Memcached,但考虑到其它的优势,这个结果已经很让人欣喜了,下一站,InnoDB的Memcached插件!大家做好准备吧。

 
测试 

不管如何,我们还是需要看看它的性能如何,因时间有限,我只进行了一点简单测试,采用memslap 来进行,同时部署了一个官方的memcached server 来进行对比。 
首先是官方memcached server 数据: 
Get Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       3226683      37960        39.6       0          25       41149      374        233.02     295.70 

Set Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       358528       4217         4.4        0          29       41313      406        233.22     335.51 

Total Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       3585210      42178        44.0       0          25       41313      377        233.56     299.46 

然后是采用Innodb_only 策略的mysql memcached: 
Get Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       1043059      12271        10.9       0          46       395949     1026       4794.93    424.72 

Set Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       115903       1363         1.2        0          76       395981     2482       9260.01    651.46 

Total Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       1158962      13634        12.1       0          46       395981     1172       5427.44    443.28 

采用Caching 策略后的结果: 
Get Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       1101521      12959        10.7       0          46       126892     987        3506.08    450.86 

Set Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       122398       1439         1.2        0          74       126725     2211       6591.01    683.18 

Total Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       1223919      14399        11.9       0          46       126892     1109       3942.50    469.99 

最后,是cache_only 策略的结果: 
Get Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       1043059      12271        10.9       0          46       395949     1026       4794.93    424.72 

Set Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       115903       1363         1.2        0          76       395981     2482       9260.01    651.46 

Total Statistics 
Type     Time(s)  Ops          TPS(ops/s)   Net(M/s)   Get_miss   Min(us)  Max(us)    Avg(us)    Std_dev    Geo_dist  
Global   85       1158962      13634        12.1       0          46       395981     1172       5427.44    443.28 

结论 

从上述测试结果中可以看出,mysql 官方的解决方案,现在还是一个不怎么成熟的产品,尤其表现在不论读写,都不是太稳定,std_dev 和geo_dist 数值都非常大。性能方面,和memcached server 比起来,差了三倍以上,不过,在特殊场合,这种性能消耗还是可以接受的。由于时间有限,也没法再拿HandlerSocket 和这个比较一下了,感兴趣的同学可以帮忙做个比较。 
不过,不管怎样,mysql 社区至少看到了nosql 方面的强烈需求,并且有意愿在这上面有些表现,至少来说是个可喜的迹象。 

相关文章推荐

MySQL-Memcached or NOSQL Tokyo Tyrant – part 3

This is part 3 of our series.  In part 1 we talked about boosting performance with memcached on t...
  • xxfigo
  • xxfigo
  • 2012年02月27日 21:37
  • 690

MySQL with Memcached 简介

在传统的环境下,访问信息的速度成为最大的可扩展性问题。为了频繁地访问信息,使用MySQL可能会变得很慢,原因是每一次数据访问,都必须请求执行SQL查询,以从数据库得到信息。这也意味着那些在某些时刻被锁...

Funambol with Mysql: how to install (非常好的tutorial)

转自:http://www.venturin.net/2011/12/11/funambol-with-mysql-how-to-install/ 我根据这个文档,成功的实现了Funambol使...

Call to undefined function getsqlvaluestring() with Dreamweaver PHP mysql

undefined function getsqlvaluestring() Hey Guys, I'm new to the forum and it seems very helpful...

【原】The 'InnoDB' feature is disabled; you need MySQL built with 'InnoDB' to have it working

今天安装php程序的时候,突然mysql报出了个错误:The 'InnoDB' feature is disabled; you need MySQL built with 'InnoDB' to h...

Google Merchant How To Setup A Live XML Data Feed With MySql and PHP

Setting up a live data feed for Google Merchant (Google Shopping) can be tricky if you don’t know ho...

How To Use MySQL with Your Ruby on Rails Application on Ubuntu 14.04

Introduction Ruby on Rails uses sqlite3 as its default database, which works great in many cases, b...

How To Look At MySQL Joins and More ORDER BY With LIMIT

The main purpose of this article is to demonstrate how to look at MySQL joins. By "look at" I mean h...

Unable to start MySQL service. Another MySQL daemon is already running with the same UNIX socket

Unable to start MySQL service. Another MySQL daemon is already running with the same UNIX socket ...
  • vicklin
  • vicklin
  • 2014年12月22日 15:56
  • 1035

nosql缓存技术之memcached介绍、安装以及使用总结

1.1  Memcached介绍 1.1.1        Memcached是什么 Memcached是一个开源的、支持高性能、高并发以及分布式的内纯缓存服务软件,从名称上前三个字符的单词Mem就是...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:NoSQL to MySQL with Memcached
举报原因:
原因补充:

(最多只允许输入30个字)