一.前言
对一个关系型数据库进行调优以获得高查询性能可能会比较困难。如果对数据模型优化和对查询调优不起作用,DBA就可以使用缓存系统,比如Redis,它是一个可以提供内存和永久数据存储的键值数据存储系统。
由于Redis能够将数据快速读写至数据存储系统,比起关系型数据库它更具性能优势。但是键值数据存储比较简单,它们没有类似SQL那样的查询语言或是结构化数据模型。取而代之的是,它们包含用键作为标识符并与值相关联的一个简单字典或是哈希模型。DBA可以通过这些键来存储和检索值。
键值存储简单而又快速,这使得它们可以很好地匹配关系型数据库丰富的数据模型和查询功能。有时使用键值和关系型数据库的组合是更好的选择。另外,还有大量支持商业化的键值数据库,包括Redis,Riak和Areospike。
要运行Redis缓存来优化常用查询的性能,首先要对你想要缓存的查询结果进行识别。将注意力集中在那些最频繁使用和耗时的查询上,然后从你要缓存的查询中识别数据。简言之,就是缓存一个查询返回的所有字段值。
流程图如下所示:
client——>app——>redis——>mysql——>redis——>client
二.基于lamp架构mysql的缓存服务器
实验环境:
主机 | 服务 |
---|---|
server1 | httpd,php等 |
server2 | redis服务器 |
server3 | mysql数据库 |
前提:
因为这之前是做过redis 高可用的。所以要将之前打开的redis-server的进程杀掉或者关闭
server2关闭主要是为了让它从slave变为master
server3也关闭
实验步骤如下所示:
第一步:配置server1和server2
- server1:
server1上面安装相应的软件
编辑server1httpd的发布文件(我这里有已经写好的,上传到httpd的默认发布目录下,改改就好了)
test.php文件的内容如下
<?php
$redis = new Redis();
$redis->connect('127.0.0.1',6379) or die ("could net connect redis server");
# $query = "select * from test limit 9";
$query = "select * from test";
for ($key = 1; $key < 10; $key++)
{
if (!$redis->get($key))
{
$connect = mysql_connect('127.0.0.1','redis','westos');
mysql_select_db(test);
$result = mysql_query($query);
//如果没有找到$key,就将该查询sql的结果缓存到redis
while ($row = mysql_fetch_assoc($result))
{
$redis->set($row['id'],$row['name']);
}
$myserver = 'mysql';
break;
}
else
{
$myserver = "redis";
$data[$key] = $redis->get($key);
}
}
echo $myserver;
echo "<br>";
for ($key = 1; $key < 10; $key++)
{
echo "number is <b><font color=#FF0000>$key</font></b>";
echo "<br>";
echo "name is <b><font color=#FF0000>$data[$key]</font></b>";
echo "<br>";
}
?>
修改test.php文件
开启httpd服务
[root@server1 ~]# systemctl start httpd
- server2
因为server2之前是slave,所以我们需要将以下内容恢复,让其恢复为master
[root@server2 ~]# vim /etc/redis/6379.conf
bind 0.0.0.0 #第70行的监听所有端口的内容不用动
#replicaof 172.25.27.1 6379
#min-replicas-to-write 1 #注释458和459行
#min-replicas-max-lag 10
[root@server2 ~]# /etc/init.d/redis_6379 start
[root@server2 ~]# redis-cli info
第二步:配置server3
[root@server3 ~]# yum install mariadb-server -y
[root@server3 ~]# systemctl start mariadb
[root@server3 ~]# mysql #查看是否有test这个库,没有的话,创建
编写test数据库的测试文件(test.sql),并将其导入到test数据库中
test.sql文件内容如下所示:
use test;
CREATE TABLE `test` (`id` int(7) NOT NULL AUTO_INCREMENT, `name` char(8) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test` VALUES (1,'test1'),(2,'test2'),(3,'test3'),(4,'test4'),(5,'test5'),(6,'test6'),(7,'test7'),(8,'test8'),(9,'test9');
#DELIMITER $$
#CREATE TRIGGER datatoredis AFTER UPDATE ON test FOR EACH ROW BEGIN
# SET @RECV=gman_do_background('syncToRedis', json_object(NEW.id as `id`, NEW.name as `name`));
# END$$
#DELIMITER ;
[root@server3 ~]# mysql < test.sql
[root@server3 ~]# mysql #进去查看是否导入成功
添加授权用户:
[root@server3 ~]# mysql
MariaDB [(none)]> grant all on test.* to 'redis'@'%' identified by 'westos';
第三步:server1添加redis模块
- server1
此时的php只有mysql的模块,没有redis的模块,这样就无法将其联系起来 ,如下图所示:
此时去浏览器访问,是空白的
所以我们需要编译php的redis模块,首先将所需的安装包发送给server1
解压:
安装devel库
预编译:我们可以用./configure --help查看帮助
我们进行以下操作来将redis模板加入进去:
./configure --enable-redis
make
make install
cd /usr/lib64/php/modules
vim /etc/php.ini #修改时区
cp mysql.ini redis.ini
vim redis.ini
extension=redis.so #加载 redis 模块(这个也可以在/etc/php.ini里面添加)
systemctl restart httpd
php -m | grep redis #此时有了redis模板
第四步:测试
在浏览器输入172.25.27.1/test.php
也可以使用redis进行测试(要客户端先去访问,redis拿到数据之后在测试):
结论:看到了test数据库中的内容,宣告实验成功
但是此时有一个问题,我们修改了数据库里面的内容,redis已经缓存,所以客户端取到的还是原本旧的内容,redis并没有同步更新,只有将redis的缓存删掉,让其重新去数据库取数据,客户端才能得到更新的数据。
如下图所示:
修改数据库内容
此时客户端去访问,还是原本的数据
因为redis的数据没更新:
将redis缓存删除,在让客户端访问,此时数据就更新了
此时是redis去取数据:
因为此时redis重新去取了数据:
总结:到这里,我们已经实现了 redis 作为 mysql 的缓存服务器,但是如果更新了 mysql,redis中仍然会有对应的 KEY,数据就不会更新,此时就会出现 mysql 和 redis 数据不一致的情况。所以接下来就要通过 mysql 触发器将改变的数据同步到 redis 中。
因为我们总不能每次更新数据就手动删除redis里面的数据,那也忒麻烦了,为了解决这个问题,我们配置gearman实现数据同步(也就是mysql的触发器)
三.配置gearman实现数据同步
前言:
Gearman+PHP+MySQL UDF的组合异步实现MySQL到Redis的数据复制。
用redis作为Mysql数据库的缓存,在查找的时候,首先查找redis缓存,如果找到则返回结果;如果在redis中没有找到,那么查找Mysql数据库,找到的花则返回结果并且更新redis;如果没有找到则返回空。对于写入的情况,直接写入mysql数据库,mysql数据库通过触发器及UDF机制自动把变更的内容更新到redis中。采用MySQL作为数据存储引擎,Redis则作为Cache。
mysql读写数据都需要从磁盘读取。磁盘的容量,带宽的大小就影响了网站的访问速度,读取的方式,也就是sql语句,次数和效率也会影响读取效率。
redis和mc都是缓存,并且都是驻留在内存中运行的,这大大提升了高数据量web访问的访问速度。然而mc只是提供了简单的数据结构,比如
string存储;redis却提供了大量的数据结构,比如string、list、set、hashset、sorted set这些,这使得用户方便了好多,毕竟封装了一层实用的功能,同时实现了同样的效果,当然用redis而慢慢舍弃mc。
这个实验的实现需要使用新的工具gearmand
Gearman是一套用来把程式需求委派给机器,提供通用的程序框架来将任务分发在机器运算。它同时具备并行工作的能力、负载均衡处理的能力,以及在不同程序语言之间沟通的能力。
运行过程:
一个Gearman请求的处理过程涉及三个角色:Client -> Job -> Worker。 Client:请求的发起者,可以是 C,PHP,Perl,MySQL UDF 等等。 Job:请求的调度者,用来负责协调把 Client 发出的请求转发给合适的 Worker。 Worker:请求的处理者,可以是 C,PHP,Perl 等等。 因为 Client,Worker 并不限制用一样的语言,所以有利于多语言多系统之间的集成。 甚至我们通过增加更多的 Worker,可以很方便的实现应用程序的分布式负载均衡架构。
大致流程:
下面要编写的 mysql 触发器,就相当于 Gearman 的客户端。修改表,插入表就相当于直接下发任务。然后通过 lib_mysqludf_json UDF 库函数将关系数据映射为 JSON 格式,然后再通过 gearman-mysql-udf
插件将任务加入到 Gearman 的任务队列中,最后通过redis_worker.php,也就是 Gearman 的 worker 端来完成 redis 数据库的更新。
实际的工作流程:
mysql(client)——>gearmand:4730(job server)——>worker(php/python/java)
实验过程如下所示:
第一步:server3安装插件
将test.mysql文件不用的部分注释,将触发器部分的注释取消(这一步也可以放到后面做):
此时导入数据库是失败的,因为缺少模块,如下图:
我们需要编译数据库添加插件,使用lib_mysqludf_json
的原因是因为Gearman只接受字符串作为入口参数,可以通过lib_mysqludf_json
UDF 库函数将关系数据映射为 JSON 格式(MySQL中的数据编码为JSON字符串,起到统一标准的作用)。编译数据库需要mariadb-devel和gcc。
[root@server3 ~]# yum install unzip -y
[root@server3 ~]# unzip lib_mysqludf_json-master.zip
[root@server3 ~]# cd lib_mysqludf_json-master
[root@server3 ~]# yum install gcc -y
[root@server3 ~]# yum install mariadb-devel -y
[root@server3 lib_mysqludf_json-master]# gcc $(mysql_config --cflags) -shared -fPIC -o lib_mysqludf_json.so lib_mysqludf_json.c
查看 :
[root@server3 lib_mysqludf_json-master]# mysql -uroot -proot
MariaDB [(none)]> show global variables like 'plugin_dir';
拷贝 lib_mysqludf_json.so 模块到mysql的插件目录
[root@server3 lib_mysqludf_json-master]# cd /usr/lib64/mysql/plugin/
[root@server plugin]# cp ~/lib_mysqludf_json-master/lib_mysqludf_json.so .
数据库里注册 UDF 函数并查看
[root@server3 lib_mysqludf_json-master]# mysql -uroot -proot
MariaDB [(none)]> CREATE FUNCTION json_object RETURNS STRING SONAME 'lib_mysqludf_json.so;
MariaDB [(none)]> select * from mysql.func;
第二步:安装与配置 gearman
安装gearmand-1.1.12-18并且其对应的依赖包:
- libgearman-1.1.12-18.el7.x86_64.rpm
- libgearman-devel-1.1.12-18.el7.x86_64.rpm
- ibevent-devel-2.0.21-4.el7.x86_64.rpm
将这下载好的这几个包上传给虚拟机:
[root@server3 ~]# yum install gearmand-* libevent-devel-2.0.21-4.el7.x86_64.rpm libgearman-* -y
[root@server3 ~]#systemctl start gearmand
[root@server3 ~]#netstat -antlp |grep gearmand
编译 gearman-mysql-udf
这个插件是用来管理调用 Gearman 的分布式的队列。
下载gearman-mysql-udf对应的软件包:
- gearman-mysql-udf-0.6.tar.gz
步骤如下:
[root@server3 ~]# tar zxf gearman-mysql-udf-0.6.tar.gz
[root@server3 ~]# cd gearman-mysql-udf-0.6
[root@server3 gearman-mysql-udf-0.6]# ls
[root@server3 gearman-mysql-udf-0.6]# ./configure --libdir=/usr/lib64/mysql/plugin/ --with-mysql #预编译
[root@server3 gearman-mysql-udf-0.6]# make && make install #编译与安装
注册 UDF 函数并查看
[root@server3 ~]# mysql -uroot -proot
#注册 UDF 函数:
MariaDB [(none)]> CREATE FUNCTION gman_do_background RETURNS STRING SONAME 'libgearman_mysql_udf.so';
MariaDB [(none)]> CREATE FUNCTION gman_servers_set RETURNS STRING SONAME 'libgearman_mysql_udf.so';
#查看函数:
MariaDB [(none)]> select * from mysql.func;
指定 gearman 的服务信息(这一步可以在上一步数据库里一起做)
root@server3 ~]# mysql
MariaDB [(none)]> SELECT gman_servers_set('172.25.27.1:4730');
编写 mysql 触发器test.mysql(我们前面已经做过),将其导入数据库:
[root@server3 ~]# mysql < test.sql
[root@server3 ~]# mysql
MariaDB [(none)]> SHOW TRIGGERS FROM test;
第三步:配置server1
安装 gearman(与server3过程一样,这里就粗略展示)
安装php的gearman 扩展
此时php没有gearman模块
进行以下操作添加:
tar zxf gearman-1.1.2.tgz
cd gearman-1.1.2
phpize
./configure
make && make install
编译完成界面:
cd /usr/lib64/php/modules #查看是否有gearman模块
cd /etc/php.d
cp redis.ini gearman.ini
vim gearman.ini
在里面写入:
extension=gearman.so
systemctl restart httpd
php -m | grep gearman
编写 gearman 的 worker 端——worker.php文件
worker.php文件内容如下:
<?php
$worker = new GearmanWorker();
$worker->addServer();
$worker->addFunction('syncToRedis', 'syncToRedis');
$redis = new Redis();
$redis->connect('172.25.27.2', 6379);
while($worker->work());
function syncToRedis($job)
{
global $redis;
$workString = $job->workload();
$work = json_decode($workString);
if(!isset($work->id)){
return false;
}
$redis->set($work->id, $work->name);
}
?>
将修改好的worker.php放到/usr/local下,并后台运行
mv worker.php /usr/local
nohup php /usr/local/worker.php &
查看服务进程:
查看服务端口:
第四步:测试
更新数据库里面的数据
[root@server3 ~]# mysql -uroot -proot
MariaDB [(none)]> use test;
MariaDB [test]> update test set name='redhat' where id=2;
MariaDB [test]> update test set name='hanghang' where id=3;
再次更新:
大量更新:
redis里面的数据也改变了