对于变化频率非常快的数据来说,如果还选择传统的静态缓存方式(Memocached、File System等)展示数据,可能在缓存的存取上会有很大的开销,并不能很好的满足需要,而Redis这样基于内存的NoSQL数据库,就非常适合担任实时数据的容器。
但是往往又有数据可靠性的需求,采用MySQL作为数据存储,不会因为内存问题而引起数据丢失,同时也可以利用关系数据库的特性实现很多功能。
所以就会很自然的想到是否可以采用MySQL作为数据存储引擎,Redis则作为Cache。而这种需求目前还没有看到有特别成熟的解决方案或工具,因此采用Gearman+PHP+MySQL UDF的组合异步实现MySQL到Redis的数据复制。
MySQL到Redis数据复制方案
无论MySQL还是Redis,自身都带有数据同步的机制,比较常用的MySQL的Master/Slave模式,就是由Slave端分析Master的binlog来实现的,这样的数据复制其实还是一个异步过程,只不过当服务器都在同一内网时,异步的延迟几乎可以忽略。
那么理论上也可以用同样方式,分析MySQL的binlog文件并将数据插入Redis。但是这需要对binlog文件以及MySQL有非常深入的理解,同时由于binlog存在Statement/Row/Mixedlevel多种形式,分析binlog实现同步的工作量是非常大的。
因此这里选择了一种开发成本更加低廉的方式,借用已经比较成熟的MySQL UDF,将MySQL数据首先放入Gearman中,然后通过一个自己编写的PHP Gearman Worker,将数据同步到Redis。比分析binlog的方式增加了不少流程,但是实现成本更低,更容易 *** 作。
Gearman的安装与使用
Gearman是一个支持分布式的任务分发框架。设计简洁,获得了非常广泛的支持。一个典型的Gearman应用包括以下这些部分:
Gearman Job Server:Gearman核心程序,需要编译安装并以守护进程形式运行在后台
Gearman Client:可以理解为任务的收件员,比如在后台执行一个发送邮件的任务,可以在程序中调用一个Gearman Client并传入邮件的信息,然后就可以将执行结果立即展示给用户,而任务本身会慢慢在后台运行。
Gearman Worker:任务的真正执行者,一般需要自己编写具体逻辑并通过守护进程方式运行,Gearman Worker接收到Gearman Client传递的任务内容后,会按顺序处理。
以前曾经介绍过类似的后台任务处理项目Resque。两者的设计其实非常接近,简单可以类比为:
Gearman Job Server:对应Resque的Redis部分
Gearman Client:对应Resque的Queue *** 作
Gearman Worker:对应Resque的Worker和Job
这里之所以选择Gearman而不是Resque是因为Gearman提供了比较好用的MySQL UDF,工作量更小。
安装Gearman及PHP Gearman扩展
以下均以Ubuntu12.04为例。
apt-get install gearman gearman-server libgearman-dev
检查Gearman的运行状况:
/etc/init.d/gearman-job-server status
* gearmand is running
说明Gearman已经安装成功。
PHP的Gearman扩展可以通过pecl直接安装
pecl install gearman
echo "extension=gearman.so">/etc/php5/conf.d/gearman.ini
service php5-fpm restart
但是实测发现ubuntu默认安装的gearman版本过低,直接运行pecl install gearman会报错
configure: error: libgearman version 1.1.0or later required
因此Gearman + PHP扩展建议通过编译方式安装,这里为了简单说明,选择安装旧版本扩展:
pecl install gearman-1.0.3
Gearman + PHP实例
为了更容易理解后文Gearman的运行流程,这里不妨从一个最简单的Gearman实例来说明,比如要进行一个文件处理的 *** 作,首先编写一个Gearman Client并命名为client.php:
<?php
$client =newGearmanClient()
$client->addServer()
$client->doBackground('writeLog','Log content')
echo '文件已经在后台 *** 作'
运行这个文件,相当于模拟用户请求一个Web页面后,将处理结束的信息返回用户:
php client.php
查看一下Gearman的状况:
(echo status sleep 0.1)| netcat127.0.0.14730
可以看到输出为
writeLog 100.
说明已经在Gearman中建立了一个名为writeLog的任务,并且有1个任务在队列等待中。
而上面的4列分别代表当前的Gearman的运行状态:
任务名称
在等待队列中的任务
正在运行的任务
正在运行的Worker进程
可以使用watch进行实时监控:
watch -n 1"(echo statussleep 0.1) | nc 127.0.0.1 4730"
然后我们需要编写一个Gearman Worker命名为worker.php:
<?php
$worker =newGearmanWorker()
$worker->addServer()
$worker->addFunction('writeLog','writeLog')while($worker->work())function writeLog($job){
$log = $job->workload()file_put_contents(__DIR__ .'/gearman.log', $log ."\n", FILE_APPEND | LOCK_EX)}
Worker使用一个while死循环实现守护进程,运行
php worker.php
可以看到Gearman状态变为:
writeLog 001
同时查看同目录下gearman.log,内容应为从Client传入的值Log content。
通过MySQL UDF + Trigger同步数据到Gearman
MySQL要实现与外部程序互通的最好方式还是通过MySQL UDF(MySQL user defined functions)来实现。为了让MySQL能将数据传入Gearman,这里使用了lib_mysqludf_json和gearman-mysql-udf的组合。
安装lib_mysqludf_json
使用lib_mysqludf_json的原因是因为Gearman只接受字符串作为入口参数,可以通过lib_mysqludf_json将MySQL中的数据编码为JSON字符串
apt-get install libmysqlclient-dev
wget https://github.com/mysqludf/lib_mysqludf_json/archive/master.zip
unzip master.zip
cd lib_mysqludf_json-master/
rm lib_mysqludf_json.so
gcc $(mysql_config --cflags)-shared -fPIC -o lib_mysqludf_json.so lib_mysqludf_json.c
可以看到重新编译生成了 lib_mysqludf_json.so 文件,此时需要查看MySQL的插件安装路径:
mysql -u root -pPASSWORD --execute="show variables like '%plugin%'"+---------------+------------------------+|Variable_name|Value|+---------------+------------------------+| plugin_dir |/usr/lib/mysql/plugin/|+---------------+------------------------+
然后将 lib_mysqludf_json.so 文件复制到对应位置:
cp lib_mysqludf_json.so /usr/lib/mysql/plugin/
最后登入MySQL运行语句注册UDF函数:
CREATE FUNCTION json_object RETURNS STRING SONAME 'lib_mysqludf_json.so'
安装gearman-mysql-udf
方法几乎一样:
apt-get install libgearman-dev
wget https://launchpad.net/gearman-mysql-udf/trunk/0.6/+download/gearman-mysql-udf-0.6.tar.gz
tar -xzf gearman-mysql-udf-0.6.tar.gz
cd gearman-mysql-udf-0.6./configure --with-mysql=/usr/bin/mysql_config
-libdir=/usr/lib/mysql/plugin/
make &&make install
登入MySQL运行语句注册UDF函数:
CREATE FUNCTION gman_do_background RETURNS STRING SONAME 'libgearman_mysql_udf.so'
CREATE FUNCTION gman_servers_set RETURNS STRING SONAME 'libgearman_mysql_udf.so'
最后指定Gearman服务器的信息:
SELECT gman_servers_set('127.0.0.1:4730')
通过MySQL触发器实现数据同步
最终同步哪些数据,同步的条件,还是需要根据实际情况决定,比如将数据表data的数据在每次更新时同步,那么编写Trigger如下:
DELIMITER $$
CREATE TRIGGER datatoredis AFTER UPDATE ON data
FOR EACH ROW BEGIN
SET @ret=gman_do_background('syncToRedis', json_object(NEW.id as`id`, NEW.volume as`volume`))END$$
DELIMITER
尝试在数据库中更新一条数据查看Gearman是否生效。
Gearman PHP Worker将MySQL数据异步复制到Redis
Redis作为时下当热的NoSQL缓存解决方案无需过多介绍,其安装及使用也非常简单:
apt-get install redis-server
pecl install redis
echo "extension=redis.so">/etc/php5/conf.d/redis.ini
然后编写一个Gearman Worker:redis_worker.php
#!/usr/bin/env php<?
$worker =newGearmanWorker()
$worker->addServer()
$worker->addFunction('syncToRedis','syncToRedis')
$redis =newRedis()
$redis->connect('127.0.0.1',6379)while($worker->work())function syncToRedis($job){global $redis
$workString = $job->workload()
$work = json_decode($workString)if(!isset($work->id)){returnfalse}
$redis->set($work->id, $workString)}
最后需要将Worker在后台运行:
nohup php redis_worker.php &
通过这种方式将MySQL数据复制到Redis,经测试单Worker基本可以瞬时完成。
MySQL 默认的复制就是异步的,主库再执行完客户端提交的事务后会立即将结果返回给客户端,并不关系从库是否已经接收和处理。
MySQL主库将Binlog事件写入到Binlog文件中,此时主库只通知一下Dump线程发送这些新的Binlog,然后主库继续处理提交 *** 作,不会保证这些Binlog传到任何一个从库节点上。
因为异步复制,主节点不关从节点是否收到Binlog,如果主crash掉了,此时主节点上已提交的事务可能并没有传到从库上,如果此时,强行将从节点提升为主节点,可能导致新的主节点上数据不完整。
全同步是指当主库接收到客户端的一个事务请求,所有的从库都执行了该事务才返回给客户端。
当主库收到客户端提交的事务后,所有的从库必须收到并且执行事务,然后主库才会执行后续 *** 作。
因为要等待所有从库执行完事务,主库才将结果返回给客户端,所以全同步复制的性能必然受到严重影响,即完成一个事务的时间被拉长,性能降低。
半同步复制是介于全同步复制和全异步复制之间的一种,主库只需要等待至少一个从库节点收到并Flush Binlog到Relay log文件即可,主库不需要等待所有从库给主库反馈。(注意只要收到一个从库的反馈即可)
介于异步复制和全同步复制之间,主库再执行完客户端提交的食物后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。
相对于异步复制,半同步复制提交了数据的安全性,同时它也造成了一定程序的延迟,这个延迟至少是一个TCP/IP往返时间,因此,半同步复制虽好在低延时的网络中使用。
XMind - Trial Version
我们在数据库中存储的是无模式的属性集(例如JSON对象或python字典)。存储的记录只需一个名为id的16字节的UUID属性。对数据库而言实体的其他部分是不可见的。我们可以简单地存入新属性来改变schema(可以简单理解为数据表中只有两个字段:id,data其中data存储的是实体的属性集)。我们通过保存在不同表中的索引来检索数据。如果想检索每个实体中的三个属性,我们就需要三个数据表-每个表用于检索某一特定属性。如果不想再用某一索引了,我们要在代码中停止该索引对应表的写 *** 作,并可选地删除那个表。如果想添加个新索引,只需要为该索引新建个MySQL表,并启动一个进程异步地为该表添加索引数据(不影响运行中的服务)。
最终,虽然我们的数据表增多了,但添加和删除索引却变得简单了。我们大力改善了添加索引数据的进程(我们称之为“清洁工")使其在快速添加索引的同时不会影响站点。我们可以在一天内完成新属性的保存和索引,并且我们不需要对调主从MySQL数据库,也不需要任何其他可怕的 *** 作。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)