昨天我们说了如何把 13.9+ 万的客户数据导入 Redis 以及客户联系人的数据导入Redis 服务器去。

今天我们再来说说程序里面如何优化,做到数据在查询的时候首先从 Redis 里面取数据,当 Redis 没有数据的时候再从 MySQL 里面取数据,并缓存。

数据的增删改,这个我们来写一套 DB 的解决方案,在数据进行 MySQL 增删改操作的时候,同时对 Redis 进行增删操作。

一套同时操作 MySQL 和 Redis 的解决方案

关于同时操作操作 MySQL 和 Redis 的方案有很多种,可以按需选择,可选的方案包括但是不限于以下的思路:

我们可以全新开发一套 ORM ,这套ORM包含了操作 MySQL 和 Redis 的CURD同步操作。 利用系统现有的 DB 操作类进行二次封装操作方法,在操作方法里面同时进行操作 MySQL 和 Redis 的 CURD 的同步操作。 如果系统本身使用了开源框架来开发的,或者本身已经包含了 ORM 的,那么直接使用现有系统的 ORM 进行二次封装,重写 CURD 等方法,让其支持 MySQL 和 Redis 同步操作。 由于我们的ERP系统使用的是ThinkPHP框架来开发的,这里我们可以使用ThinkPHP自带的ORM来进行二次封装。

首先新建客户信息的数据模型,ClientModel.class.php ,然后在模型里面我们重写 CURD 方法。

缓存处理类

<?php

namespace Client\Api;

use Predis\Client;
use Think\Log;

class ClientCache
{
    protected $redis = null;
    protected $listName = 'ids:client';
    protected $prefix = 'client:contact:%s:';

    public function __construct()
    {
        if ($this->redis == null) {
            $server = [
                'host' => C('REDIS.HOST'),
                'PORT' => C('REDIS.PORT'),
                'DATABASE' => C('REDIS.DATABASE'),
            ];
            $this->redis = new Client($server);
        }
    }

    public function select($id)
    {
        if (empty($id)) {
            return [];
        }
        $data = [];
        $prefix = sprintf($this->prefix, $id);
        $keyNames = $this->redis->keys($prefix . '*');
        sort($keyNames);
        $info = $this->redis->mget($keyNames);
        foreach ($keyNames as $index => $key) {
            $newKey = str_replace($prefix, '', $key);
            $data[$newKey] = $info[$index];
        }
        unset($keyNames, $info);
        return $data;
    }

    public function add($client = array(), $db = 0)
    {
        if (!is_array($client)) {
            return '';
        }
        if ($db != 0) {
            $this->redis->select($db);
        }
        $dict = $this->transform($client);
        $this->redis->lpush($this->listName, $client['id']);
        $ret = $this->redis->msetnx($dict);
        Log::write('Redis Write Client info ID : ' . $ret, Log::INFO);
        return true;
    }

    public function delete($id)
    {
        $prefix = sprintf($this->prefix, $id);
        $keyNames = $this->redis->keys($prefix . '*');
        $ret = $this->redis->del($keyNames);
        $lret = $this->redis->lrem($this->listName, 1, $id);
        Log::write('Redis Delete Client info ID : ' . $ret . ',list: ' . $lret, Log::INFO);
        return $ret;
    }

    public function save($client = array(), $db = 0)
    {
        if (!is_array($client)) {
            return '';
        }
        if ($db != 0) {
            $this->redis->select($db);
        }
        $dict = $this->transform($client);
        $ret = $this->redis->mset($dict);
        Log::write('Redis Update Client info ID : ' . $ret, Log::INFO);
    }

    private function transform($data = array())
    {
        $keys = array_keys($data);
        $dict = [];
        foreach ($keys as $key) {
            $keyName = sprintf($this->prefix . "%s", $data['id'], $key);
            $dict[$keyName] = is_null($data[$key]) ? '' : $data[$key];
            unset($keyNames);
        }
        return $dict;
    }
}

模型类

我们在模型类里面重写CURL方法,然后执行 ClientCache 类的相应操作方法后再去执行模型类的CURL方法。这样既可做到数据新增、更新、删除客源同时操作 MySQL 和 Redis ,数据查询的时候,也可以先从 Redis 查询,当Redis没有数据才从数据库从库去查询数据。

<?php

namespace Client\Model;

use Client\Api\ClientCache;
use Think\Model;

class ClientModel extends Model
{
    protected $clientCache = NULL;

    public function _initialize()
    {
        parent::_initialize();
        if ($this->clientCache == NULL) {
            $this->clientCache = new ClientCache();
        }
    }

    public function select($options = array())
    {
        $clientId = isset($options['id']) ? $options['id'] : '';
        $clientInfo = $this->clientCache->select($clientId);
        if (empty($clientInfo)) {
            #Redis 命中失败,从数据库查询
            $clientInfo = parent::select($options);
        }
        return $clientInfo;
    }

    public function save($data = '', $options = array())
    {
        $this->clientCache->save($data);
        return parent::save($data, $options);
    }

    public function add($data = '', $options = array(), $replace = false)
    {
        $this->clientCache->add($data);
        return parent::add($data, $options, $replace);
    }

    public function delete($options = array())
    {
        $clientId = isset($options['id']) ? $options['id'] : '';
        $this->clientCache->delete($clientId);
        return parent::delete($options);
    }

}

今天到这里我们这篇文章也完了,我们明天再来写下篇《ERP系统优化之客户信息、联系人信息缓存之主从同步》。