1、多租户系统中包含多个租户,每个租户均有其自有的数据库配置信息,现有的需求是通过调用多租户系统的接口,基于响应的主机名、用户名和密码来连接数据库,而不是以应用组件的方式来配置,如图1
2、现在是以应用组件的方式来配置,/common/config/base.php,如图2
3、打开 Dubug,然后查看 Log Messages,db 组件的属性需要在 yii/db/Connection::open 之前完成初始化定义(调用多租户系统的接口),如图3
4、编辑 /common/config/base.php,配置 db 组件的类:common/components/db/Connection,将所有 env 变量注释掉
'db'=>[
'class'=>'common/components/db/Connection',
// 'dsn' => env('DB_DSN'),
// 'username' => env('DB_USERNAME'),
// 'password' => env('DB_PASSWORD'),
// 'tablePrefix' => env('DB_TABLE_PREFIX'),
'charset' => 'utf8',
'enableSchemaCache' => YII_ENV_PROD,
],
5、新建 /common/components/db/Connection.php,继承至 /yii/db/Connection
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/01/15
* Time: 16:18
*/
namespace common/components/db;
use Yii;
use yii/helpers/ArrayHelper;
/**
* 获取租户模块环境配置信息,存储至Redis,注册db组件
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class Connection extends /yii/db/Connection
{
public function __construct($config = [])
{
// ... 配置生效前的初始化过程
$tenantConfig = [
'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=cmcp-api',
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'tablePrefix' => env('DB_TABLE_PREFIX'),
];
if (!empty($config)) {
$config = ArrayHelper::merge($config, $tenantConfig);
}
parent::__construct($config);
}
}
6、重复第3步骤,db 组件的属性已经在 yii/db/Connection::open 之前完成初始化定义(host=localhost 变化为 host=127.0.0.1),如图4
7、现在需要调用多租户系统的接口,以其响应重新赋值 $tenantConfig(建议:此方案仅适用于存在一个租户的情况,如果存在多个租户,建议每个租户对应不同的连接组件,连接组件的命名基于租户ID,而不是对应 db 连接组件),如图5
8、现在还原所有修改,准备实现同时使用多个数据库(每个租户对应不同的连接组件,连接组件的命名基于租户ID),参考网址:https://github.com/yiichina/yii2/blob/master/docs/guide-zh-CN/db-active-record.md ,如图6
9、新建 /common/components/db/ActiveRecord.php,继承至 /yii/db/ActiveRecord,重写 [[yii/db/ActiveRecord::getDb()|getDb()]] 方法
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/01/16
* Time: 10:31
*/
namespace common/components/db;
use Yii;
/**
* 获取租户模块环境配置信息,存储至Redis,注册数据库连接组件
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class ActiveRecord extends /yii/db/ActiveRecord
{
/**
* Returns the database connection used by this AR class.
* By default, the "db" 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()
{
$tenantDb = new /yii/db/Connection([
'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=cmcp-api',
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'tablePrefix' => env('DB_TABLE_PREFIX'),
'charset' => 'utf8',
'enableSchemaCache' => YII_ENV_PROD,
]);
return $tenantDb;
// return Yii::$app->getDb();
}
}
10、编辑 /common/models/KeyStorageItem.php,继承至 common/components/db/ActiveRecord
<?php
namespace common/models;
use Yii;
use yii/behaviors/TimestampBehavior;
use common/components/db/ActiveRecord;
/**
* This is the model class for table "key_storage_item".
*
* @property integer $key
* @property integer $value
*/
class KeyStorageItem extends ActiveRecord
{
}
11、编辑 /common/config/base.php,注释掉 db 组件,后期建议将 db 组件移至开发环境,以方便于 Gii 的使用
/*
'db'=>[
'class'=>'yii/db/Connection',
'dsn' => env('DB_DSN'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'tablePrefix' => env('DB_TABLE_PREFIX'),
'charset' => 'utf8',
'enableSchemaCache' => YII_ENV_PROD,
],
*/
12、打开:http://frontend.cmcp-api.localhost/ ,报错:Failed to instantiate component or class “db”.,原因在于日志目标为数据库表,如图7
13、查看日志组件配置,/common/config/base.php
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
'db'=>[
'class' => 'yii/log/DbTarget',
'levels' => ['error', 'warning'],
'except'=>['yii/web/HttpException:*', 'yii/i18n/I18N/*'],
'prefix'=>function () {
$url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
return sprintf('[%s][%s]', Yii::$app->id, $url);
},
'logVars'=>[],
'logTable'=>'{{%system_log}}'
]
],
],
14、日志组件必须在引导期间就被加载,因此如果数据库连接是动态配置的,已经晚于引导阶段了,编辑日志组件配置,/common/config/base.php,保存日志消息到文件中
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
'file'=>[
'class' => 'yii/log/FileTarget',
'levels' => ['error', 'warning'],
'except' => ['yii/web/HttpException:*', 'yii/i18n/I18N/*'],
'prefix' => function () {
$url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
return sprintf('[%s][%s]', Yii::$app->id, $url);
},
'logVars'=>[],
]
],
],
15、打开:http://frontend.cmcp-api.localhost/ ,报错:Unknown component ID: db,如图8
16、将 /common/models 下的所有模型文件,use yii/db/ActiveRecord; 替换为 use common/components/db/ActiveRecord;,如图9
use yii/db/ActiveRecord; 替换为 use common/components/db/ActiveRecord; /yii/db/ActiveRecord 替换为 /common/components/db/ActiveRecord
17、打开:http://frontend.cmcp-api.localhost/ ,报错:Failed to instantiate component or class “db”.,原因在于 RBAC 组件使用数据库表存放数据,如图10
18、编辑 /common/config/base.php,注释掉 RBAC 组件,因为其 db 属性默认值为 db,需要动态调整为租户ID所对应的数据库连接组件
/*
'authManager' => [
'class' => 'yii/rbac/DbManager',
'itemTable' => '{{%rbac_auth_item}}',
'itemChildTable' => '{{%rbac_auth_item_child}}',
'assignmentTable' => '{{%rbac_auth_assignment}}',
'ruleTable' => '{{%rbac_auth_rule}}'
],
*/
19、编辑 /common/components/db/ActiveRecord.php,通过 [[yii/di/ServiceLocator::setComponents()]] 方法注册数据库连接组件、RBAC组件
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/01/16
* Time: 10:31
*/
namespace common/components/db;
use Yii;
/**
* 获取租户模块环境配置信息,存储至Redis,注册数据库连接组件
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class ActiveRecord extends /yii/db/ActiveRecord
{
/**
* Returns the database connection used by this AR class.
* By default, the "db" 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()
{
$tenantDb = 'defaultDb';
// 注册数据库连接组件、RBAC组件
Yii::$app->setComponents([
$tenantDb => [
'class' => 'yii/db/Connection',
'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=cmcp-api',
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'tablePrefix' => env('DB_TABLE_PREFIX'),
'charset' => 'utf8',
'enableSchemaCache' => YII_ENV_PROD,
],
'authManager' => [
'class' => 'yii/rbac/DbManager',
'db' => $tenantDb,
'itemTable' => '{{%rbac_auth_item}}',
'itemChildTable' => '{{%rbac_auth_item_child}}',
'assignmentTable' => '{{%rbac_auth_assignment}}',
'ruleTable' => '{{%rbac_auth_rule}}'
],
]);
return Yii::$app->$tenantDb;
}
}
20、重复第3步骤,db 组件的属性已经在 yii/db/Connection::open 之前完成初始化定义(host=localhost 变化为 host=127.0.0.1),如图11
21、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,如图12
22、打开网址:http://backend.cmcp-api.localhost/ ,报错:Unknown component ID: db,如图13
23、编辑 /backend/models/SystemLog.php,继承至 /common/components/db/ActiveRecord,如图14
/yii/db/ActiveRecord 替换为 /common/components/db/ActiveRecord
24、打开网址:http://backend.cmcp-api.localhost/ ,200响应
25、现在需要调用多租户系统的接口,以其响应直接初始化使用数据库连接、RBAC,基于已经安装的 Yii 2 的 HTTP 客户端扩展,如图15
26、编辑 /.env.dist、/.env,新增多租户的相关配置
# 多租户 # ---- TENANT_HOST_INFO = http://wjdev.chinamcloud.com:8600 # HOME URL TENANT_BASE_URL = /interface # BASE URL TENANT_APP_NAME = cmcpapi # 模块英文名称 TENANT_SECRET = 94030d307b160f04b88592cb9bebdd4c # 模块Secret
27、编辑 /common/config/bootstrap.php,设置别名
Yii::setAlias('@tenantUrl', env('TENANT_HOST_INFO') . env('TENANT_BASE_URL'));
28、通过应用组件配置客户端,编辑 /common/config/web.php,如图16
'tenantHttp' => [
'class' => 'yii/httpclient/Client',
'baseUrl' => Yii::getAlias('@tenantUrl'),
'transport' => 'yii/httpclient/CurlTransport'
],
29、通过配置日志记录 HTTP 发送的请求并分析其执行情况,编辑 /common/config/base.php,如图17
'httpRequest'=>[
'class' => 'yii/log/FileTarget',
'logFile' => '@runtime/logs/http-request.log',
'categories' => ['yii/httpclient/*'],
]
30、yii2 HTTP 客户端扩展提供了一个可以与 yii 调试模块集成的调试面板,并显示已执行的HTTP 请求,启用调试面板,如图18
$config['modules']['debug'] = [
'class' => 'yii/debug/Module',
'allowedIPs' => ['127.0.0.1', '::1', '192.168.33.1', '172.17.42.1', '172.17.0.1', '192.168.99.1'],
'panels' => [
'httpclient' => [
'class' => 'yii/httpclient/debug/HttpClientPanel',
],
],
];
31、刷新页面之后,查看日志面板,新增 HTTP Client,如图19
32、新建 /common/logics/http/tenant/Env.php,http目录表示此目录下的模型数据源自于HTTP请求,tenant目录表示多租户系统相关的模型,Env.php表示环境配置模型
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/01/17
* Time: 15:20
*/
namespace common/logics/http/tenant;
use Yii;
use yii/base/Model;
use yii/web/ServerErrorHttpException;
/**
* 多租户的模块环境配置
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class Env extends Model
{
public $app_name;
public $secret;
public $tenant_id;
public function attributeLabels()
{
return [
'app_name' => /Yii::t('model/http/tenant/env', 'App Name'),
'secret' => /Yii::t('model/http/tenant/env', 'Secret'),
'tenant_id' => /Yii::t('model/http/tenant/env', 'Tenant ID'),
];
}
/**
* 返回租户模块环境配置信息
*
* @return array|false
*
* 格式如下:
*
* 租户模块环境配置信息
* [
* 'message' => '', //说明
* 'data' => [], //数据
* ]
*
* 失败(将错误保存在 [[yii/base/Model::errors]] 属性中)
* false
*
* @throws ServerErrorHttpException 如果响应状态码不等于20x
*/
public function getTenantEnv()
{
$this->app_name = env('TENANT_APP_NAME');
$this->secret = env('TENANT_SECRET');
/* 租户ID后续从请求参数中获取 */
$this->tenant_id = 'default';
$response = Yii::$app->tenantHttp->createRequest()
->setMethod('get')
->setUrl('getTenantEnvs')
->setData([
'appname' => $this->app_name,
'secret' => $this->secret,
'tenantid1' => $this->tenant_id,
])
->send();
// 检查响应状态码是否等于20x
if ($response->isOk) {
// 检查业务逻辑是否成功
if ($response->data['returnCode'] === 0) {
return ['message' => $response->data['returnDesc'], 'data' => $response->data['returnData']];
} else {
$this->addError('tenant_id', $response->data['returnDesc']);
return false;
}
} else {
throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20005'), ['statusCode' => $response->getStatusCode()])));
}
}
}
33、新建语言包文件,/common/messages/en/model/http/tenant/env.php、/common/messages/zh/model/http/tenant/env.php,如图20
/common/messages/en/model/http/tenant/env.php
return [
'App Name' => 'English name of the module',
'Secret' => 'Secret module in multi-tenant system',
'Tenant ID' => 'Tenant ID',
];
/common/messages/zh/model/http/tenant/env.php
return [
'App Name' => '模块英文名称',
'Secret' => '模块Secret',
'Tenant ID' => '租户ID',
];
34、编辑语言包文件,/api/messages/en/error.php、/api/messages/zh/error.php
/api/messages/en/error.php
20005 => 'Multitenant HTTP request failed with status code: {statusCode}',
20006 => 'Multitenant HTTP request failed: {firstErrors}',
/api/messages/zh/error.php
20005 => '多租户HTTP请求失败,状态码:{statusCode}',
20006 => '多租户HTTP请求失败:{firstErrors}',
35、复制 /api/messages/en/app.php、/api/messages/en/error.php、/api/messages/zh/app.php、/api/messages/zh/error.php 至 /common/messages/en/app.php、/common/messages/en/error.php、/common/messages/zh/app.php、/common/messages/zh/error.php,编辑
/common/messages/zh/error.php
return [
20000 => 'error',
20005 => '多租户HTTP请求失败,状态码:{statusCode}',
20006 => '多租户HTTP请求失败:{firstErrors}',
];
36、编辑 /common/components/db/ActiveRecord.php,数据库配置信息源自于租户环境配置接口
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/01/16
* Time: 10:31
*/
namespace common/components/db;
use Yii;
use common/logics/http/tenant/Env;
use yii/web/ServerErrorHttpException;
/**
* 获取租户模块环境配置信息,存储至Redis,注册数据库连接组件
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class ActiveRecord extends /yii/db/ActiveRecord
{
/**
* Returns the database connection used by this AR class.
* By default, the "db" 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()
{
$env = new Env();
$tenantEnv = $env->getTenantEnv();
if ($tenantEnv === false) {
if ($env->hasErrors()) {
foreach ($env->getFirstErrors() as $message) {
$firstErrors = $message;
}
throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20006'), ['firstErrors' => $firstErrors])));
} elseif (!$env->hasErrors()) {
throw new ServerErrorHttpException('Multi-tenant HTTP requests fail for unknown reasons.');
}
}
$tenantDb = $tenantEnv['data']['tenantid'] . 'Db';
// 注册数据库连接组件、RBAC组件
Yii::$app->setComponents([
$tenantDb => [
'class' => 'yii/db/Connection',
'dsn' => 'mysql:host=' .$tenantEnv['data']['db_info']['host'] . ';port=3306;dbname=' . $tenantEnv['data']['db_info']['database'] . '',
'username' => $tenantEnv['data']['db_info']['login'],
'password' => $tenantEnv['data']['db_info']['password'],
'tablePrefix' => $tenantEnv['data']['db_info']['prefix'],
'charset' => 'utf8',
'enableSchemaCache' => YII_ENV_PROD,
],
'authManager' => [
'class' => 'yii/rbac/DbManager',
'db' => $tenantDb,
'itemTable' => '{{%rbac_auth_item}}',
'itemChildTable' => '{{%rbac_auth_item_child}}',
'assignmentTable' => '{{%rbac_auth_assignment}}',
'ruleTable' => '{{%rbac_auth_rule}}'
],
]);
return Yii::$app->$tenantDb;
}
}
37、在 Postman 中 GET http://www.cmcp-api.localhost/v1/pages ,200响应,如图21
38、在 Postman 中 GET 多租户的环境配置接口,一个参数是错误的,如图22
39、编辑 /.env,TENANT_APP_NAME的值是错误的
TENANT_APP_NAME = cmcpapi1 # 模块英文名称
40、在 Postman 中 GET http://www.cmcp-api.localhost/v1/pages ,500响应,如图23
41、查看日志,发现接口应用与后台应用分别请求多租户接口的次数为8、139次,后台报错:Unable to send log via yii/log/FileTarget: Exception (Database Exception) ‘yii/db/Exception’ with message ‘SQLSTATE[HY000] [1040] Too many connections’ ,如图24、25
42、检查数据库连接组件、RBAC组件是否被注册,如果已经被注册,则无需覆盖,编辑 /common/components/db/ActiveRecord.php,如图26
// 检查数据库连接组件、RBAC组件是否被注册
if (!(Yii::$app->has($tenantDb) && Yii::$app->has('authManager'))) {
// 注册数据库连接组件、RBAC组件
Yii::$app->setComponents([
$tenantDb => [
'class' => 'yii/db/Connection',
'dsn' => 'mysql:host=' . $tenantEnv['data']['db_info']['host'] . ';port=3306;dbname=' . $tenantEnv['data']['db_info']['database'] . '',
'username' => $tenantEnv['data']['db_info']['login'],
'password' => $tenantEnv['data']['db_info']['password'],
'tablePrefix' => $tenantEnv['data']['db_info']['prefix'],
'charset' => 'utf8',
'enableSchemaCache' => YII_ENV_PROD,
],
'authManager' => [
'class' => 'yii/rbac/DbManager',
'db' => $tenantDb,
'itemTable' => '{{%rbac_auth_item}}',
'itemChildTable' => '{{%rbac_auth_item_child}}',
'assignmentTable' => '{{%rbac_auth_assignment}}',
'ruleTable' => '{{%rbac_auth_rule}}'
],
]);
}
43、后台应用报错:’SQLSTATE[HY000] [1040] Too many connections’ 已经解决,如图27
44、至此,数据库连接时的动态配置,配置属性来源于多租户系统已经基本上实现了,后续应该会实现缓存(多租户系统的响应),因为一次请求中,对于多租户系统的HTTP请求多达上百次,而每次HTTP请求平均耗时为100ms左右,如图28
45、开启 Schema 缓存(仅在生产环境中开启),且缓存至 Redis 中,编辑 /.env.dist、/.env,如图29
YII_ENV = prod # Redis # ---- REDIS_HOSTNAME = localhost # 主机名/IP地址 REDIS_PORT = 6379 # 端口 #REDIS_PASSWORD = # 密码 REDIS_DATABASE = 0 # 数据库 # Redis cache # ---- REDIS_CACHE_KEY_PREFIX = ca: # 唯一键前缀
46、编辑 /common/config/base.php,在应用程序配置中配置 Redis 连接,且配置缓存组件
'redisCache' => [
'class' => 'yii/redis/Cache',
'keyPrefix' => env('REDIS_CACHE_KEY_PREFIX'), // 唯一键前缀
],
'redis' => [
'class' => 'yii/redis/Connection',
'hostname' => env('REDIS_HOSTNAME'),
'port' => env('REDIS_PORT'),
'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_DATABASE'),
],
47、在数据库连接中开启 Schema 缓存,编辑 /common/components/db/ActiveRecord.php
// 检查数据库连接组件、RBAC组件是否被注册
if (!(Yii::$app->has($tenantDb) && Yii::$app->has('authManager'))) {
// 注册数据库连接组件、RBAC组件
Yii::$app->setComponents([
$tenantDb => [
'class' => 'yii/db/Connection',
'dsn' => 'mysql:host=' . $tenantEnv['data']['db_info']['host'] . ';port=3306;dbname=' . $tenantEnv['data']['db_info']['database'] . '',
'username' => $tenantEnv['data']['db_info']['login'],
'password' => $tenantEnv['data']['db_info']['password'],
'tablePrefix' => $tenantEnv['data']['db_info']['prefix'],
'charset' => 'utf8',
'enableSchemaCache' => YII_ENV_PROD,
'schemaCache' => 'redisCache',
],
'authManager' => [
'class' => 'yii/rbac/DbManager',
'db' => $tenantDb,
'itemTable' => '{{%rbac_auth_item}}',
'itemChildTable' => '{{%rbac_auth_item_child}}',
'assignmentTable' => '{{%rbac_auth_assignment}}',
'ruleTable' => '{{%rbac_auth_rule}}'
],
]);
}
48、运行后台应用后,查看 Redis 中的数据,数据表的结构已经被缓存了,如图30
49、实现缓存(多租户系统的响应),编辑 /common/logics/http/tenant/Env.php,运行后台应用后,查看 Redis 中的数据,多租户系统的响应数据已经被缓存了,如图31
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/01/17
* Time: 15:20
*/
namespace common/logics/http/tenant;
use Yii;
use yii/base/Model;
use yii/web/ServerErrorHttpException;
/**
* 多租户的模块环境配置
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class Env extends Model
{
public $app_name;
public $secret;
public $tenant_id;
public function attributeLabels()
{
return [
'app_name' => /Yii::t('model/http/tenant/env', 'App Name'),
'secret' => /Yii::t('model/http/tenant/env', 'Secret'),
'tenant_id' => /Yii::t('model/http/tenant/env', 'Tenant ID'),
];
}
/**
* 返回租户模块环境配置信息
*
* @return array|false
*
* 格式如下:
*
* 租户模块环境配置信息
* [
* 'message' => '', //说明
* 'data' => [], //数据
* ]
*
* 失败(将错误保存在 [[yii/base/Model::errors]] 属性中)
* false
*
* @throws ServerErrorHttpException 如果响应状态码不等于20x
*/
public function getTenantEnv()
{
/* 租户ID后续从请求参数中获取 */
$this->tenant_id = 'default';
// 设置多租户数据的缓存键
$redisCache = Yii::$app->redisCache;
$tenantKey = 'tenant:' . $this->tenant_id;
// 从缓存中取回多租户数据
$tenantData = $redisCache[$tenantKey];
if ($tenantData === false) {
$this->app_name = env('TENANT_APP_NAME');
$this->secret = env('TENANT_SECRET');
$response = Yii::$app->tenantHttp->createRequest()
->setMethod('get')
->setUrl('getTenantEnv')
->setData([
'appname' => $this->app_name,
'secret' => $this->secret,
'tenantid' => $this->tenant_id,
])
->send();
// 检查响应状态码是否等于20x
if ($response->isOk) {
// 检查业务逻辑是否成功
if ($response->data['returnCode'] === 0) {
$tenantData = ['message' => $response->data['returnDesc'], 'data' => $response->data['returnData']];
// 将多租户数据存放到缓存供下次使用
$redisCache[$tenantKey] = $tenantData;
return $tenantData;
} else {
$this->addError('tenant_id', $response->data['returnDesc']);
return false;
}
} else {
throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20005'), ['statusCode' => $response->getStatusCode()])));
}
} else {
return $tenantData;
}
}
}
50、清空Redis,一次请求中,对于多租户系统的HTTP请求仅有1次,如图32
51、默认情况下,缓存中的数据会永久存留,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除),后续准备实现清除对应缓存数据的接口,以便于多租户系统的数据发生变化时,可以调用清除对应缓存数据的接口
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/webdev/180928.html
