正文
Redis使用
安装扩展
使用Yii2的redis组件,需要先加载依赖,composer.json中引入yii2-redis:
"require": {
// 其他依赖...
"yiisoft/yii2-redis": "~2.0.0",
},
再执行下面命令安装:
composer update
或者也可以在 Yii2 项目根目录,执行以下命令安装:
composer require yiisoft/yii2-redis
基本使用
添加配置:
'components' => [
'redis' => [
'class' => 'yii\redis\Connection',
'hostname' => 'test.redis.yuming.com',
'password' => 'abc123!@#',
'port' => 6379,
'database' => 23,
],
...
],
"params" => [
'redisKeyPrefix' => 'lzapi_dev_',
...
],
然后开始使用:
// 获取 redis 组件
$redis = Yii::$app->redis;
// 判断 key 为 username 的是否有值,有则打印,没有则赋值
$key = 'username';
if ($val = $redis->get($key);) {
var_dump($val);
} else {
$redis->set($key, 'xiaozhang');
$redis->expire($key, 5 * 60);
}
配合数据库使用
有时为了提速会把redis作为读取数据源,不过底层数据还是会先在数据库中保存一份。主题思路是更新数据表数据时,同时更新redis中的数据。
看个具体的使用的例子。
common/lib/LRedisActiveRecord.php 文件内容:
<?php
namespace common\lib;
use Yii;
use yii\redis\ActiveRecord;
class LRedisActiveRecord extends ActiveRecord
{
/**
* keyPrefix
* @return string
*/
public static function keyPrefix()
{
return Yii::$app->params['redisKeyPrefix'] .parent::keyPrefix();
}
}
common/models/redis/DepartmentOfRedis.php 文件内容:
<?php
namespace common\models\redis;
use common\lib\LRedisActiveRecord;
use common\models\redis\WorkerOfRedis;
/**
* redis中department模型
* Class DepartmentOfRedis
* @package common\models\redis
*/
class DepartmentOfRedis extends LRedisActiveRecord
{
/**
* 主键 默认为 id
* @return array|string[]
*/
public static function primaryKey()
{
return ['id'];
}
/**
* 模型对应记录的属性列表
*
* @return array
*/
public function attributes()
{
return ['id', 'parent_id', 'sort_num', 'name', 'description', 'from_type', 'is_deleted',
'created_time', 'created_worker_id', 'updated_time', 'updated_worker_id'];
}
/**
* 获取父部门
* @return \yii\db\ActiveQueryInterface
*/
public function getParent()
{
return $this->hasMany(self::className(), ['id' => 'parent_id']);
}
/**
* 获取子部门
* @return \yii\db\ActiveQueryInterface
*/
public function getSon()
{
return $this->hasMany(self::className(), ['parent_id' => 'id']);
}
/**
* 部门有多个员工
*
* @return \yii\db\ActiveQueryInterface
*/
public function getWorkers()
{
return $this->hasMany(WorkerOfRedis::className(), ['department_id' => 'id']);
}
/**
* 写入一条redis的部门信息
* @param $attributes
* @return bool
*/
public static function addToRedis($attributes)
{
$model = new self();
if (!isset($attributes['id'])) {
return false;
} else {
$model->id = $attributes['id'];
}
if (isset($attributes['parent_id'])) {
$model->parent_id = $attributes['parent_id'];
}
if (isset($attributes['sort_num'])) {
$model->sort_num = $attributes['sort_num'];
}
if (isset($attributes['name'])) {
$model->name = $attributes['name'];
}
if (isset($attributes['description'])) {
$model->description = $attributes['description'];
}
if (isset($attributes['from_type'])) {
$model->from_type = $attributes['from_type'];
}
if (isset($attributes['is_deleted'])) {
$model->is_deleted = $attributes['is_deleted'];
}
if (isset($attributes['created_time'])) {
$model->created_time = $attributes['created_time'];
}
if (isset($attributes['created_worker_id'])) {
$model->created_worker_id = $attributes['created_worker_id'];
}
if (isset($attributes['updated_time'])) {
$model->updated_time = $attributes['updated_time'];
}
if (isset($attributes['updated_worker_id'])) {
$model->updated_worker_id = $attributes['updated_worker_id'];
}
return $model->save();
}
/**
* 更新redis的部门信息
* @param $id
* @param $attributes
* @return bool
*/
public static function updateToRedis($id, $attributes)
{
$model = self::findOne($id);
if (empty($model)) {
if (!isset($attributes['id'])) {
$attributes['id'] = $id;
}
return self::addToRedis($attributes);
}
if (isset($attributes['parent_id'])) {
$model->parent_id = $attributes['parent_id'];
}
if (isset($attributes['sort_num'])) {
$model->sort_num = $attributes['sort_num'];
}
if (isset($attributes['name'])) {
$model->name = $attributes['name'];
}
if (isset($attributes['description'])) {
$model->description = $attributes['description'];
}
if (isset($attributes['from_type'])) {
$model->from_type = $attributes['from_type'];
}
if (isset($attributes['is_deleted'])) {
$model->is_deleted = $attributes['is_deleted'];
}
if (isset($attributes['created_time'])) {
$model->created_time = $attributes['created_time'];
}
if (isset($attributes['created_worker_id'])) {
$model->created_worker_id = $attributes['created_worker_id'];
}
if (isset($attributes['updated_time'])) {
$model->updated_time = $attributes['updated_time'];
}
if (isset($attributes['updated_worker_id'])) {
$model->updated_worker_id = $attributes['updated_worker_id'];
}
return $model->save();
}
}
现在看一下最关键的数据表AR类。
common/models/laza/Department.php 文件内容:
<?php
namespace common\models\laza;
use common\lib\LActiveRecord;
use common\models\redis\DepartmentOfRedis;
use Yii;
class Department extends LActiveRecord
{
const EVENT_UPDATE_DEPARTMENT_OF_REDIS = 'update_department_of_redis';
public function init()
{
parent::init();
$this->on(self::EVENT_UPDATE_DEPARTMENT_OF_REDIS, [$this, 'onUpdateDepartmentOfRedis']);
}
public static function getDb()
{
return Yii::$app->db;
}
public static function tableName()
{
return 'department';
}
public function save($runValidation = true, $attributeNames = null)
{
$res = parent::save($runValidation, $attributeNames);
if ($res) {
$this->trigger(self::EVENT_UPDATE_DEPARTMENT_OF_REDIS);
}
return $res;
}
/**
* 更新redis中department事件
* @param $event
* @return bool
* @throws \yii\db\Exception
* @throws \yii\db\StaleObjectException
*/
public function onUpdateDepartmentOfRedis($event)
{
$attributes = $event->sender->getAttributes();
return DepartmentOfRedis::updateToRedis($attributes['id'], $attributes);
}
}
这里使用了事件,每当数据表AR在进行save保存后,就会自动更新redis数据。
看一下写入redis的数据内容。
部门内容:
key:
lzapi_dev_department_of_redis:a:107
value:
from_type:20,
created_time:2018-04-28 11:15:20,
updated_time:2018-08-13 22:10:20,
is_deleted:0,
parent_id:101,
sort_num:0,
name:客服,
description:值班,
updated_worker_id:0,
created_worker_id:0,
id:107
用户内容:
key:
lzapi_dev_worker_of_redis:a:159
value:
from_type:20,
head:https://statics.laza.com/head.png,
org_struct:总公司-销售部-销售1组,
created_time:2018-05-11 14:14:01,
updated_time:2020-07-09 18:06:49,
from_id:973,
chat_uuid:1b5bgde6-d46e-2b02-4c1b-ret90250b6e4,
phone:18411001102,
department_id:387,
nickname:yaoyao,
updated_worker_id:159,
created_worker_id:0,id:159,
position:4,
feature_dpt_id:103,
status:1
源码
yiisoft/yii2-redis/ActiveRecord.php
文件源码:
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use Yii;
use yii\base\InvalidConfigException;
use yii\db\BaseActiveRecord;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;
/**
* ActiveRecord is the base class for classes representing relational data in terms of objects.
*
* This class implements the ActiveRecord pattern for the [redis](http://redis.io/) key-value store.
*
* For defining a record a subclass should at least implement the [[attributes()]] method to define
* attributes. A primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified.
*
* The following is an example model called `Customer`:
*
* ``php
* class Customer extends \yii\redis\ActiveRecord
* {
* public function attributes()
* {
* return ['id', 'name', 'address', 'registration_date'];
* }
* }
* ``
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRecord extends BaseActiveRecord
{
/**
* Returns the database connection used by this AR class.
* By default, the "redis" application component is used as the database connection.
* You may override this method if you want to use a different database connection.
* @return Connection the database connection used by this AR class.
*/
public static function getDb()
{
return Yii::$app->get('redis');
}
/**
* @inheritdoc
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function find()
{
return Yii::createObject(ActiveQuery::className(), [get_called_class()]);
}
/**
* Returns the primary key name(s) for this AR class.
* This method should be overridden by child classes to define the primary key.
*
* Note that an array should be returned even when it is a single primary key.
*
* @return string[] the primary keys of this record.
*/
public static function primaryKey()
{
return ['id'];
}
/**
* Returns the list of all attribute names of the model.
* This method must be overridden by child classes to define available attributes.
* @return array list of attribute names.
*/
public function attributes()
{
throw new InvalidConfigException('The attributes() method of redis ActiveRecord has to be implemented by child classes.');
}
/**
* Declares prefix of the key that represents the keys that store this records in redis.
* By default this method returns the class name as the table name by calling [[Inflector::camel2id()]].
* For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
* 'order_item'. You may override this method if you want different key naming.
* @return string the prefix to apply to all AR keys
*/
public static function keyPrefix()
{
return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
}
/**
* @inheritdoc
*/
public function insert($runValidation = true, $attributes = null)
{
if ($runValidation && !$this->validate($attributes)) {
return false;
}
if (!$this->beforeSave(true)) {
return false;
}
$db = static::getDb();
$values = $this->getDirtyAttributes($attributes);
$pk = [];
foreach ($this->primaryKey() as $key) {
$pk[$key] = $values[$key] = $this->getAttribute($key);
if ($pk[$key] === null) {
// use auto increment if pk is null
$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]);
$this->setAttribute($key, $values[$key]);
} elseif (is_numeric($pk[$key])) {
// if pk is numeric update auto increment value
$currentPk = $db->executeCommand('GET', [static::keyPrefix() . ':s:' . $key]);
if ($pk[$key] > $currentPk) {
$db->executeCommand('SET', [static::keyPrefix() . ':s:' . $key, $pk[$key]]);
}
}
}
// save pk in a findall pool
$db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]);
$key = static::keyPrefix() . ':a:' . static::buildKey($pk);
// save attributes
$setArgs = [$key];
foreach ($values as $attribute => $value) {
// only insert attributes that are not null
if ($value !== null) {
if (is_bool($value)) {
$value = (int) $value;
}
$setArgs[] = $attribute;
$setArgs[] = $value;
}
}
if (count($setArgs) > 1) {
$db->executeCommand('HMSET', $setArgs);
}
$changedAttributes = array_fill_keys(array_keys($values), null);
$this->setOldAttributes($values);
$this->afterSave(true, $changedAttributes);
return true;
}
/**
* Updates the whole table using the provided attribute values and conditions.
* For example, to change the status to be 1 for all customers whose status is 2:
*
* ~~~
* Customer::updateAll(['status' => 1], ['id' => 2]);
* ~~~
*
* @param array $attributes attribute values (name-value pairs) to be saved into the table
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @return integer the number of rows updated
*/
public static function updateAll($attributes, $condition = null)
{
if (empty($attributes)) {
return 0;
}
$db = static::getDb();
$n = 0;
foreach (self::fetchPks($condition) as $pk) {
$newPk = $pk;
$pk = static::buildKey($pk);
$key = static::keyPrefix() . ':a:' . $pk;
// save attributes
$delArgs = [$key];
$setArgs = [$key];
foreach ($attributes as $attribute => $value) {
if (isset($newPk[$attribute])) {
$newPk[$attribute] = $value;
}
if ($value !== null) {
if (is_bool($value)) {
$value = (int) $value;
}
$setArgs[] = $attribute;
$setArgs[] = $value;
} else {
$delArgs[] = $attribute;
}
}
$newPk = static::buildKey($newPk);
$newKey = static::keyPrefix() . ':a:' . $newPk;
// rename index if pk changed
if ($newPk != $pk) {
$db->executeCommand('MULTI');
if (count($setArgs) > 1) {
$db->executeCommand('HMSET', $setArgs);
}
if (count($delArgs) > 1) {
$db->executeCommand('HDEL', $delArgs);
}
$db->executeCommand('LINSERT', [static::keyPrefix(), 'AFTER', $pk, $newPk]);
$db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
$db->executeCommand('RENAME', [$key, $newKey]);
$db->executeCommand('EXEC');
} else {
if (count($setArgs) > 1) {
$db->executeCommand('HMSET', $setArgs);
}
if (count($delArgs) > 1) {
$db->executeCommand('HDEL', $delArgs);
}
}
$n++;
}
return $n;
}
/**
* Updates the whole table using the provided counter changes and conditions.
* For example, to increment all customers' age by 1,
*
* ~~~
* Customer::updateAllCounters(['age' => 1]);
* ~~~
*
* @param array $counters the counters to be updated (attribute name => increment value).
* Use negative values if you want to decrement the counters.
* @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @return integer the number of rows updated
*/
public static function updateAllCounters($counters, $condition = null)
{
if (empty($counters)) {
return 0;
}
$db = static::getDb();
$n = 0;
foreach (self::fetchPks($condition) as $pk) {
$key = static::keyPrefix() . ':a:' . static::buildKey($pk);
foreach ($counters as $attribute => $value) {
$db->executeCommand('HINCRBY', [$key, $attribute, $value]);
}
$n++;
}
return $n;
}
/**
* Deletes rows in the table using the provided conditions.
* WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
*
* For example, to delete all customers whose status is 3:
*
* ~~~
* Customer::deleteAll(['status' => 3]);
* ~~~
*
* @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
* Please refer to [[ActiveQuery::where()]] on how to specify this parameter.
* @return integer the number of rows deleted
*/
public static function deleteAll($condition = null)
{
$pks = self::fetchPks($condition);
if (empty($pks)) {
return 0;
}
$db = static::getDb();
$attributeKeys = [];
$db->executeCommand('MULTI');
foreach ($pks as $pk) {
$pk = static::buildKey($pk);
$db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
$attributeKeys[] = static::keyPrefix() . ':a:' . $pk;
}
$db->executeCommand('DEL', $attributeKeys);
$result = $db->executeCommand('EXEC');
return end($result);
}
private static function fetchPks($condition)
{
$query = static::find();
$query->where($condition);
$records = $query->asArray()->all(); // TODO limit fetched columns to pk
$primaryKey = static::primaryKey();
$pks = [];
foreach ($records as $record) {
$pk = [];
foreach ($primaryKey as $key) {
$pk[$key] = $record[$key];
}
$pks[] = $pk;
}
return $pks;
}
/**
* Builds a normalized key from a given primary key value.
*
* @param mixed $key the key to be normalized
* @return string the generated key
*/
public static function buildKey($key)
{
if (is_numeric($key)) {
return $key;
} elseif (is_string($key)) {
return ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
} elseif (is_array($key)) {
if (count($key) == 1) {
return self::buildKey(reset($key));
}
ksort($key); // ensure order is always the same
$isNumeric = true;
foreach ($key as $value) {
if (!is_numeric($value)) {
$isNumeric = false;
}
}
if ($isNumeric) {
return implode('-', $key);
}
}
return md5(json_encode($key));
}
}
参考资料
yii2-redis 扩展详解 https://www.yiichina.com/doc/guide/2.0/yii2-redis?language=zh-cn
yii2-redis 扩展详解 https://www.yiichina.com/tutorial/1608
yii Redis使用举例 http://blog.sina.com.cn/s/blog_6aba78b40102wwr6.html