实验环境:

server1   172.25.254.1      做nginx,用户访问的入口
server2   172.25.254.2		做redis,缓存数据
server3   172.25.254.3		做mysql,真实存储数据

原理:

用 redis 用来缓存热点数据,来降低mysql的访问压力,80%的访问都集中在20%的数据上,所以我们把这20%的数据放到 redis 中。

访问流程:

client --> app(tomcat..) --> redis -> mysql -> redis -> client
如果redis中没有,就先取mysql中缓存到redis中,在从redis中读取,返回给客户端

redis从Mysql中缓存

server1中:

我们编译安装nginx-1.18,

vim auto/cc/gcc
在里面注释掉dubug,使安装更小更快
yum install -y pcre-devel         安装依赖性
yum install zlib-devel -y
./configure --prefix=/usr/local/nginx    预编译
make && make install

配置一下:nginx.conf

nginx 读取redis缓存 nginx加redis_mysql


nginx 读取redis缓存 nginx加redis_nginx 读取redis缓存_02


打开php功能模块.

/usr/local/nginx/sbin/nginx    启动

测试访问:

nginx 读取redis缓存 nginx加redis_nginx 读取redis缓存_03


配置一个php页面,

nginx 读取redis缓存 nginx加redis_nginx_04


但是我们当前不能解析php,我们需要去配置php的一些插件:

nginx 读取redis缓存 nginx加redis_nginx_05


yum install 安装。

nginx 读取redis缓存 nginx加redis_mysql_06


启动php,9000端口和80端口打开

测试访问php页面:

nginx 读取redis缓存 nginx加redis_mysql_07


访问成功,更改index.php文件内容为:

<?php
        $redis = new Redis();
        $redis->connect('172.25.254.2',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('172.25.254.3','redis','caoaoyuan');
                        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>";
        }
?>

server2中:

配置redis,并启动。

server3中:

安装mariadb,并启动。

yum install -y mariadb-server
systemctl start mariadb.service

登陆数据库,

MariaDB [(none)]> create database test;
Query OK, 1 row affected (0.00 sec)
#创建一个test的表,因为我们server1上index.php 访问的就是test的表,可去上面查看

MariaDB [(none)]> grant all on test.* to redis@'%' identified by 'caoaoyuan';
Query OK, 0 rows affected (0.00 sec)
#授权用户

MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.00 sec)
#刷新授权表

测试

我们给数据库导入一些数据进行测试

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 ;
mysql -pcaoaoyuan < test.sql      导入

访问:

nginx 读取redis缓存 nginx加redis_nginx_08


代表这些数据时从mysql获取的,我们在刷新一次:

nginx 读取redis缓存 nginx加redis_nginx_09


就说明这些数据时从redis获取的,说明第一次将数据存储到了redis,第二次直接从redis中读取。

而且:

nginx 读取redis缓存 nginx加redis_nginx 读取redis缓存_10


我们的redis刚才是没有数据的,这时可以获取到缓存的数据了。

如果Mysql中的数据进行了变更,这时我们redis的数据会不会同时更新那?

MariaDB [test]> update test set name='westos' where id=1;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0
更新数据

访问:

nginx 读取redis缓存 nginx加redis_mysql_11


nginx 读取redis缓存 nginx加redis_nginx_12


没有更新。除非我们手动更新。

redis 同步mysql

所以我们应定时同步mysql的数据。这时我们用到了gearmand服务。使用udf的方式。

在server1中:

nginx 读取redis缓存 nginx加redis_mysql_13


我们在server3数据库中配置,当数据更新时会触发触发器,触发器会触发mysql中的 udf(user defined function) 插件,这个擦件会调用gearman的服务。它会调用 worker 再通过worker 调用php-pecl-redis,从而把数据同步到redis中。

一个由Gearman驱动的应用程序由三部分组成:一个客户端、一个worker和一个job服务器。客户端负责创建要运行的作业并将其发送到作业服务器。作业服务器将找到一个合适的工作者,可以运行作业并转发作业。worker执行客户机请求的工作,并通过作业服务器向客户机发送响应。Gearman提供了客户端和工作者api,您的应用程序可以调用这些api与Gearman作业服务器(也称为gearmand)进行对话,因此您不需要处理作业的联网或映射。在内部,gearman客户端和工作api使用TCP套接字与作业服务器进行通信。

我们的server1就相当于job服务器,用来分发任务,在server3充当客户端。

启动服务,会开启一个4730的端口

nginx 读取redis缓存 nginx加redis_数据库_14


server3中:

安装插件,

yum install mariadb-devel.x86_64 -y          否则不支持 udf

nginx 读取redis缓存 nginx加redis_redis_15


编译这个用户自定义函数:

gcc $(mysql_config --cflags) -shared -fPIC -o lib_mysqludf_json.so lib_mysqludf_json.c
cp lib_mysqludf_json.so /usr/lib64/mysql/plugin/
把它放到数据库的默认插件目录中去。

现在我们注册刚才编译的 udf函数,我们创建一个json对象,把数据全部映射为json格式,因为json格式可读性比较强。

MariaDB [(none)]> CREATE FUNCTION json_object RETURNS STRING SONAME 'lib_mysqludf_json.so';
Query OK, 0 rows affected (0.00 sec)
yum install libevent-devel-2.0.21-4.el7.x86_64.rpm libgearman-1.1.12-18.el7.x86_64.rpm libgearman-devel-1.1.12-18.el7.x86_64.rpm -y
安装一些gearman的库文件和依赖性

编译安装gearman-mysql-udf插件:

nginx 读取redis缓存 nginx加redis_mysql_16

./configure --libdir=/usr/lib64/mysql/plugin --with-mysql
make && make install

这样我们就在server3中安装好了需要的插件。
注册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';

nginx 读取redis缓存 nginx加redis_数据库_17


总体就是这三个函数。

查找服务:

nginx 读取redis缓存 nginx加redis_nginx_18


然后我们需要去创建触发器。

nginx 读取redis缓存 nginx加redis_数据库_19


与上次不同。

导入到mysql;

[root@server3 ~]# mysql -pcaoaoyuan < test.sql

nginx 读取redis缓存 nginx加redis_nginx_20


这就是我们刚定义的触发器。

现在我们在server1中去配置worker端,谁去执行这个同步的任务。

vim worker.php
<?php
$worker = new GearmanWorker();
$worker->addServer();
$worker->addFunction('syncToRedis', 'syncToRedis');

$redis = new Redis();
$redis->connect('172.25.254.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);
}
?>
[root@server1 rhel7]# cp worker.php /usr/local/

我们可以直接用php执行,这个进程会一直运行worker.php,会一直从redis中同步数据
[root@server1 rhel7]# nohup php /usr/local/worker.php &> /dev/null & 
[1] 9080

测试:
在server3中:

# 更新数据
MariaDB [test]> update test set name='westos' where id=2;
Query OK, 1 row affected (0.29 sec)
Rows matched: 1  Changed: 1  Warnings: 0
# 查看
MariaDB [test]> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | westos |
|  2 | westos |
|  3 | test3  |
|  4 | test4  |
|  5 | test5  |
|  6 | test6  |
|  7 | test7  |
|  8 | test8  |
|  9 | test9  |
+----+--------+

然后我们测试访问:

nginx 读取redis缓存 nginx加redis_redis_21


就同步成功了。1不会更新,开启gearman后 2就更新了

  • 原理
  • mysql的变更触发 trigger ,然后 json_map插件,将变更格式化为json格式,
  • 然后找到 mysql 的 gearman 和 udf 的插件 ,然后找到 gearman(job-server / server1)
  • 然后 jojb-server 将任务分发给 worker。
  • worker 就和 php-gearman 和 php-redis 通信,将数据同步到server2上的 redis,redis 再发送到客户端,同时同步到本地缓存。