昨天我们说了如何把 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系统优化之客户信息、联系人信息缓存之主从同步》。