最近写的这些文章都是对在职的时候的一些经验的总结。

说到内存缓存我想很多人肯定会想想到 Redis 和 Memcache 这两个内存数据库。Redis 和 Memcache 的共同之处都是将数据存放在内存中,都支持一主多从,都支持过时设置。

不同之处在于 Memcache 除了k/v类型的数据外还支持缓存其他东西,如图片等。 Redis 不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等更丰富数据结构的存储。 Memcache 挂了后数据会丢失,而Redis则可以通过aof恢复数据(当然,这个恢复是要点时间的)。

两者的使用场景也一般不太一样,Redis 出来作为 NoSQL 数据库使用外,还能用做消息队列、数据堆栈和数据缓存等。 Memcached 适合于缓存 SQL 语句、数据集、用户临时性数据、延迟查询数据和session等。

为什么要做缓存优化?

因为每天早上的8:30 ~ 11:00 下午 2:00 ~ 4:00是员工使用ERP的高峰期,这时候的数据库的读写量很大,然后给 MySQL 就开始很吃力。

这里从数据的稳定性和数据类型的支持程度来看,我们选择使用 Redis ,因为我们需要使用了计数器,队列等功能。

既然是优化,那么我们首先从读写最频繁的数据表开始进行做优化,当前ERP里面的 客户信息(xx_client) 表和 客户联系人(xx_client_contact) 信息表,这两张表的读写是最频繁的。

所以经过衡量,我们觉得把这两张表的信息放到 Redis 里面去。 数据在查询的时候首先从 Redis 里面取数据,当 Redis 没有数据的时候再从 MySQL 里面取数据。 数据的增删改,这个我们维护一套 DB 的解决方案,在数据进行 MySQL 增删改操作的时候,同时对 Redis 进行增删操作。

下面我们来按步骤来开始进行:

如何把 MySQL 的数据转换成为 Redis 里面保存的键值数据呢?

在 MySQL 中保存的数据格式是这样的:

id company_name province city address ……
1 张三的公司 广西 南宁市 西乡塘区总部路一期X栋 ……

我们使用 Redis 该如何存储数据这些数据呢?

这里我们同样依照客户的ID作为标识,从横表转换成为竖表的形式。

client表则是:

client:id
client:id:company_name
client:id:province
client:id:city
client:id:address
……

然后为了达到客户搜索的需求,在 MySQL ,我们是这样来查询的

SELECT id,company_name,province,... FROM xx_client limit 0,100

而在 Redis 我们则可以使用 LRANGE 来查询 list ,进行分页操作.

然后我们如何解决通过客户名称,客户行业等来搜索客户数据的问题呢?

考虑到 Redis 本身不适合做模糊查询等的业务,所以当通过客户名称,客户行业等客户信息查询的时候,我们使用 Elasticsearch 来做搜索服务,把常用的字段放入 Elasticsearch 里面,就可以解决以上的问题了。

当查询详情信息的时候,再根据客户的ID从 Redis 里面取出用户数据。 这样可以大大减轻 MySQL 数据库的压力。

把现有的客户数据和客户联系人的数据导入 Redis

这一步我们需要把大量的数据从 MySQL 导入到 Redis 中去。 经过查询我们的客户数量有 139327 条数据。

由于数据比较多了,所以我们放弃了逐条导入的方式。

经过查询,Redis 2.6+ 版本开始支持 Redis管道(pipeline)导入的形式,这种形式可以批量导入,13万的数据十来秒可以搞定。

预先把命令导入到文本文件中

我们来写一个 php cli 模式下运行的 PHP 文件。 数据有 13W+,所以肯定不能直接 SELECT 查询全部,这里我们使用 非缓冲查询 的方式来查询出数据,避免把查询到全部数据都放到缓冲区中,出现 Fatal error: Allowed memory size of xxxx bytes exhausted 的问题。

//命名rdata.php
<?php
$pdo = new \PDO('mysql:host=127.0.0.1;dbname=vhosts', 'root', 'root');
$pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$result = $pdo->query('SELECT * FROM `wd_client`');
if ($result) {
    while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
        $keys = array_keys($row);
        foreach ($keys as $key) {
            if ($key == 'id') {
                makeCommand('LPUSH', "ids:client", $row['id']);
            }
            $mykey = sprintf("client:contact:%s:%s",$row['id'],$key);
            makeCommand('SET', $mykey , $row[$key]);
        }
    }
}

function makeCommand($command, $key, $value)
{
    print "*3\r\n$" . strlen($command) . "\r\n" . $command . "\r\n$" . strlen($key) . "\r\n" . $key . "\r\n$" . strlen($value) . "\r\n" . $value . "\r\n";
}

批量导入数据到 Redis

使用Redis管道(pipeline)导入的形式。

MrCong@MrCong $ php rdata.php | redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 6687696

数据导入完成,13.9+ 万条客户数据,导出 Redis 后,总共654.8 万+ 个key

到这里客户表的数据导入完成,剩下的就是客户的联系人的表,也相同的模式来进行导入。

到这里发现文章也很长了,我们明天再来写下篇《ERP系统优化之客户信息、联系人信息缓存之DB 的解决方案》。