console – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Sun, 31 May 2026 08:10:19 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 在 Postman 中请求 https 接口响应成功,在前端页面中请求 https 接口无响应,控制台中报错: net::ERR_CERT_AUTHORITY_INVALID https://www.shuijingwanwq.com/2025/05/14/9026/ https://www.shuijingwanwq.com/2025/05/14/9026/#respond Wed, 14 May 2025 02:34:50 +0000 https://www.shuijingwanwq.com/?p=9026 Post Views: 123 1、在 Postman 中请求 https 接口响应成功,在前端页面中请求 https 接口无响应,控制台中报错: net::ERR_CERT_AUTHORITY_INVALID。如图1
在 Postman 中请求 https 接口响应成功,在前端页面中请求 https 接口无响应,控制台中报错: net::ERR_CERT_AUTHORITY_INVALID

图1

2、原因:浏览器不信任 mkcert 证书,因为它是由本地 CA(Certificate Authority)签发的,并未被操作系统或浏览器默认信任。使用以下命令检查:CA 证书 是否已经安装。确认未安装。如图2
原因:浏览器不信任 mkcert 证书,因为它是由本地 CA(Certificate Authority)签发的,并未被操作系统或浏览器默认信任。使用以下命令检查:CA 证书 是否已经安装。确认未安装。

图2



PS C:\wwwroot> certutil -store -user root "mkcert development CA"
root "受信任的根证书颁发机构"
CertUtil: -store 失败: 0x80090011 (-2146893807 NTE_NOT_FOUND)
CertUtil: 找不到对象。
PS C:\wwwroot>


3、运行 certlm.msc(本地计算机证书管理器)。在 受信任的根证书颁发机构 下,检查是否有 mkcert development CA 证书。不存在。如图3
运行 certlm.msc(本地计算机证书管理器)。在 受信任的根证书颁发机构 下,检查是否有 mkcert development CA 证书。不存在

图3

4、将 C:\Users\wangqiang\AppData\Local\mkcert\rootCA.pem 证书导入到 受信任的根证书颁发机构。如图4
将 C:\Users\wangqiang\AppData\Local\mkcert\rootCA.pem 证书导入到 受信任的根证书颁发机构

图4

5、信任 CA 证书 Chrome / Edge 进入 chrome://settings/certificates。选择 “本地证书” 选项卡 – 查看从 Windows 导入的证书,查看是否包含 mkcert 生成的 CA 证书。已经包含,重启浏览器。如图5
信任 CA 证书 Chrome / Edge 进入 chrome://settings/certificates。选择 “本地证书” 选项卡 - 查看从 Windows 导入的证书,查看是否包含 mkcert 生成的 CA 证书。已经包含,重启浏览器

图5

6、再次请求,报错:Access to XMLHttpRequest at ‘https://api.apply.local/sms/get-login-verify-code’ from origin ‘https://console.apply.local’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. 如图6
再次请求,报错:Access to XMLHttpRequest at 'https://api.apply.local/sms/get-login-verify-code' from origin 'https://console.apply.local' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

图6

7、从来源“https://console.apply.local”访问“https://api.apply.local/sms/get-login-verify-code”处的 XMLHttpRequest 已被 CORS 策略阻止:请求的资源上不存在“Access-Control-Allow-Origin”标头。如图7
从来源“https://console.apply.local”访问“https://api.apply.local/sms/get-login-verify-code”处的 XMLHttpRequest 已被 CORS 策略阻止:请求的资源上不存在“Access-Control-Allow-Origin”标头。

图7

8、在服务端设置响应 Access-Control-Allow-Origin https://console.apply.local ,控制台中也不再报错。如图8
在服务端设置响应 Access-Control-Allow-Origin https://console.apply.local ,控制台中也不再报错

图8

]]>
https://www.shuijingwanwq.com/2025/05/14/9026/feed/ 0
在浏览器控制台中报错:UncaughtSyntaxError Invalid or unexpected token (xxx.js) https://www.shuijingwanwq.com/2023/06/21/7751/ https://www.shuijingwanwq.com/2023/06/21/7751/#respond Wed, 21 Jun 2023 01:37:44 +0000 https://www.shuijingwanwq.com/?p=7751 Post Views: 88

1、在浏览器控制台中报错:UncaughtSyntaxError: Invalid or unexpected token (xxx.js)。如图1

在浏览器控制台中报错:UncaughtSyntaxError: Invalid or unexpected token (xxx.js)

图1

2、最后发现原因在于响应的 js 文件内容中包含了 \,内容进行转义。如图2

最后发现原因在于响应的 js 文件内容中包含了 \,内容进行转义

图2

3、响应的 js 文件内容中去掉了 \ 后,内容不再转义。控制台不再报错。

]]>
https://www.shuijingwanwq.com/2023/06/21/7751/feed/ 0
在 Windows 10 中执行命令行,报错:Call to undefined function App\Console\Commands\posix_getpid() https://www.shuijingwanwq.com/2021/09/02/5216/ https://www.shuijingwanwq.com/2021/09/02/5216/#respond Thu, 02 Sep 2021 03:43:50 +0000 https://www.shuijingwanwq.com/?p=5216 Post Views: 131 1、在 Windows 10 中执行命令行,报错:Call to undefined function App\Console\Commands\posix_getpid()。如图1
在 Windows 10 中执行命令行,报错:Call to undefined function App\Console\Commands\posix_getpid()。

图1



PS E:\wwwroot\msi_main> php artisan migrate

In ExportRecords.php line 65:

  Call to undefined function App\Console\Commands\posix_getpid()




2、参考网址:https://www.php.net/manual/zh/intro.posix.php 。此扩展在 Windows 平台上不可用。您也可以尝试使用 getmypid() 代替。如图2
参考网址:https://www.php.net/manual/zh/intro.posix.php 。此扩展在 Windows 平台上不可用。您也可以尝试使用 getmypid() 代替。

图2

3、posix_getpid — 返回当前进程 id。决定替换为:getmypid — 获取 PHP 进程的 ID。参考网址:https://www.php.net/getmypid。 4、编辑 /app/Console/Commands/ExportRecords.php 。将 posix_getpid 替换为 getmypid。


    private function setLogPrefix() {
        // $pid = posix_getpid();
		$pid = getmypid();
        $ppid = posix_getppid();  //获取父进程id
        $this->log_prefix = "CommandExportRecords : pid:{$pid} : ppid:{$ppid} :";
        return $this;
    }


5、继续执行命令行,报错:Call to undefined function App\Console\Commands\posix_getppid()。


PS E:\wwwroot\msi_main> php artisan migrate

In ExportRecords.php line 67:

  Call to undefined function App\Console\Commands\posix_getppid()




6、posix_getppid 在 Windows 上不起作用。这是一个替代方案。参考网址:https://www.php.net/manual/zh/function.posix-getppid 。判断是否是 Windows 系统,如果是,就使用替代方案。如图3
posix_getppid 在 Windows 上不起作用。这是一个替代方案。参考网址:https://www.php.net/manual/zh/function.posix-getppid 。判断是否是 Windows 系统,如果是,就使用替代方案。

图3



    private function setLogPrefix() {
		if(strncasecmp(PHP_OS, "win", 3) == 0) {
			$pid = getmypid();  // child process ID
			$ppid = shell_exec("wmic process where (processid=$pid) get parentprocessid");
			$ppid = explode("\n", $ppid);
			$ppid = intval($ppid[1]);
		} else {
			$pid = posix_getpid();
			$ppid = posix_getppid();  //获取父进程id
		}
        $this->log_prefix = "CommandExportRecords : pid:{$pid} : ppid:{$ppid} :";
        return $this;
    }


7、继续执行命令行,未再报错。如图4
继续执行命令行,未再报错。

图4



PS E:\wwwroot\msi_main> php artisan migrate
Migration table created successfully.


]]>
https://www.shuijingwanwq.com/2021/09/02/5216/feed/ 0
在容器启动时,复制文件至 NAS 存储中,基于 CakePHP v2.6.4 实现 https://www.shuijingwanwq.com/2020/08/25/4457/ https://www.shuijingwanwq.com/2020/08/25/4457/#respond Tue, 25 Aug 2020 01:55:32 +0000 https://www.shuijingwanwq.com/?p=4457 Post Views: 107 1、现阶段存在一个新需求,需要在部署产品时,针对产品做一些初始化数据的工作。预先准备好相应的 SQL 文件,但是对于 MySQL 表中的文件路径:/upload/20200817/20203554555373.png。其存储于 NAS 存储中。而并非程序的目录下。如图1
现阶段存在一个新需求,需要在部署产品时,针对产品做一些初始化数据的工作。预先准备好相应的 SQL 文件,但是对于 MySQL 表中的文件路径:/upload/20200817/20203554555373.png。其存储于 NAS 存储中。而并非程序的目录下。

图1

2、计划在容器启动时,自动执行一个 PHP 命令行,判断 NAS 存储中,默认的文件是否存在,如果存在,则退出。不存在,则复制。 3、实现将 app/webroot/images/20200817 下的所有文件放入 app/webroot/upload/20200817 中的命令行。新建文件:app/Console/Command/AssetShell.php
<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2020/08/18
 * Time: 15:04
 */

App::uses('Folder', 'Utility');

class AssetShell extends AppShell {
    public function sync() {
        $folder = new Folder(WWW_ROOT . 'images' . DS . '20200817');
        $folder->copy(UP_DIR . DS . 'upload' . DS . '20200817');
        $this->out('Asset sync successfully.');
    }
}

</pre>
4、进入目录:E:\wwwroot\creditshop\app,执行:Console/cake asset sync,将 app/webroot/images/20200817 下的所有文件复制至 app/webroot/upload/20200817 中。如图2
进入目录:E:\wwwroot\creditshop\app,执行:Console/cake asset sync,将 app/webroot/images/20200817 下的所有文件复制至 app/webroot/upload/20200817 中。

图2



PS E:\wwwroot\creditshop\app> Console/cake asset sync


Welcome to CakePHP v2.6.4 Console
---------------------------------------------------------------
App : app
Path: E:\wwwroot\creditshop\app\
---------------------------------------------------------------
Asset sync successfully.



5、在容器中运行相应的命令,将 /mcloud/creditshop/app/webroot/images/20200817 下的所有文件复制至 /webtv/wangjie/creditbak/upload/20200817 中。/webtv 为所挂载的 NAS 存储。如图3
在容器中运行相应的命令,将 /mcloud/creditshop/app/webroot/images/20200817 下的所有文件复制至 /webtv/wangjie/creditbak/upload/20200817 中。/webtv 为所挂载的 NAS 存储。

图3



[root@465e0aa491eb /]# cd /webtv/wangjie/creditbak/upload
[root@465e0aa491eb upload]# ls -l
total 44
drwxrwxrwx 2 nginx nginx  106 Dec 17  2018 20181217
drwxrwxrwx 2 nginx nginx 4096 Jan 24  2019 20190124
drwxrwxrwx 2 nginx nginx  156 Feb 21  2019 20190221
drwxrwxrwx 2 nginx nginx 4096 Feb 22  2019 20190222
drwxrwxrwx 2 nginx nginx 4096 Feb 25  2019 20190225
drwxrwxrwx 2 nginx nginx 4096 Feb 28  2019 20190228
drwxrwxrwx 2 nginx nginx  106 Apr  9  2019 20190409
drwxrwxrwx 2 nginx nginx  155 Sep 11  2019 20190911
drwxrwxrwx 2 nginx nginx 4096 Nov 11  2019 20191111
drwxrwxrwx 2 nginx nginx  130 Nov 12  2019 20191112
drwxrwxrwx 2 nginx nginx 4096 Nov 13  2019 20191113
drwxrwxrwx 2 nginx nginx  130 Nov 14  2019 20191114
drwxrwxrwx 2 nginx nginx 4096 Nov 19  2019 20191119
drwxrwxrwx 2 nginx nginx  106 Nov 27  2019 20191127
drwxrwxrwx 2 nginx nginx 4096 Nov 29  2019 20191129
drwxrwxrwx 2 nginx nginx  129 Dec 21  2019 20191221
drwxrwxrwx 2 nginx nginx 4096 Jul 16 22:33 20200716
drwxrwxrwx 2 nginx nginx 4096 Aug  3 22:34 20200803
drwxrwxrwx 2 nginx nginx 4096 Aug 18 16:04 20200818
[root@465e0aa491eb upload]# cd /mcloud/creditshop/app
[root@465e0aa491eb app]# Console/cake asset sync
bash: Console/cake: Permission denied
[root@465e0aa491eb app]# chmod +x Console/cake
[root@465e0aa491eb app]# Console/cake asset sync

Welcome to CakePHP v2.6.4 Console
---------------------------------------------------------------
App : app
Path: /mcloud/creditshop/app/
---------------------------------------------------------------
Asset sync successfully.
[root@465e0aa491eb app]# cd /webtv/wangjie/creditbak/upload
[root@465e0aa491eb upload]# ls -l
total 44
drwxrwxrwx 2 nginx nginx  106 Dec 17  2018 20181217
drwxrwxrwx 2 nginx nginx 4096 Jan 24  2019 20190124
drwxrwxrwx 2 nginx nginx  156 Feb 21  2019 20190221
drwxrwxrwx 2 nginx nginx 4096 Feb 22  2019 20190222
drwxrwxrwx 2 nginx nginx 4096 Feb 25  2019 20190225
drwxrwxrwx 2 nginx nginx 4096 Feb 28  2019 20190228
drwxrwxrwx 2 nginx nginx  106 Apr  9  2019 20190409
drwxrwxrwx 2 nginx nginx  155 Sep 11  2019 20190911
drwxrwxrwx 2 nginx nginx 4096 Nov 11  2019 20191111
drwxrwxrwx 2 nginx nginx  130 Nov 12  2019 20191112
drwxrwxrwx 2 nginx nginx 4096 Nov 13  2019 20191113
drwxrwxrwx 2 nginx nginx  130 Nov 14  2019 20191114
drwxrwxrwx 2 nginx nginx 4096 Nov 19  2019 20191119
drwxrwxrwx 2 nginx nginx  106 Nov 27  2019 20191127
drwxrwxrwx 2 nginx nginx 4096 Nov 29  2019 20191129
drwxrwxrwx 2 nginx nginx  129 Dec 21  2019 20191221
drwxrwxrwx 2 nginx nginx 4096 Jul 16 22:33 20200716
drwxrwxrwx 2 nginx nginx 4096 Aug  3 22:34 20200803
drwxr-xr-x 2 root  root   126 Aug 18 19:57 20200817
drwxrwxrwx 2 nginx nginx 4096 Aug 18 16:04 20200818
[root@465e0aa491eb upload]# cd 20200817
[root@465e0aa491eb 20200817]# ls -l
total 488
-rwxr-xr-x 1 root root 183116 Aug 18 17:16 20201164114857.png
-rwxr-xr-x 1 root root  32149 Aug 18 17:16 20202146638315.png
-rwxr-xr-x 1 root root 183116 Aug 18 17:16 20202503127232.png
-rwxr-xr-x 1 root root  96546 Aug 18 17:16 20203554555373.png
[root@465e0aa491eb 20200817]#


6、在容器启动时,编辑 Shell 脚本,自动运行 PHP 命令行。


env | grep "CREDITSHOP_CFG_PIC_DATASTATUS" || export CREDITSHOP_CFG_PIC_DATASTATUS="off"
	if [[ $CREDITSHOP_CFG_PIC_DATASTATUS == "off" ]]
	then
		echo "不运行命令"
	elif [[ $CREDITSHOP_CFG_PIC_DATASTATUS == "on" ]]
	then
		cd /mcloud/creditshop/app
		chmod +x Console/cake
		Console/cake asset sync
		echo "运行命令"
	fi


7、查看容器的日志,成功运行,符合预期。如图4
查看容器的日志,成功运行,符合预期。

图4



2020/8/19 下午 3:56:30 CREDITSHOP_CFG_TENANT_URL=http://tenant1.xcttest.chinamcloud.cn/
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_APPNAME=creditshop
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_SECRET=02bcebf67be82fc6c596f7aaab159833090189d8
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_PIC_PORT=80
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_SERVER_PORT=80
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_MEMBERAPI_URL=http://memberapi.wjtest.chinamcloud.cn/memberapi
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_IDA_URL=http://ida.xcttest.chinamcloud.cn
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_QYWX_KEY=okqb7vqm8y2I6Q7zeyyY9K1JfTzs
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_QYWX_URL=qywx.wjtest.chinamcloud.cn
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_PIC_DIR=/webtv/wangjie/creditbak
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_FILE_DIR=/webtv/wangjie/creditbak
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_MSERVER_URL=http://mserver.wjtest.chinamcloud.cn/creditbak
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_QYHD_URL=http://morefunapi.wjtest.chinamcloud.cn
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_BMSHD_URL=http://default.bms.wjtest.chinamcloud.cn
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_ACCESS_KEY_ID=DdEBQtT801LjMuYw
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_ACCESS_KEY_SECRET=FyVST8x73D1AKlhG9brEHvgWkwdeNipz
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_CHECK_USER_LOGIN_URL=http://cmcconsole.wjtest.chinamcloud.cn/interface/get-user-auth
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_USER_LOGIN_URL=http://cmcgroup.wjtest.chinamcloud.cn/?call_back=http://creditshop.wjtest.chinamcloud.cn
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_REDIS_HOST=192.168.2.6
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_REDIS_PASSWORD=q5dHP@xCBUUJY^L
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_REDIS_DB=5
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_FILE_DIR=/webtv/wangjie/creditbak
2020/8/19 下午 3:56:30 CREDITSHOP_CFG_PIC_DATASTATUS=on
2020/8/19 下午 3:56:30
2020/8/19 下午 3:56:30 Welcome to CakePHP v2.6.4 Console
2020/8/19 下午 3:56:30 ---------------------------------------------------------------
2020/8/19 下午 3:56:30 App : app
2020/8/19 下午 3:56:30 Path: /mcloud/creditshop/app/
2020/8/19 下午 3:56:30 ---------------------------------------------------------------
2020/8/19 下午 3:56:30 Asset sync successfully.
2020/8/19 下午 3:56:30 运行命令


]]>
https://www.shuijingwanwq.com/2020/08/25/4457/feed/ 0
在 CakePHP v2.6.4 中运行:./cake hello,报错:Error Shell class HelloShell could not be found. 的分析解决 https://www.shuijingwanwq.com/2020/08/20/4441/ https://www.shuijingwanwq.com/2020/08/20/4441/#respond Thu, 20 Aug 2020 01:57:48 +0000 https://www.shuijingwanwq.com/?p=4441 Post Views: 65 1、参考网址:https://book.cakephp.org/2/en/console-and-shells.html ,创建一个简单的 Hello world shell。 在应用程序的 Console/Command 目录中,创建 HelloShell.php。
<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2020/08/18
 * Time: 15:04
 */

class HelloShell extends AppShell {
    public function main() {
        $this->out('Hello world.');
    }
}

</pre>
2、进入目录:E:\wwwroot\creditshop\app\Console,执行:./cake hello,报错:Error Shell class HelloShell could not be found. 如图1
进入目录:E:\wwwroot\creditshop\app\Console,执行:./cake hello,报错:Error Shell class HelloShell could not be found.

图1



PS E:\wwwroot\creditshop\app\Console> ./cake hello

Error: Shell class HelloShell could not be found.
#0 E:\wwwroot\creditshop\lib\Cake\Console\ShellDispatcher.php(200): ShellDispatcher->_getShell('hello')
#1 E:\wwwroot\creditshop\lib\Cake\Console\ShellDispatcher.php(66): ShellDispatcher->dispatch()
#2 E:\wwwroot\creditshop\app\Console\cake.php(47): ShellDispatcher::run(Array)
#3 {main}



3、参考网址:https://stackoverflow.com/questions/10671634/cakephp-shell-shell-class-helloshell-could-not-be-found ,如图2
参考网址:https://stackoverflow.com/questions/10671634/cakephp-shell-shell-class-helloshell-could-not-be-found

图2

4、进入目录:E:\wwwroot\creditshop\app,执行:Console/cake hello,未再报错。如图3
进入目录:E:\wwwroot\creditshop\app,执行:Console/cake hello,未再报错。

图3



PS E:\wwwroot\creditshop\app> Console/cake hello


Welcome to CakePHP v2.6.4 Console
---------------------------------------------------------------
App : app
Path: E:\wwwroot\creditshop\app\
---------------------------------------------------------------
Hello world.



]]>
https://www.shuijingwanwq.com/2020/08/20/4441/feed/ 0
在 Yii 2.0 中,控制台命令行,添加参数的实现 https://www.shuijingwanwq.com/2020/05/29/4196/ https://www.shuijingwanwq.com/2020/05/29/4196/#respond Fri, 29 May 2020 08:14:57 +0000 https://www.shuijingwanwq.com/?p=4196 Post Views: 128 1、CmcConsoleUser/actionSync,现有实现,参考:https://www.shuijingwanwq.com/2020/03/02/3964/ ,现在需要添加参数:租户ID 2、参数将传递给请求的子命令对应的动作方法。设置参数 $groupId 的默认值为 null


public function actionSync($groupId = null)


3、判断 $groupId 是否为空,如果不为空,且在 $httpCmcApiGroupIds 中不存在,则成功退出


        /* 判断 $groupId 是否为空,如果不为空,且在 $httpCmcApiGroupIds 中不存在,则成功退出 */
        if (!empty($groupId) && !in_array($groupId, $httpCmcApiGroupIds)) {
            return ExitCode::OK;
        }


4、基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds,打印排序前后的数组


        // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
        $sortCmcApiGroupIds = $cmcApiGroupIds;
        print_r($sortCmcApiGroupIds);
        ArrayHelper::multisort($sortCmcApiGroupIds, 'cmc_console_user_last_synced_at', SORT_ASC);
        print_r($sortCmcApiGroupIds);
        exit;




PS E:\wwwroot\pcs-api> ./yii cmc-console-user/sync
Array
(
    [0] => Array
        (
            [group_id] => c10e87f39873512a16727e17f57456a5
            [cmc_console_user_last_synced_at] => 1590649259
        )

    [1] => Array
        (
            [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
            [cmc_console_user_last_synced_at] => 0
        )

    [2] => Array
        (
            [group_id] => 4fd58ceba1fbc537b5402302702131eb
            [cmc_console_user_last_synced_at] => 0
        )

    [3] => Array
        (
            [group_id] => f53df1a8d46108afc8ae9eeb3f0e1f0e
            [cmc_console_user_last_synced_at] => 0
        )

    [4] => Array
        (
            [group_id] => e774bfcf8fc4cfe2ce57ac875a266e94
            [cmc_console_user_last_synced_at] => 0
        )

    [5] => Array
        (
            [group_id] => b28312e46044da2682e540d1fb838c67
            [cmc_console_user_last_synced_at] => 0
        )

)
Array
(
    [0] => Array
        (
            [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
            [cmc_console_user_last_synced_at] => 0
        )

    [1] => Array
        (
            [group_id] => 4fd58ceba1fbc537b5402302702131eb
            [cmc_console_user_last_synced_at] => 0
        )

    [2] => Array
        (
            [group_id] => f53df1a8d46108afc8ae9eeb3f0e1f0e
            [cmc_console_user_last_synced_at] => 0
        )

    [3] => Array
        (
            [group_id] => e774bfcf8fc4cfe2ce57ac875a266e94
            [cmc_console_user_last_synced_at] => 0
        )

    [4] => Array
        (
            [group_id] => b28312e46044da2682e540d1fb838c67
            [cmc_console_user_last_synced_at] => 0
        )

    [5] => Array
        (
            [group_id] => c10e87f39873512a16727e17f57456a5
            [cmc_console_user_last_synced_at] => 1590649259
        )

)


5、现在需要基于参数 $groupId 的值,来同步此租户下的用户,即需要让此租户排在第一位。才能够满足需求。先销毁 $sortCmcApiGroupIds 中的 $groupId,再插入至开头。打印排序前后的数组。符合预期。如图1
现在需要基于参数 $groupId 的值,来同步此租户下的用户,即需要让此租户排在第一位。才能够满足需求。先销毁 $sortCmcApiGroupIds 中的 $groupId,再插入至开头。打印排序前后的数组。符合预期

图1



        // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
        $sortCmcApiGroupIds = $cmcApiGroupIds;
        print_r($sortCmcApiGroupIds);
        ArrayHelper::multisort($sortCmcApiGroupIds, 'cmc_console_user_last_synced_at', SORT_ASC);
        print_r($sortCmcApiGroupIds);

        /* 判断 $groupId 是否为空,如果不为空 */
        if (!empty($groupId)) {
            // 销毁 $sortCmcApiGroupIds 中的 $groupId
            foreach ($sortCmcApiGroupIds as $sortCmcApiGroupIdKey => $sortCmcApiGroupId) {
                if ($sortCmcApiGroupId['group_id'] == $groupId) {
                    unset($sortCmcApiGroupIds[$sortCmcApiGroupIdKey]);
                    break;
                }
            }
            print_r($sortCmcApiGroupIds);
            // 将 $groupId 插入至 $sortCmcApiGroupIds 的开头
            array_unshift($sortCmcApiGroupIds, [
                'group_id' => $groupId,
                'cmc_console_user_last_synced_at' => 0, //上次同步时间
            ]);
        }

        print_r($sortCmcApiGroupIds);
        exit;




PS E:\wwwroot\pcs-api> ./yii cmc-console-user/sync b28312e46044da2682e540d1fb838c67
Array
(
    [0] => Array
        (
            [group_id] => c10e87f39873512a16727e17f57456a5
            [cmc_console_user_last_synced_at] => 1590649259
        )

    [1] => Array
        (
            [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
            [cmc_console_user_last_synced_at] => 0
        )

    [2] => Array
        (
            [group_id] => 4fd58ceba1fbc537b5402302702131eb
            [cmc_console_user_last_synced_at] => 0
        )

    [3] => Array
        (
            [group_id] => f53df1a8d46108afc8ae9eeb3f0e1f0e
            [cmc_console_user_last_synced_at] => 0
        )

    [4] => Array
        (
            [group_id] => e774bfcf8fc4cfe2ce57ac875a266e94
            [cmc_console_user_last_synced_at] => 0
        )

    [5] => Array
        (
            [group_id] => b28312e46044da2682e540d1fb838c67
            [cmc_console_user_last_synced_at] => 0
        )

)
Array
(
    [0] => Array
        (
            [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
            [cmc_console_user_last_synced_at] => 0
        )

    [1] => Array
        (
            [group_id] => 4fd58ceba1fbc537b5402302702131eb
            [cmc_console_user_last_synced_at] => 0
        )

    [2] => Array
        (
            [group_id] => f53df1a8d46108afc8ae9eeb3f0e1f0e
            [cmc_console_user_last_synced_at] => 0
        )

    [3] => Array
        (
            [group_id] => e774bfcf8fc4cfe2ce57ac875a266e94
            [cmc_console_user_last_synced_at] => 0
        )

    [4] => Array
        (
            [group_id] => b28312e46044da2682e540d1fb838c67
            [cmc_console_user_last_synced_at] => 0
        )

    [5] => Array
        (
            [group_id] => c10e87f39873512a16727e17f57456a5
            [cmc_console_user_last_synced_at] => 1590649259
        )

)
Array
(
    [0] => Array
        (
            [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
            [cmc_console_user_last_synced_at] => 0
        )

    [1] => Array
        (
            [group_id] => 4fd58ceba1fbc537b5402302702131eb
            [cmc_console_user_last_synced_at] => 0
        )

    [2] => Array
        (
            [group_id] => f53df1a8d46108afc8ae9eeb3f0e1f0e
            [cmc_console_user_last_synced_at] => 0
        )

    [3] => Array
        (
            [group_id] => e774bfcf8fc4cfe2ce57ac875a266e94
            [cmc_console_user_last_synced_at] => 0
        )

    [5] => Array
        (
            [group_id] => c10e87f39873512a16727e17f57456a5
            [cmc_console_user_last_synced_at] => 1590649259
        )

)
Array
(
    [0] => Array
        (
            [group_id] => b28312e46044da2682e540d1fb838c67
            [cmc_console_user_last_synced_at] => 0
        )

    [1] => Array
        (
            [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
            [cmc_console_user_last_synced_at] => 0
        )

    [2] => Array
        (
            [group_id] => 4fd58ceba1fbc537b5402302702131eb
            [cmc_console_user_last_synced_at] => 0
        )

    [3] => Array
        (
            [group_id] => f53df1a8d46108afc8ae9eeb3f0e1f0e
            [cmc_console_user_last_synced_at] => 0
        )

    [4] => Array
        (
            [group_id] => e774bfcf8fc4cfe2ce57ac875a266e94
            [cmc_console_user_last_synced_at] => 0
        )

    [5] => Array
        (
            [group_id] => c10e87f39873512a16727e17f57456a5
            [cmc_console_user_last_synced_at] => 1590649259
        )

)


6、最终的代码实现


        // 设置同步标识的缓存键
        $redisCache = Yii::$app->redisCache;

        // HTTP 请求,获取开通有效服务的租户ID列表
        $httpCmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();

        /* 判断 $httpCmcApiGroupIds 是否为空,如果为空,则成功退出 */
        if (empty($httpCmcApiGroupIds)) {
            // 延缓执行 60 * 60 秒
            // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);

            return ExitCode::OK;
        }

        /* 判断 $groupId 是否为空,如果不为空,且在 $httpCmcApiGroupIds 中不存在,则成功退出 */
        if (!empty($groupId) && !in_array($groupId, $httpCmcApiGroupIds)) {
            return ExitCode::OK;
        }

        // 设置租户ID列表的缓存键
        $redisCacheGroupIdsKey = 'cmc_api_group_ids';

        // 从缓存中取回租户ID列表
        $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];

        // 是否设置租户ID列表的缓存,默认:否
        $isSetRedisCacheGroupIds = false;

        if ($redisCacheGroupIdsData === false) {
            $cmcApiGroupIds = [];
            foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                $cmcApiGroupIds[] = [
                    'group_id' => $httpCmcApiGroupId,
                    'cmc_console_user_last_synced_at' => 0, //上次同步时间
                ];
            }
            // 是否设置租户ID列表的缓存:是
            $isSetRedisCacheGroupIds = true;
        } else {
            // 获取 group_id 值列表
            $redisCacheGroupIds = ArrayHelper::getColumn($redisCacheGroupIdsData, 'group_id');
            $cmcApiGroupIds = $redisCacheGroupIdsData;

            /* 删除出现在 $cmcApiGroupIds 中但是未出现在 $httpCmcApiGroupIds 中的租户 */
            foreach ($cmcApiGroupIds as $cmcApiGroupIdKey => $cmcApiGroupId) {
                if (!in_array($cmcApiGroupId['group_id'], $httpCmcApiGroupIds)) {
                    unset($cmcApiGroupIds[$cmcApiGroupIdKey]);
                    // 是否设置租户ID列表的缓存:是
                    $isSetRedisCacheGroupIds = true;
                }
            }

            foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                if (!in_array($httpCmcApiGroupId, $redisCacheGroupIds)) {
                    $cmcApiGroupIds[] = [
                        'group_id' => $httpCmcApiGroupId,
                        'cmc_console_user_last_synced_at' => 0, //上次同步时间
                    ];
                    // 是否设置租户ID列表的缓存:是
                    $isSetRedisCacheGroupIds = true;
                }
            }
        }

        // 判断是否设置租户ID列表的缓存
        if ($isSetRedisCacheGroupIds) {
            // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
            $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);
        }


        // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
        $sortCmcApiGroupIds = $cmcApiGroupIds;
        ArrayHelper::multisort($sortCmcApiGroupIds, 'cmc_console_user_last_synced_at', SORT_ASC);

        /* 判断 $groupId 是否为空,如果不为空 */
        if (!empty($groupId)) {
            // 销毁 $sortCmcApiGroupIds 中的 $groupId
            foreach ($sortCmcApiGroupIds as $sortCmcApiGroupIdKey => $sortCmcApiGroupId) {
                if ($sortCmcApiGroupId['group_id'] == $groupId) {
                    unset($sortCmcApiGroupIds[$sortCmcApiGroupIdKey]);
                    break;
                }
            }
            // 将 $groupId 插入至 $sortCmcApiGroupIds 的开头
            array_unshift($sortCmcApiGroupIds, [
                'group_id' => $groupId,
                'cmc_console_user_last_synced_at' => 0, //上次同步时间
            ]);
        }



 ]]>
https://www.shuijingwanwq.com/2020/05/29/4196/feed/ 0
在 Google Chrome 浏览器中,今日头条打开后,一片空白的分析解决 https://www.shuijingwanwq.com/2020/03/04/3986/ https://www.shuijingwanwq.com/2020/03/04/3986/#respond Wed, 04 Mar 2020 02:07:57 +0000 https://www.shuijingwanwq.com/?p=3986 Post Views: 273 1、在 Google Chrome 浏览器中,今日头条打开后,一片空白,如图1
在 Google Chrome 浏览器中,今日头条打开后,一片空白

图1



Request URL: https://verify.snssdk.com/reportError
Referrer Policy: no-referrer-when-downgrade


2、查看 Console,报错:Access to XMLHttpRequest at ‘https://verify.snssdk.com/reportError’ from origin ‘https://www.toutiao.com’ has been blocked by CORS policy: Request header field ra-ver is not allowed by Access-Control-Allow-Headers in preflight response.如图2
查看 Console,报错:Access to XMLHttpRequest at 'https://verify.snssdk.com/reportError' from origin 'https://www.toutiao.com' has been blocked by CORS policy: Request header field ra-ver is not allowed by Access-Control-Allow-Headers in preflight response.

图2

3、清除浏览数据,包含基本与高级,仍然一片空白,如图3
清除浏览数据,包含基本与高级,仍然一片空白

图3

4、打开新的无痕窗口,今日头条打开后,正常,查看 Console,未报错,如图4
打开新的无痕窗口,今日头条打开后,正常,查看 Console,未报错

图4

<pre class="wp-block-syntaxhighlighter-code">

version:0.1.7
pc_text.js:1 version:0.1.7
(index):1 A cookie associated with a cross-site resource at http://i.snssdk.com/ was set without the `SameSite` attribute. A future release of Chrome will only deliver cookies with cross-site requests if they are set with `SameSite=None` and `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.
vendor.e2caf4792c6de5bf3950.js:112 [tea-sdk][default]已禁止默认上报predefine_pageview事件,需手动上报。
VM15:1 
raven.js:1 [tea-sdk][default]看到本提示,意味着用户信息已完全就绪,上报通道打开。用户标识如下:
raven.js:1 [tea-sdk][default] {
  "user_unique_id": "6799972943449261575",
  "web_id": "6799972943449261575",
  "ssid": "e58f1dbf-0ff2-4702-94b4-6cc59d325369"
}
raven.js:1 [tea-sdk] SDK已经初始化过了!请检查是否多次引入了SDK文件
e.<computed> @ raven.js:1
2p3.pstatp.com/list/240x240/6799949924902619915:1 Failed to load resource: the server responded with a status of 400 (Bad Request)
(index):1 A cookie associated with a cross-site resource at https://xxbg.snssdk.com/ was set without the `SameSite` attribute. A future release of Chrome will only deliver cookies with cross-site requests if they are set with `SameSite=None` and `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.
p3.pstatp.com/list/240x240/6799949924902619915:1 Failed to load resource: the server responded with a status of 400 (Bad Request)

</pre>
5、对比请求:https://verify.snssdk.com/reportError 的响应,一为无响应,一为有响应。如图5
对比请求:https://verify.snssdk.com/reportError 的响应,一为无响应,一为有响应。

图5



{"code":"200","data":null,"msg":""}


6、发现无痕模式下,新增加了下载APP、注册头条号等链接,隐约记得之前是没有的。那么这次打开后一片空白的原因,可能在于今日头条升级了新版本,不兼容导致的。如图6
发现无痕模式下,新增加了下载APP、注册头条号等链接,隐约记得之前是没有的。那么这次打开后一片空白的原因,可能在于今日头条升级了新版本,不兼容导致的。

图6

7、最后在360软件管家中,卸载了 Google Chrome 浏览器,如图7
最后在360软件管家中,卸载了 Google Chrome 浏览器

图7

8、卸载时,勾选同时删除你的浏览数据吗?如图8
卸载时,勾选同时删除你的浏览数据吗?

图8

9、重新安装 Google Chrome 浏览器,再次打开今日头条,正常,如图9
重新安装 Google Chrome 浏览器,再次打开今日头条,正常

图9

]]>
https://www.shuijingwanwq.com/2020/03/04/3986/feed/ 0
在 Yii 2.0 中,控制台命令的集群实现,Redis模型的锁定实现(以保证同一时间段内,即使多台服务器皆在运行命令行,但是每台服务器运行的任务是不重复的,以提升命令行的总体处理性能) https://www.shuijingwanwq.com/2020/03/02/3964/ https://www.shuijingwanwq.com/2020/03/02/3964/#respond Mon, 02 Mar 2020 05:54:25 +0000 https://www.shuijingwanwq.com/?p=3964 Post Views: 71 1、Docker 部署,基于 Supervisor 的 crontab (bash sleep) 的实现,以降低内存占用,参考:https://www.shuijingwanwq.com/2019/10/12/3555/ 。/console/controllers/CmcConsoleUserController.php
<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */

namespace console\controllers;

use Yii;
use console\models\ConfigColumnUser;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\services\CmcApiGroupService;
use console\services\CmcConsoleUserService;
use yii\base\InvalidArgumentException;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;

/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)、栏目人员配置模型(MySQL)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     * @throws InvalidArgumentException if the $direction or $sortFlag parameters do not have
     */
    public function actionSync()
    {
        // 设置同步标识的缓存键
        $redisCache = Yii::$app->redisCache;
        $redisCacheIdentityKey = 'cmc_console_user_sync';

        // 从缓存中取回同步标识
        $redisCacheIdentityData = $redisCache[$redisCacheIdentityKey];

        if ($redisCacheIdentityData === false) {
            // HTTP 请求,获取开通有效服务的租户ID列表
            $httpCmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();

            /* 判断 $httpCmcApiGroupIds 是否为空,如果为空,则成功退出 */
            if (empty($httpCmcApiGroupIds)) {
                // 延缓执行 60 * 60 秒
                // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);

                return ExitCode::OK;
            }

            // 设置租户ID列表的缓存键
            $redisCacheGroupIdsKey = 'cmc_api_group_ids';

            // 从缓存中取回租户ID列表
            $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];

            // 是否设置租户ID列表的缓存,默认:否
            $isSetRedisCacheGroupIds = false;

            if ($redisCacheGroupIdsData === false) {
                $cmcApiGroupIds = [];
                foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                    $cmcApiGroupIds[] = [
                        'group_id' => $httpCmcApiGroupId,
                        'cmc_console_user_last_synced_at' => 0, //上次同步时间
                    ];
                }
                // 是否设置租户ID列表的缓存:是
                $isSetRedisCacheGroupIds = true;
            } else {
                // 获取 group_id 值列表
                $redisCacheGroupIds = ArrayHelper::getColumn($redisCacheGroupIdsData, 'group_id');
                $cmcApiGroupIds = $redisCacheGroupIdsData;
                foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                    if (!in_array($httpCmcApiGroupId, $redisCacheGroupIds)) {
                        $cmcApiGroupIds[] = [
                            'group_id' => $httpCmcApiGroupId,
                            'cmc_console_user_last_synced_at' => 0, //上次同步时间
                        ];
                        // 是否设置租户ID列表的缓存:是
                        $isSetRedisCacheGroupIds = true;
                    }
                }
            }

            // 判断是否设置租户ID列表的缓存
            if ($isSetRedisCacheGroupIds) {
                // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
                $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);
            }


            // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
            $sortCmcApiGroupIds = $cmcApiGroupIds;
            ArrayHelper::multisort($sortCmcApiGroupIds, 'cmc_console_user_last_synced_at', SORT_ASC);

            foreach ($sortCmcApiGroupIds as $sortCmcApiGroupId) {
                $isLockExist = RedisCmcConsoleUser::isLockExist($sortCmcApiGroupId['group_id']);
                // 返回 true,表示锁定存在,即已经被其他客户端锁定
                if ($isLockExist === true) {
                    continue;
                }

                // HTTP请求,获取租户ID下的用户列表
                $httpGetUserListData = [
                    'groupId' => $sortCmcApiGroupId['group_id'],
                    'loginId' => '',
                    'loginTid' => '',
                ];
                $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);

                // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
                $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');

                // 销毁变量
                unset($getUserListData['list']);

                // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
                $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->indexBy('id')->asArray()->all();

                // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
                $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
                // 销毁变量
                unset($redisCmcConsoleUserItems);
                if (!empty($redisArrayDiffItems)) {
                    $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                    // 销毁变量
                    unset($redisArrayDiffItems);
                    if (RedisCmcConsoleUser::deleteAllByIds($sortCmcApiGroupId['group_id'], $redisArrayDiffIds) === false) {
                        continue;
                    }
                }

                // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
                $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->isDeletedNo()->indexBy('user_id')->asArray()->all();

                // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Http) 中的记录
                $diffItems = array_diff_key($configColumnUserItems, $httpCmcConsoleUserItems);

                // 销毁变量
                unset($configColumnUserItems);
                if (!empty($diffItems)) {
                    // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                    $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                        'and',
                        ['group_id' => $sortCmcApiGroupId['group_id']],
                        ['in', 'user_id', $diffItems],
                    ])->isDeletedNo()->all();
                    foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                        /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                        $toBeDeletedConfigColumnUserItem->softDelete();
                    }
                }

                // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
                foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                    $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();

                    if (!isset($redisCmcConsoleUserItem)) {

                        $redisCmcConsoleUser = new RedisCmcConsoleUser();
                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                            $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUser->attributes = $attributes;
                        $redisCmcConsoleUser->insert();

                    } else {
                        if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {

                            $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                                $httpCmcConsoleUserItemValue);
                            $redisCmcConsoleUserItem->attributes = $attributes;
                            $redisCmcConsoleUserItem->save();

                        }
                    }

                }

                // 从缓存中取回租户ID列表
                $cmcApiGroupIds = $redisCache[$redisCacheGroupIdsKey];
                // 设置当前租户的上次同步时间
                foreach ($cmcApiGroupIds as $cmcApiGroupIdKey => $cmcApiGroupId) {
                    if ($cmcApiGroupId['group_id'] == $sortCmcApiGroupId['group_id']) {
                        $cmcApiGroupIds[$cmcApiGroupIdKey]['cmc_console_user_last_synced_at'] = time();
                        break;
                    }
                }

                // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
                $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);

                // break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表
                break;
            }

            // 延缓执行 60 秒
            // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
            // 将同步标识存放到缓存供下次使用,将数据在缓存中保留 60 秒
            $redisCache->set($redisCacheIdentityKey, $redisCacheIdentityKey, Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);

            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

            return ExitCode::OK;
        } else {
            // 延缓执行 60 秒
            // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
            return ExitCode::OK;
        }
    }

    /**
     * 获取属性列表
     * @param string $groupId 租户ID
     * @param array $httpCmcConsoleUser 框架服务控制台的用户模型(Http)
     *
     * @return array
     */
    private function getAttributes($groupId, $httpCmcConsoleUser) {
        return [
            'id' => $httpCmcConsoleUser['id'],
            'group_id' => $groupId,
            'login_name' => $httpCmcConsoleUser['login_name'],
            'user_token' => $httpCmcConsoleUser['user_token'],
            'user_nick' => $httpCmcConsoleUser['user_nick'],
            'user_pic' => $httpCmcConsoleUser['user_pic'],
            'user_mobile' => $httpCmcConsoleUser['user_mobile'] ? $httpCmcConsoleUser['user_mobile'] : '',
            'user_email' => $httpCmcConsoleUser['user_email'] ? $httpCmcConsoleUser['user_email'] : '',
            'user_sex' => $httpCmcConsoleUser['user_sex'],
            'user_type' => $httpCmcConsoleUser['user_type'],
            'user_birthday' => $httpCmcConsoleUser['user_birthday'],
            'user_chat_id' => $httpCmcConsoleUser['user_chat_id'] ? $httpCmcConsoleUser['user_chat_id'] : '',
            'is_open' => $httpCmcConsoleUser['is_open'],
            'add_time' => $httpCmcConsoleUser['add_time'],
            'update_time' => $httpCmcConsoleUser['update_time'],
        ];
    }
}

</pre>
2、break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表。当租户数量过多的时候,假设 100 个租户,平均 1 个租户的同步时间为 10 秒钟 + 下一个租户的同步的间隔时间 60 秒(包含了 bash sleep 5 秒),那么可能某个租户下的用户同步,其最大间隔时间就有可能为 7000 秒钟了。因此,如果支持了集群的部署,假设为 10 个容器,那么某个租户下的用户同步,其最大间隔时间就有可能为 700 秒钟了,同步的即时性提升了 10 倍。/mcloud/yii-cmc-console-user-sync.ini


[program:yii-cmc-console-user-sync]
command = bash -c 'sleep 5 && exec php /mcloud/www/pcs-api/yii cmc-console-user/sync'
autorestart = true
startsecs = 5
stopwaitsecs = 10
stderr_logfile = /data/logs/yii-cmc-console-user-sync-stderr.log
stdout_logfile = /data/logs/yii-cmc-console-user-sync-stdout.log


3、如果要支持集群的部署,现阶段存在的问题:同一个租户下的用户同步,可能在某一时间段,多个容器皆在执行,导致了同步的冗余执行(未达到最大化利用集群部署,进而满足同步的即时性提升至 10 倍的理想值)。因此,应该保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。 4、将同步标识存放到缓存供下次使用,将数据在缓存中保留 60 秒。之前实现的初衷是同步成功一个租户下的用户后,间隔 60 秒再同步下一个租户下的用户,以降低资源消耗。现在如果要支持集群,就需要取消。否则会导致某个容器同步成功一个租户下的用户后,在 60 秒的间隔时间段内,其他容器也无法同步其他租户下的用户。/console/controllers/CmcConsoleUserController.php
<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */

namespace console\controllers;

use Yii;
use console\models\ConfigColumnUser;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\services\CmcApiGroupService;
use console\services\CmcConsoleUserService;
use yii\base\InvalidArgumentException;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;

/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)、栏目人员配置模型(MySQL)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     * @throws InvalidArgumentException if the $direction or $sortFlag parameters do not have
     */
    public function actionSync()
    {
        // 设置同步标识的缓存键
        $redisCache = Yii::$app->redisCache;

        // HTTP 请求,获取开通有效服务的租户ID列表
        $httpCmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();

        /* 判断 $httpCmcApiGroupIds 是否为空,如果为空,则成功退出 */
        if (empty($httpCmcApiGroupIds)) {
            // 延缓执行 60 * 60 秒
            // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);

            return ExitCode::OK;
        }

        // 设置租户ID列表的缓存键
        $redisCacheGroupIdsKey = 'cmc_api_group_ids';

        // 从缓存中取回租户ID列表
        $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];

        // 是否设置租户ID列表的缓存,默认:否
        $isSetRedisCacheGroupIds = false;

        if ($redisCacheGroupIdsData === false) {
            $cmcApiGroupIds = [];
            foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                $cmcApiGroupIds[] = [
                    'group_id' => $httpCmcApiGroupId,
                    'cmc_console_user_last_synced_at' => 0, //上次同步时间
                ];
            }
            // 是否设置租户ID列表的缓存:是
            $isSetRedisCacheGroupIds = true;
        } else {
            // 获取 group_id 值列表
            $redisCacheGroupIds = ArrayHelper::getColumn($redisCacheGroupIdsData, 'group_id');
            $cmcApiGroupIds = $redisCacheGroupIdsData;
            foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                if (!in_array($httpCmcApiGroupId, $redisCacheGroupIds)) {
                    $cmcApiGroupIds[] = [
                        'group_id' => $httpCmcApiGroupId,
                        'cmc_console_user_last_synced_at' => 0, //上次同步时间
                    ];
                    // 是否设置租户ID列表的缓存:是
                    $isSetRedisCacheGroupIds = true;
                }
            }
        }

        // 判断是否设置租户ID列表的缓存
        if ($isSetRedisCacheGroupIds) {
            // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
            $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);
        }


        // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
        $sortCmcApiGroupIds = $cmcApiGroupIds;
        ArrayHelper::multisort($sortCmcApiGroupIds, 'cmc_console_user_last_synced_at', SORT_ASC);

        foreach ($sortCmcApiGroupIds as $sortCmcApiGroupId) {
            $isLockExist = RedisCmcConsoleUser::isLockExist($sortCmcApiGroupId['group_id']);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }

            // HTTP请求,获取租户ID下的用户列表
            $httpGetUserListData = [
                'groupId' => $sortCmcApiGroupId['group_id'],
                'loginId' => '',
                'loginTid' => '',
            ];
            $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);

            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');

            // 销毁变量
            unset($getUserListData['list']);

            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->indexBy('id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
            $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
            // 销毁变量
            unset($redisCmcConsoleUserItems);
            if (!empty($redisArrayDiffItems)) {
                $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                // 销毁变量
                unset($redisArrayDiffItems);
                if (RedisCmcConsoleUser::deleteAllByIds($sortCmcApiGroupId['group_id'], $redisArrayDiffIds) === false) {
                    continue;
                }
            }

            // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
            $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->isDeletedNo()->indexBy('user_id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Http) 中的记录
            $diffItems = array_diff_key($configColumnUserItems, $httpCmcConsoleUserItems);

            // 销毁变量
            unset($configColumnUserItems);
            if (!empty($diffItems)) {
                // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                    'and',
                    ['group_id' => $sortCmcApiGroupId['group_id']],
                    ['in', 'user_id', $diffItems],
                ])->isDeletedNo()->all();
                foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                    /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                    $toBeDeletedConfigColumnUserItem->softDelete();
                }
            }

            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();

                if (!isset($redisCmcConsoleUserItem)) {

                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                        $httpCmcConsoleUserItemValue);
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();

                } else {
                    if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {

                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                            $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUserItem->attributes = $attributes;
                        $redisCmcConsoleUserItem->save();

                    }
                }

            }

            // 从缓存中取回租户ID列表
            $cmcApiGroupIds = $redisCache[$redisCacheGroupIdsKey];
            // 设置当前租户的上次同步时间
            foreach ($cmcApiGroupIds as $cmcApiGroupIdKey => $cmcApiGroupId) {
                if ($cmcApiGroupId['group_id'] == $sortCmcApiGroupId['group_id']) {
                    $cmcApiGroupIds[$cmcApiGroupIdKey]['cmc_console_user_last_synced_at'] = time();
                    break;
                }
            }

            // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
            $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);

            // break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表
            break;
        }

        // 延缓执行 60 秒
        // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);

        // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

        return ExitCode::OK;
    }

}

</pre>
5、此时,当租户数量过多的时候,假设 100 个租户,平均 1 个租户的同步时间为 10 秒钟 + 下一个租户的同步的间隔时间 60 秒(包含了 bash sleep 60 秒),那么可能某个租户下的用户同步,其最大间隔时间就有可能为 7000 秒钟了。因此,如果支持了集群的部署,假设为 10 个容器,那么某个租户下的用户同步,其最大间隔时间就有可能为 700 秒钟了,同步的即时性提升了 10 倍。/mcloud/yii-cmc-console-user-sync.ini


[program:yii-cmc-console-user-sync]
command = bash -c 'sleep 60 && exec php /mcloud/www/pcs-api/yii cmc-console-user/sync'
autorestart = true
startsecs = 5
stopwaitsecs = 10
stderr_logfile = /data/logs/yii-cmc-console-user-sync-stderr.log
stdout_logfile = /data/logs/yii-cmc-console-user-sync-stdout.log


6、编辑 /common/logics/redis/Lock.php,Redis模型的锁定实现
<pre class="wp-block-syntaxhighlighter-code">

<?php

namespace common\logics\redis;

use Yii;

/**
 * This is the model class for table "{{%lock}}".
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Lock extends \yii\redis\ActiveRecord
{

    /**
     * Redis模型的锁定实现
     * @param string $lockKeyName 锁定键名
     * 格式如下:
     *
     * 'game_category' //锁定键名,如比赛分类
     *
     * @return bool 成功返回真/失败返回假
     * 格式如下:
     *
     * true //状态,获取锁定成功,可继续执行
     * 
     * 或者
     * 
     * false //状态,获取锁定失败,不可继续执行
     *
     */
    public function lock($lockKeyName)
    {
        // 设置锁定的过期时间,获取相关锁定参数
        $time = time();
        $lockKey = Yii::$app->params['redisLock']['keyPrefix'] . $lockKeyName;
        $lockExpire = $time + Yii::$app->params['redisLock']['timeOut'];
        // 获取 Redis 连接,以执行相关命令
        $redis = Yii::$app->redis;
        // 获取锁定
        $executeCommandResult = $redis->setnx($lockKey, $lockExpire);
        // 返回0,表示已经被其他客户端锁定
        if ($executeCommandResult == 0) {
            // 防止死锁,获取当前锁的过期时间
            $lockCurrentExpire = $redis->get($lockKey);
            // 判断锁是否过期,如果已经过期
            if ($time > $lockCurrentExpire) {
                // 防止并发锁定,检查存储在 key 的旧值是否仍然是过期的时间戳,如果是,则获取锁定,否则返回假
                $executeCommandResult = $redis->getset($lockKey, $lockExpire);
                if ($lockCurrentExpire != $executeCommandResult) {
                    return false;
                }
            }

            // 返回0,表示已经被其他客户端锁定,且不存在死锁,返回假
            if ($executeCommandResult == 0) {
                return false;
            }

        }
        
        return true;
    }
    
    /**
     * 判断Redis模型的锁定是否存在
     * @param string $lockKeyName 锁定键名
     * 格式如下:
     *
     * 'game_category' //锁定键名,如比赛分类
     *
     * @return bool 锁定是否存在
     * 格式如下:
     *
     * true //状态:已存在
     * 
     * 或者
     * 
     * false //状态:不存在
     *
     */
    public function isLockExist($lockKeyName)
    {
        // 获取相关锁定参数
        $time = time();
        $lockKey = Yii::$app->params['redisLock']['keyPrefix'] . $lockKeyName;
        // 获取 Redis 连接,以执行相关命令
        $redis = Yii::$app->redis;
        // 获取锁定
        $executeCommandResult = $redis->get($lockKey);

        // 返回NULL,表示不存在锁定,否则表示存在
        if ($executeCommandResult === null) {
            return false;
        } else {
            // 如果存在锁定,判断锁是否过期,如果已经过期,则仍然认定为不存在锁定
            if ($time > $executeCommandResult) {
                // 如果已经过期,则释放锁定
                $redis->del($lockKey);
                return false;
            }
            
        }

        return true;
    }

    /**
     * Redis模型的释放锁定实现
     * @param string $lockKeyName 锁定键名
     * 格式如下:
     *
     * 'game_category' //锁定键名,如比赛分类
     *
     * @return integer 被删除的keys的数量
     * 格式如下:
     *
     * 1 //被删除的keys的数量
     * 
     * 或者
     * 
     * 0 //被删除的keys的数量
     *
     */
    public function unlock($lockKeyName)
    {
        // 获取相关锁定参数
        $lockKey = Yii::$app->params['redisLock']['keyPrefix'] . $lockKeyName;
        // 获取 Redis 连接,以执行相关命令
        $redis = Yii::$app->redis;
        // 释放锁定
        return $redis->del($lockKey);
    }
}


</pre>
7、编辑 /console/controllers/CmcConsoleUserController.php,Redis模型的锁定实现。在开始一个租户下的用户同步之前,判断Redis模型的锁定是否存在,如果存在,则跳出当前循环,继续下一个租户的同步。在开始一个租户下的用户同步之前,Redis模型的获取锁定实现,如果获取锁定失败,则跳出当前循环,继续下一个租户的同步。一个租户下的用户同步成功之后,释放锁定。结束循环。
<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */

namespace console\controllers;

use Yii;
use console\models\ConfigColumnUser;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\models\redis\Lock as RedisLock;
use console\services\CmcApiGroupService;
use console\services\CmcConsoleUserService;
use yii\base\InvalidArgumentException;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;

/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)、栏目人员配置模型(MySQL)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     * @throws InvalidArgumentException if the $direction or $sortFlag parameters do not have
     */
    public function actionSync()
    {
        // 设置同步标识的缓存键
        $redisCache = Yii::$app->redisCache;

        // HTTP 请求,获取开通有效服务的租户ID列表
        $httpCmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();

        /* 判断 $httpCmcApiGroupIds 是否为空,如果为空,则成功退出 */
        if (empty($httpCmcApiGroupIds)) {
            // 延缓执行 60 * 60 秒
            // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);

            return ExitCode::OK;
        }

        // 设置租户ID列表的缓存键
        $redisCacheGroupIdsKey = 'cmc_api_group_ids';

        // 从缓存中取回租户ID列表
        $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];

        // 是否设置租户ID列表的缓存,默认:否
        $isSetRedisCacheGroupIds = false;

        if ($redisCacheGroupIdsData === false) {
            $cmcApiGroupIds = [];
            foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                $cmcApiGroupIds[] = [
                    'group_id' => $httpCmcApiGroupId,
                    'cmc_console_user_last_synced_at' => 0, //上次同步时间
                ];
            }
            // 是否设置租户ID列表的缓存:是
            $isSetRedisCacheGroupIds = true;
        } else {
            // 获取 group_id 值列表
            $redisCacheGroupIds = ArrayHelper::getColumn($redisCacheGroupIdsData, 'group_id');
            $cmcApiGroupIds = $redisCacheGroupIdsData;
            foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                if (!in_array($httpCmcApiGroupId, $redisCacheGroupIds)) {
                    $cmcApiGroupIds[] = [
                        'group_id' => $httpCmcApiGroupId,
                        'cmc_console_user_last_synced_at' => 0, //上次同步时间
                    ];
                    // 是否设置租户ID列表的缓存:是
                    $isSetRedisCacheGroupIds = true;
                }
            }
        }

        // 判断是否设置租户ID列表的缓存
        if ($isSetRedisCacheGroupIds) {
            // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
            $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);
        }


        // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
        $sortCmcApiGroupIds = $cmcApiGroupIds;
        ArrayHelper::multisort($sortCmcApiGroupIds, 'cmc_console_user_last_synced_at', SORT_ASC);

        foreach ($sortCmcApiGroupIds as $sortCmcApiGroupId) {
            $isLockExist = RedisCmcConsoleUser::isLockExist($sortCmcApiGroupId['group_id']);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }

            /* 判断Redis模型的锁定是否存在 */
            $redisLockKeyName = 'cmc_console_user:sync:' . implode(':', ['group_id' => $sortCmcApiGroupId['group_id']]);
            $redisLock = new RedisLock();
            $redisLockResult = $redisLock->isLockExist($redisLockKeyName);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($redisLockResult === true) {
                continue;
            }

            /* Redis模型的锁定实现 */
            $redisLockResult = $redisLock->lock($redisLockKeyName);
            // 返回 false,表示已经被其他客户端锁定
            if ($redisLockResult === false) {
                continue;
            }

            // HTTP请求,获取租户ID下的用户列表
            $httpGetUserListData = [
                'groupId' => $sortCmcApiGroupId['group_id'],
                'loginId' => '',
                'loginTid' => '',
            ];
            $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);

            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');

            // 销毁变量
            unset($getUserListData['list']);

            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->indexBy('id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
            $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
            // 销毁变量
            unset($redisCmcConsoleUserItems);
            if (!empty($redisArrayDiffItems)) {
                $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                // 销毁变量
                unset($redisArrayDiffItems);
                if (RedisCmcConsoleUser::deleteAllByIds($sortCmcApiGroupId['group_id'], $redisArrayDiffIds) === false) {
                    continue;
                }
            }

            // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
            $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->isDeletedNo()->indexBy('user_id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Http) 中的记录
            $diffItems = array_diff_key($configColumnUserItems, $httpCmcConsoleUserItems);

            // 销毁变量
            unset($configColumnUserItems);
            if (!empty($diffItems)) {
                // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                    'and',
                    ['group_id' => $sortCmcApiGroupId['group_id']],
                    ['in', 'user_id', $diffItems],
                ])->isDeletedNo()->all();
                foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                    /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                    $toBeDeletedConfigColumnUserItem->softDelete();
                }
            }

            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();

                if (!isset($redisCmcConsoleUserItem)) {

                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                        $httpCmcConsoleUserItemValue);
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();

                } else {
                    if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {

                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                            $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUserItem->attributes = $attributes;
                        $redisCmcConsoleUserItem->save();

                    }
                }

            }

            // 从缓存中取回租户ID列表
            $cmcApiGroupIds = $redisCache[$redisCacheGroupIdsKey];
            // 设置当前租户的上次同步时间
            foreach ($cmcApiGroupIds as $cmcApiGroupIdKey => $cmcApiGroupId) {
                if ($cmcApiGroupId['group_id'] == $sortCmcApiGroupId['group_id']) {
                    $cmcApiGroupIds[$cmcApiGroupIdKey]['cmc_console_user_last_synced_at'] = time();
                    break;
                }
            }

            // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
            $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);

            // 释放锁定
            $redisLock->unlock($redisLockKeyName);

            // break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表
            break;
        }

        // 延缓执行 60 秒
        // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);

        // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

        return ExitCode::OK;
    }

}

</pre>
8、当一个租户下的用户同步时间长度小于等于 Redis锁定超时时间 的值 10秒时(sleep(8),实际长度超过了 8 秒),此时,此租户已经处于锁定状态,进而导致部署为集群时,可以保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。在本地环境中,模拟了 3 个容器,3 个容器开始同步时间:1582784312、1582784315、1582784318,分别间隔 3 秒、3秒,皆小于 10 秒。第 1 个容器同步了租户:c10e87f39873512a16727e17f57456a5,第 2 个容器同步了租户:015ce30b116ce86058fa6ab4fea4ac63,第 3 个容器同步了租户:4fd58ceba1fbc537b5402302702131eb。3 个容器的并行执行,分别同步了 3 个租户,符合预期。文件生成顺序体现出了执行执行流程。如图1
当一个租户下的用户同步时间长度小于等于 Redis锁定超时时间 的值 10秒时(sleep(8),实际长度超过了 8 秒),此时,此租户已经处于锁定状态,进而导致部署为集群时,可以保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。在本地环境中,模拟了 3 个容器,3 个容器开始同步时间:1582784312、1582784315、1582784318,分别间隔 3 秒、3秒,皆小于 10 秒。第 1 个容器同步了租户:c10e87f39873512a16727e17f57456a5,第 2 个容器同步了租户:015ce30b116ce86058fa6ab4fea4ac63,第 3 个容器同步了租户:4fd58ceba1fbc537b5402302702131eb。3 个容器的并行执行,分别同步了 3 个租户,符合预期。文件生成顺序体现出了执行执行流程。

图1



    'redisLock' => [
        'keyPrefix' => 'pa:lock:', //Redis锁定 key 前缀
        'timeOut' => 10, //Redis锁定超时时间,单位为秒
    ],




            file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-' . $sortCmcApiGroupId['group_id'] . '-' . time() . '.txt', $sortCmcApiGroupId['group_id']);

            /* 判断Redis模型的锁定是否存在 */
            $redisLockKeyName = 'cmc_console_user:sync:' . implode(':', ['group_id' => $sortCmcApiGroupId['group_id']]);
            $redisLock = new RedisLock();
            $isLockExist = $redisLock->isLockExist($redisLockKeyName);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-is-lock-exist-' . $sortCmcApiGroupId['group_id'] . '-' . time() . '.txt', $sortCmcApiGroupId['group_id']);
                continue;
            }

            /* Redis模型的锁定实现 */
            $lock = $redisLock->lock($redisLockKeyName);
            // 返回 false,表示已经被其他客户端锁定
            if ($lock === false) {
                file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-lock-' . $sortCmcApiGroupId['group_id'] . '-' . time() . '.txt', $sortCmcApiGroupId['group_id']);
                continue;
            }

            // HTTP请求,获取租户ID下的用户列表
            $httpGetUserListData = [
                'groupId' => $sortCmcApiGroupId['group_id'],
                'loginId' => '',
                'loginTid' => '',
            ];
            $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);

            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');

            // 销毁变量
            unset($getUserListData['list']);

            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->indexBy('id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
            $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
            // 销毁变量
            unset($redisCmcConsoleUserItems);
            if (!empty($redisArrayDiffItems)) {
                $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                // 销毁变量
                unset($redisArrayDiffItems);
                if (RedisCmcConsoleUser::deleteAllByIds($sortCmcApiGroupId['group_id'], $redisArrayDiffIds) === false) {
                    continue;
                }
            }

            // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
            $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->isDeletedNo()->indexBy('user_id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Http) 中的记录
            $diffItems = array_diff_key($configColumnUserItems, $httpCmcConsoleUserItems);

            // 销毁变量
            unset($configColumnUserItems);
            if (!empty($diffItems)) {
                // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                    'and',
                    ['group_id' => $sortCmcApiGroupId['group_id']],
                    ['in', 'user_id', $diffItems],
                ])->isDeletedNo()->all();
                foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                    /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                    $toBeDeletedConfigColumnUserItem->softDelete();
                }
            }

            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();

                if (!isset($redisCmcConsoleUserItem)) {

                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                        $httpCmcConsoleUserItemValue);
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();

                } else {
                    if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {

                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                            $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUserItem->attributes = $attributes;
                        $redisCmcConsoleUserItem->save();

                    }
                }

            }

            // 从缓存中取回租户ID列表
            $cmcApiGroupIds = $redisCache[$redisCacheGroupIdsKey];
            // 设置当前租户的上次同步时间
            foreach ($cmcApiGroupIds as $cmcApiGroupIdKey => $cmcApiGroupId) {
                if ($cmcApiGroupId['group_id'] == $sortCmcApiGroupId['group_id']) {
                    $cmcApiGroupIds[$cmcApiGroupIdKey]['cmc_console_user_last_synced_at'] = time();
                    break;
                }
            }

            // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
            sleep(8);
            $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);

            // 释放锁定
            $redisLock->unlock($redisLockKeyName);


9、打印出从缓存中取回租户ID列表,已经成功同步了 3 个租户:c10e87f39873512a16727e17f57456a5、015ce30b116ce86058fa6ab4fea4ac63、4fd58ceba1fbc537b5402302702131eb,但是,仅有租户 4fd58ceba1fbc537b5402302702131eb 的同步时间得到了更新。虽然在实际的生产环境中,不存在 sleep(8),此为在本地环境模拟并发请求,而添加的。但是,理论上来说,从缓存中取回租户ID列表,到 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留 的间隔时间段(假设租户数量为 10000 个,则遍历 10000 次,时间间隔为 30000 微秒,即 0.03 秒)内,如果存在 多个容器 在运行的话,便会出现相互覆盖的情况。暂时不做处理,即时出现了相互覆盖的情况,其后果是可以接受的。租户:c10e87f39873512a16727e17f57456a5、015ce30b116ce86058fa6ab4fea4ac63 会相继立即重复同步一次。如图2
打印出从缓存中取回租户ID列表,已经成功同步了 3 个租户:c10e87f39873512a16727e17f57456a5、015ce30b116ce86058fa6ab4fea4ac63、4fd58ceba1fbc537b5402302702131eb,但是,仅有租户 4fd58ceba1fbc537b5402302702131eb 的同步时间得到了更新。虽然在实际的生产环境中,不存在 sleep(8),此为在本地环境模拟并发请求,而添加的。但是,理论上来说,从缓存中取回租户ID列表,到 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留 的间隔时间段(假设租户数量为 10000 个,则遍历 10000 次,时间间隔为 30000 微秒,即 0.03 秒)内,如果存在 多个容器 在运行的话,便会出现相互覆盖的情况。暂时不做处理,即时出现了相互覆盖的情况,其后果是可以接受的。租户:c10e87f39873512a16727e17f57456a5、015ce30b116ce86058fa6ab4fea4ac63 会相继立即重复同步一次。

图2



Array
(
    [0] => Array
        (
            [group_id] => c10e87f39873512a16727e17f57456a5
            [cmc_console_user_last_synced_at] => 0
        )

    [1] => Array
        (
            [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
            [cmc_console_user_last_synced_at] => 0
        )

    [2] => Array
        (
            [group_id] => 4fd58ceba1fbc537b5402302702131eb
            [cmc_console_user_last_synced_at] => 1582784318
        )

    [3] => Array
        (
            [group_id] => f53df1a8d46108afc8ae9eeb3f0e1f0e
            [cmc_console_user_last_synced_at] => 0
        )

)


10、当一个租户下的用户同步时间长度超过 Redis锁定超时时间 的值 10秒后(sleep(60),实际长度超过了 60 秒),此时,此租户已经被自动解除锁定,进而导致部署为集群时,无法保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。在本地环境中,模拟了 3 个容器,3 个容器开始同步时间:1582773882、1582773895、1582773917,分别间隔 13 秒、22秒,皆大于 10 秒。第 3 个容器开始同步时,第 1 个容器仍然在同步中,并未结束(释放锁定)。因此,租户:c10e87f39873512a16727e17f57456a5 被 3 个容器同时同步。如图3
当一个租户下的用户同步时间长度超过 Redis锁定超时时间 的值 10秒后(sleep(60),实际长度超过了 60 秒),此时,此租户已经被自动解除锁定,进而导致部署为集群时,无法保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。在本地环境中,模拟了 3 个容器,3 个容器开始同步时间:1582773882、1582773895、1582773917,分别间隔 13 秒、22秒,皆大于 10 秒。第 3 个容器开始同步时,第 1 个容器仍然在同步中,并未结束(释放锁定)。因此,租户:c10e87f39873512a16727e17f57456a5 被 3 个容器同时同步。

图3




            // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
            sleep(60);
            $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);

            // 释放锁定
            $redisLock->unlock($redisLockKeyName);


11、编辑 /common/logics/redis/Lock.php。public function lock($lockKeyName),添加一个参数,支持自定义Redis锁定超时时间。以基于场景灵活设置。


    /**
     * Redis模型的锁定实现
     * @param string $lockKeyName 锁定键名
     * 格式如下:
     *
     * 'game_category' //锁定键名,如比赛分类
     *
     * @param int $timeOut Redis锁定超时时间,单位为秒
     * 格式如下:3
     *
     * @return bool 成功返回真/失败返回假
     * 格式如下:
     *
     * true //状态,获取锁定成功,可继续执行
     * 
     * 或者
     * 
     * false //状态,获取锁定失败,不可继续执行
     *
     */
    public function lock($lockKeyName, $timeOut = 3)
    {
        // 设置锁定的过期时间,获取相关锁定参数
        $time = time();
        $lockKey = Yii::$app->params['redisLock']['keyPrefix'] . $lockKeyName;
        $lockExpire = $time + $timeOut;
        // 获取 Redis 连接,以执行相关命令
        $redis = Yii::$app->redis;
        // 获取锁定
        $executeCommandResult = $redis->setnx($lockKey, $lockExpire);
        // 返回0,表示已经被其他客户端锁定
        if ($executeCommandResult == 0) {
            // 防止死锁,获取当前锁的过期时间
            $lockCurrentExpire = $redis->get($lockKey);
            // 判断锁是否过期,如果已经过期
            if ($time > $lockCurrentExpire) {
                // 防止并发锁定,检查存储在 key 的旧值是否仍然是过期的时间戳,如果是,则获取锁定,否则返回假
                $executeCommandResult = $redis->getset($lockKey, $lockExpire);
                if ($lockCurrentExpire != $executeCommandResult) {
                    return false;
                }
            }

            // 返回0,表示已经被其他客户端锁定,且不存在死锁,返回假
            if ($executeCommandResult == 0) {
                return false;
            }

        }
        
        return true;
    }


12、Redis模型的锁定实现,Redis锁定超时时间,单位为秒(自定义 600)。一个租户下的用户同步,耗费的最长时间只要不超过 10 分钟,当部署为集群时,就可以保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。


            /* Redis模型的锁定实现 */
            $lock = $redisLock->lock($redisLockKeyName, 600);


13、在开发环境,部署了 3 个容器。以便于测试集群部署时,并发锁定的情况。在 bash sleep 60 秒 的情况下,很难出现并发锁定的情况。不过,当部署为集群时,已经可以保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。当出现租户下的用户同步及时性不够理想的情况,可以通过添加容器的方案提升同步的及时性。在 3 个容器中,总计同步了 30 + 30 + 33 = 93 次。其中 租户 f53df1a8d46108afc8ae9eeb3f0e1f0e、c10e87f39873512a16727e17f57456a5、4fd58ceba1fbc537b5402302702131eb、015ce30b116ce86058fa6ab4fea4ac63 分别同步了:23、24、22、24 次。3 个容器的情况下,在 15:43 至 16:16 的时间段内(32 分钟左右),一个租户的同步次数平均为:23 次。实际测试结果,一个租户的同步时间间隔为:32 * 60 / 24 = 80 秒。理论上的计算公式,一个租户的同步时间间隔为:租户数量 / 容器数量 * 60,结果单位为秒。同步性能符合设计预期。如图4
在开发环境,部署了 3 个容器。以便于测试集群部署时,并发锁定的情况。在 bash sleep 60 秒 的情况下,很难出现并发锁定的情况。不过,当部署为集群时,已经可以保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。当出现租户下的用户同步及时性不够理想的情况,可以通过添加容器的方案提升同步的及时性。在 3 个容器中,总计同步了 30 + 30 + 33 = 93 次。其中 租户 f53df1a8d46108afc8ae9eeb3f0e1f0e、c10e87f39873512a16727e17f57456a5、4fd58ceba1fbc537b5402302702131eb、015ce30b116ce86058fa6ab4fea4ac63 分别同步了:23、24、22、24 次。3 个容器的情况下,在 15:43 至 16:16 的时间段内(32 分钟左右),一个租户的同步次数平均为:23 次。实际测试结果,一个租户的同步时间间隔为:32 * 60 / 24 = 80 秒。理论上的计算公式,一个租户的同步时间间隔为:租户数量 / 容器数量 * 60,结果单位为秒。同步性能符合设计预期。

图4



[root@1d03b809a523 logs]# ls -ltr
total 120
-rw-r--r-- 1 root root 32 Feb 28 15:43 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582875818.txt
-rw-r--r-- 1 root root 32 Feb 28 15:44 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582875879.txt
-rw-r--r-- 1 root root 32 Feb 28 15:45 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582875941.txt
-rw-r--r-- 1 root root 32 Feb 28 15:49 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876192.txt
-rw-r--r-- 1 root root 32 Feb 28 15:50 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876254.txt
-rw-r--r-- 1 root root 32 Feb 28 15:51 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876315.txt
-rw-r--r-- 1 root root 32 Feb 28 15:52 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876377.txt
-rw-r--r-- 1 root root 32 Feb 28 15:53 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876438.txt
-rw-r--r-- 1 root root 32 Feb 28 15:55 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876500.txt
-rw-r--r-- 1 root root 32 Feb 28 15:56 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876561.txt
-rw-r--r-- 1 root root 32 Feb 28 15:57 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876623.txt
-rw-r--r-- 1 root root 32 Feb 28 15:58 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876684.txt
-rw-r--r-- 1 root root 32 Feb 28 15:59 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876746.txt
-rw-r--r-- 1 root root 32 Feb 28 16:00 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876808.txt
-rw-r--r-- 1 root root 32 Feb 28 16:01 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876869.txt
-rw-r--r-- 1 root root 32 Feb 28 16:02 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876931.txt
-rw-r--r-- 1 root root 32 Feb 28 16:03 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876992.txt
-rw-r--r-- 1 root root 32 Feb 28 16:04 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877054.txt
-rw-r--r-- 1 root root 32 Feb 28 16:05 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877115.txt
-rw-r--r-- 1 root root 32 Feb 28 16:06 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877177.txt
-rw-r--r-- 1 root root 32 Feb 28 16:07 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877238.txt
-rw-r--r-- 1 root root 32 Feb 28 16:08 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877300.txt
-rw-r--r-- 1 root root 32 Feb 28 16:09 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877362.txt
-rw-r--r-- 1 root root 32 Feb 28 16:10 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877423.txt
-rw-r--r-- 1 root root 32 Feb 28 16:11 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877485.txt
-rw-r--r-- 1 root root 32 Feb 28 16:12 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877546.txt
-rw-r--r-- 1 root root 32 Feb 28 16:13 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877608.txt
-rw-r--r-- 1 root root 32 Feb 28 16:14 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877669.txt
-rw-r--r-- 1 root root 32 Feb 28 16:15 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877731.txt
-rw-r--r-- 1 root root 32 Feb 28 16:16 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877792.txt

[root@7e1ea0bc777c logs]# ls -ltr
total 120
-rw-r--r-- 1 root root 32 Feb 28 15:43 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582875803.txt
-rw-r--r-- 1 root root 32 Feb 28 15:44 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582875864.txt
-rw-r--r-- 1 root root 32 Feb 28 15:45 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582875926.txt
-rw-r--r-- 1 root root 32 Feb 28 15:49 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876189.txt
-rw-r--r-- 1 root root 32 Feb 28 15:50 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876251.txt
-rw-r--r-- 1 root root 32 Feb 28 15:51 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876313.txt
-rw-r--r-- 1 root root 32 Feb 28 15:52 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876374.txt
-rw-r--r-- 1 root root 32 Feb 28 15:53 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876436.txt
-rw-r--r-- 1 root root 32 Feb 28 15:54 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876497.txt
-rw-r--r-- 1 root root 32 Feb 28 15:55 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876559.txt
-rw-r--r-- 1 root root 32 Feb 28 15:57 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876621.txt
-rw-r--r-- 1 root root 32 Feb 28 15:58 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876682.txt
-rw-r--r-- 1 root root 32 Feb 28 15:59 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876744.txt
-rw-r--r-- 1 root root 32 Feb 28 16:00 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876805.txt
-rw-r--r-- 1 root root 32 Feb 28 16:01 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876867.txt
-rw-r--r-- 1 root root 32 Feb 28 16:02 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876929.txt
-rw-r--r-- 1 root root 32 Feb 28 16:03 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876990.txt
-rw-r--r-- 1 root root 32 Feb 28 16:04 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877052.txt
-rw-r--r-- 1 root root 32 Feb 28 16:05 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877113.txt
-rw-r--r-- 1 root root 32 Feb 28 16:06 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877175.txt
-rw-r--r-- 1 root root 32 Feb 28 16:07 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877237.txt
-rw-r--r-- 1 root root 32 Feb 28 16:08 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877298.txt
-rw-r--r-- 1 root root 32 Feb 28 16:09 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877360.txt
-rw-r--r-- 1 root root 32 Feb 28 16:10 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877422.txt
-rw-r--r-- 1 root root 32 Feb 28 16:11 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877483.txt
-rw-r--r-- 1 root root 32 Feb 28 16:12 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877545.txt
-rw-r--r-- 1 root root 32 Feb 28 16:13 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877606.txt
-rw-r--r-- 1 root root 32 Feb 28 16:14 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877668.txt
-rw-r--r-- 1 root root 32 Feb 28 16:15 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877730.txt
-rw-r--r-- 1 root root 32 Feb 28 16:16 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877791.txt

[root@16fa59fcd4e0 logs]# ls -ltr
total 144
-rw-r--r-- 1 root root 8845 Feb 28 15:43 app.log
-rw-r--r-- 1 root root   32 Feb 28 15:44 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582875846.txt
-rw-r--r-- 1 root root   32 Feb 28 15:45 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582875908.txt
-rw-r--r-- 1 root root   32 Feb 28 15:46 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582875969.txt
-rw-r--r-- 1 root root   32 Feb 28 15:47 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876031.txt
-rw-r--r-- 1 root root   32 Feb 28 15:48 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876093.txt
-rw-r--r-- 1 root root   32 Feb 28 15:49 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876155.txt
-rw-r--r-- 1 root root   32 Feb 28 15:50 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876218.txt
-rw-r--r-- 1 root root   32 Feb 28 15:51 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876279.txt
-rw-r--r-- 1 root root   32 Feb 28 15:52 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876341.txt
-rw-r--r-- 1 root root   32 Feb 28 15:53 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876403.txt
-rw-r--r-- 1 root root   32 Feb 28 15:54 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876465.txt
-rw-r--r-- 1 root root   32 Feb 28 15:55 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876526.txt
-rw-r--r-- 1 root root   32 Feb 28 15:56 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876588.txt
-rw-r--r-- 1 root root   32 Feb 28 15:57 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876650.txt
-rw-r--r-- 1 root root   32 Feb 28 15:58 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876712.txt
-rw-r--r-- 1 root root   32 Feb 28 15:59 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582876773.txt
-rw-r--r-- 1 root root   32 Feb 28 16:00 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582876835.txt
-rw-r--r-- 1 root root   32 Feb 28 16:01 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582876897.txt
-rw-r--r-- 1 root root   32 Feb 28 16:02 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582876959.txt
-rw-r--r-- 1 root root   32 Feb 28 16:03 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877020.txt
-rw-r--r-- 1 root root   32 Feb 28 16:04 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877082.txt
-rw-r--r-- 1 root root   32 Feb 28 16:05 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877144.txt
-rw-r--r-- 1 root root   32 Feb 28 16:06 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877206.txt
-rw-r--r-- 1 root root   32 Feb 28 16:07 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877268.txt
-rw-r--r-- 1 root root   32 Feb 28 16:08 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877330.txt
-rw-r--r-- 1 root root   32 Feb 28 16:09 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877392.txt
-rw-r--r-- 1 root root   32 Feb 28 16:10 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877454.txt
-rw-r--r-- 1 root root   32 Feb 28 16:11 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877515.txt
-rw-r--r-- 1 root root   32 Feb 28 16:12 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877577.txt
-rw-r--r-- 1 root root   32 Feb 28 16:13 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1582877639.txt
-rw-r--r-- 1 root root   32 Feb 28 16:15 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1582877701.txt
-rw-r--r-- 1 root root   32 Feb 28 16:16 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1582877763.txt
-rw-r--r-- 1 root root   32 Feb 28 16:17 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1582877825.txt


14、在 bash sleep 60 秒 的情况下,很难出现并发锁定的情况。或者增加更多的容器,或者提升执行命令行的频率。设置 bash sleep 10 秒。已经出现并发锁定(防止同一个租户下的用户同步同时在多个容器中执行)的情况。当部署为集群时,已经可以保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。理论上的计算公式,一个租户的同步时间间隔为:4 / 3 * 10 = 13,结果单位为秒。符合设计预期。总结:部署的容器数量不要超过租户的数量,以防止并发锁定的情况过于频繁。如图5
在 bash sleep 60 秒 的情况下,很难出现并发锁定的情况。或者增加更多的容器,或者提升执行命令行的频率。设置 bash sleep 10 秒。已经出现并发锁定(防止同一个租户下的用户同步同时在多个容器中执行)的情况。当部署为集群时,已经可以保证同一个租户下的用户同步,在某一时间段,仅在 1 个容器中执行。理论上的计算公式,一个租户的同步时间间隔为:4 / 3 * 10 = 13,结果单位为秒。符合设计预期。总结:部署的容器数量不要超过租户的数量,以防止并发锁定的情况过于频繁。

图5



[root@0f5a081a481a logs]# ls -ltr
total 320
-rw-r--r-- 1 root root 8845 Mar  2 09:50 app.log
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113813.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113825.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113836.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113848.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113860.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113871.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113883.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113894.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113906.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113917.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113929.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113940.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113952.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113964.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113975.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113987.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113998.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114010.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114021.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114033.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114044.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114056.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114067.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114079.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114091.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114102.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114114.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114125.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114137.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114148.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114160.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114171.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-is-lock-exist-c10e87f39873512a16727e17f57456a5-1583114171.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114171.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114183.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-is-lock-exist-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114183.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114183.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114194.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114206.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114218.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114228.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114240.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114252.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114263.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114275.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114286.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114298.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114309.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114321.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114332.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114344.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114355.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-is-lock-exist-4fd58ceba1fbc537b5402302702131eb-1583114355.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114355.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114367.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-is-lock-exist-c10e87f39873512a16727e17f57456a5-1583114367.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114367.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114379.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114390.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114402.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114413.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114425.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114436.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114448.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114460.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114471.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114483.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114494.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114506.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114517.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114529.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114540.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114552.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114563.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114575.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114587.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114598.txt

[root@88f433b87315 logs]# ls -ltr
total 344
-rw-r--r-- 1 root root 9092 Mar  2 09:49 app.log
-rw-r--r-- 1 root root   32 Mar  2 09:49 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113764.txt
-rw-r--r-- 1 root root   32 Mar  2 09:49 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113776.txt
-rw-r--r-- 1 root root   32 Mar  2 09:49 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113788.txt
-rw-r--r-- 1 root root   32 Mar  2 09:49 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113799.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113811.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113823.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113834.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113846.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113857.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113869.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113881.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113892.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113904.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113915.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113927.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113939.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113950.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113962.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113973.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113985.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113997.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114008.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114020.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114032.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114043.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114055.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114066.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114078.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114090.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114101.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114113.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114125.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114136.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114148.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114159.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114171.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114183.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114195.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-is-lock-exist-4fd58ceba1fbc537b5402302702131eb-1583114195.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114195.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114206.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-is-lock-exist-015ce30b116ce86058fa6ab4fea4ac63-1583114206.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114206.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114218.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114229.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114241.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114253.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114264.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114276.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114288.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114299.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114311.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114322.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114334.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114346.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114357.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114369.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114380.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114392.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114404.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114415.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114427.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114439.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114450.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114462.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114474.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-is-lock-exist-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114474.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114474.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114485.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-is-lock-exist-015ce30b116ce86058fa6ab4fea4ac63-1583114485.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114485.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114497.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-is-lock-exist-4fd58ceba1fbc537b5402302702131eb-1583114497.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114497.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114509.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114520.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114532.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114543.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114555.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114567.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114578.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114590.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114601.txt

[root@c2e084f1424c logs]# ls -ltr
total 308
-rw-r--r-- 1 root root 8845 Mar  2 09:49 app.log
-rw-r--r-- 1 root root   32 Mar  2 09:49 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113778.txt
-rw-r--r-- 1 root root   32 Mar  2 09:49 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113790.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113802.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113814.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113825.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113837.txt
-rw-r--r-- 1 root root   32 Mar  2 09:50 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113849.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113861.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113873.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113884.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113896.txt
-rw-r--r-- 1 root root   32 Mar  2 09:51 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113908.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113920.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113932.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113943.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583113955.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583113967.txt
-rw-r--r-- 1 root root   32 Mar  2 09:52 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583113979.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583113990.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114002.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114014.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114026.txt
-rw-r--r-- 1 root root   32 Mar  2 09:53 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114037.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114049.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114061.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114073.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114085.txt
-rw-r--r-- 1 root root   32 Mar  2 09:54 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114096.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114108.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114120.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114132.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114143.txt
-rw-r--r-- 1 root root   32 Mar  2 09:55 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114155.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114167.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114179.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114191.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114202.txt
-rw-r--r-- 1 root root   32 Mar  2 09:56 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114214.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114226.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114238.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114249.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114261.txt
-rw-r--r-- 1 root root   32 Mar  2 09:57 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114273.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114285.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114296.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114308.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114320.txt
-rw-r--r-- 1 root root   32 Mar  2 09:58 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114332.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114343.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114355.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114367.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114379.txt
-rw-r--r-- 1 root root   32 Mar  2 09:59 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114391.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114403.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114414.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114426.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114438.txt
-rw-r--r-- 1 root root   32 Mar  2 10:00 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114450.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114461.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114473.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114485.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114497.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114509.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-is-lock-exist-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114509.txt
-rw-r--r-- 1 root root   32 Mar  2 10:01 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114509.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114520.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114532.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114544.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114556.txt
-rw-r--r-- 1 root root   32 Mar  2 10:02 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114568.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-c10e87f39873512a16727e17f57456a5-1583114580.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-f53df1a8d46108afc8ae9eeb3f0e1f0e-1583114592.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-4fd58ceba1fbc537b5402302702131eb-1583114603.txt
-rw-r--r-- 1 root root   32 Mar  2 10:03 cmc-console-user-sync-015ce30b116ce86058fa6ab4fea4ac63-1583114615.txt


15、总结: (1) 理论上的计算公式,一个租户的同步时间间隔为:租户数量 / 容器数量 * 60,结果单位为秒。 (2) 部署的容器数量不要超过租户的数量,以防止并发锁定的情况过于频繁,且必要性不大(当容器数量等于租户数量时,同步时间间隔为:60 秒)。]]>
https://www.shuijingwanwq.com/2020/03/02/3964/feed/ 0
基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,日志消息的自动定时删除 https://www.shuijingwanwq.com/2019/10/09/3550/ https://www.shuijingwanwq.com/2019/10/09/3550/#respond Wed, 09 Oct 2019 05:59:56 +0000 http://www.shuijingwanwq.com/?p=3550 Post Views: 109

1、由于日志消息是存储于 MySQL 的 log 表中,在生产环境中,累积了大量的数据,log 表的数据长度已经达到 数百 GB ,因此,决定基于控制台命令行实现日志消息的定时删除,开发环境的 log 表的数据长度为 14.64 GB,如图1

由于日志消息是存储于 MySQL 的 log 表中,在生产环境中,累积了大量的数据,log 表的数据长度已经达到 数百 GB ,因此,决定基于控制台命令行实现日志消息的定时删除,开发环境的 log 表的数据长度为 14.64 GB

图1

2、编辑 \console\controllers\LogController.php,获取服务器当前时间,判断范围([0点,1点)),如果不在范围内,则延缓执行 10 * 60 秒,再退出

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/10/08
 * Time: 13:36
 */
 
namespace console\controllers;
 
use Yii;
use console\models\Log;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 日志
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class LogController extends Controller
{
    const SLEEP_TIME = 10 * 60; // 延缓执行 10 * 60 秒
    /**
     * 日志的删除
     *
     */
    public function actionDelete()
    {
        /* 获取服务器当前时间,判断范围([0点,1点)),如果不在范围内,则延缓执行 10 * 60 秒,再退出 */
        $time = time();
        $zeroTime = strtotime("today"); // 0点
        $oneTime = strtotime("today +1 hours"); // 1点
        echo $time . " ";
        echo date('Y-m-d H:i:s', $time) . " ";
        echo $zeroTime . " ";
        echo date('Y-m-d H:i:s', $zeroTime) . " ";
        echo $oneTime . " ";
        echo date('Y-m-d H:i:s', $oneTime) . " ";
        exit;
        if (!($time >= $zeroTime && $time < $oneTime)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        echo 1;
        exit;
 
 
    }
}

PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1570518464 2019-10-08 15:29:17 1570464000 2019-10-08 00:00:00 1570467600 2019-10-08 01:00:00


3、查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出


        /* 查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出 */
        $logItem = Log::find()->select(['log_time'])->orderBy(['log_time' => SORT_ASC])->limit(1)->one();
        if (!isset($logItem)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);

            return ExitCode::OK;
        }
        $logTime = $logItem->log_time;


4、获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出


        /* 获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出 */
        $reservedTime = strtotime("today -15 days"); // 保留时间,0点
        echo $logTime . " ";
        echo date('Y-m-d H:i:s', $logTime) . " ";
        echo $reservedTime . " ";
        echo date('Y-m-d H:i:s', $reservedTime) . " ";
        if ($logTime >= $reservedTime) {
            echo 0;
            exit;
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);

            return ExitCode::OK;
        }
        echo 1;
        exit;



PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567046168.2703 2019-08-29 10:36:08 1569168000 2019-09-23 00:00:00 1


5、获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足)


        /* 获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足) */
        $logDate = date('Y-m-d', $logTime);
        $deadlineTime = strtotime("$logDate +1 days"); // 截止时间,0点
        echo $logDate . " ";
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
        if ($deadlineTime > $reservedTime) {
            $deadlineTime = $reservedTime;
            echo 1;
            exit;
        }
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
        echo 2;
        exit;



PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
2019-08-29 2019-08-30 00:00:00 2019-08-30 00:00:00 2


6、执行删除 SQL(),条件(日志时间 小于 $deadlineTime),即命令行 1 次运行仅删除 1 天的日志消息,延缓执行 60 秒,再退出。
注:执行删除 SQL(),DELETE FROM `cpa_log` WHERE `log_time` < 1570590733,删除 26 万余条记录(1 天的日志消息),发现执行时间长达 1 分余钟,如图2

执行删除 SQL(),DELETE FROM `cpa_log` WHERE `log_time` < 1570590733,删除 26 万余条记录(1 天的日志消息),发现执行时间长达 1 分余钟

图2

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/10/08
 * Time: 13:36
 */
 
namespace console\controllers;
 
use Yii;
use console\models\Log;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 日志
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class LogController extends Controller
{
    const SLEEP_TIME = 10 * 60; // 延缓执行 10 * 60 秒
    /**
     * 日志的删除
     *
     */
    public function actionDelete()
    {
        /* 获取服务器当前时间,判断范围([0点,1点]),如果不在范围内,则延缓执行 10 * 60 秒,再退出 */
        $time = time(); // 当前时间
        $zeroTime = strtotime("today"); // 0点
        $oneTime = strtotime("today +1 hours"); // 1点
        if (($time >= $zeroTime && $time <= $oneTime)) {
            // 延缓执行 10 * 60 秒
            echo 0;
            exit;
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出 */
        $logItem = Log::find()->select(['log_time'])->orderBy(['log_time' => SORT_ASC])->limit(1)->one();
        if (!isset($logItem)) {
            // 延缓执行 10 * 60 秒
            echo 1;
            exit;
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
        $logTime = $logItem->log_time;
 
        /* 获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出 */
        $reservedTime = strtotime("today -15 days"); // 保留时间,0点
        echo $logTime . " ";
        echo date('Y-m-d H:i:s', $logTime) . " ";
        echo $reservedTime . " ";
        echo date('Y-m-d H:i:s', $reservedTime) . " ";
        if ($logTime >= $reservedTime) {
            echo 2;
            exit;
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足) */
        $logDate = date('Y-m-d', $logTime);
        $deadlineTime = strtotime("$logDate +1 days"); // 截止时间,0点
        echo $logDate . " ";
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
        if ($deadlineTime > $reservedTime) {
            $deadlineTime = $reservedTime;
        }
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
 
        /* 执行删除 SQL(),条件(日志时间 小于 $deadlineTime),即命令行 1 次运行仅删除 1 天的日志消息,延缓执行 60 秒,再退出 */
        Log::deleteAll(['<', 'log_time', $deadlineTime]);
        // 延缓执行 60 秒
        //sleep(60);
        //return ExitCode::OK;
        echo 3;
        exit;
    }
}
 

PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567046168.2703 2019-08-29 10:36:08 1569168000 2019-09-23 00:00:00 2019-08-29 2019-08-30 00:00:00 2019-08-30 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567094579.432 2019-08-30 00:02:59 1569168000 2019-09-23 00:00:00 2019-08-30 2019-08-31 00:00:00 2019-08-31 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567180800.2962 2019-08-31 00:00:00 1569168000 2019-09-23 00:00:00 2019-08-31 2019-09-01 00:00:00 2019-09-01 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567267282.2226 2019-09-01 00:01:22 1569168000 2019-09-23 00:00:00 2019-09-01 2019-09-02 00:00:00 2019-09-02 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1568958887.1824 2019-09-20 13:54:47 1569168000 2019-09-23 00:00:00 2019-09-20 2019-09-21 00:00:00 2019-09-21 00:00:00
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1569832928.1543 2019-09-30 16:42:08 1569168000 2019-09-23 00:00:00 2
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1569832928.1543 2019-09-30 16:42:08 1569168000 2019-09-23 00:00:00 2


7、设置参数,日志的定时删除的保留时间(前 15 天),单位为天,编辑 \console\config\params-local.php

<?php
return [
    'logDeleteReservedTime' => 15, // 日志的定时删除的保留时间(前 15 天),单位为天
];

8、最终的代码实现,开发环境的 log 表的数据长度为 2.36 GB,符合预期,如图3

最终的代码实现,开发环境的 log 表的数据长度为 2.36 GB,符合预期

图3

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/10/08
 * Time: 13:36
 */
 
namespace console\controllers;
 
use Yii;
use console\models\Log;
use yii\console\Controller;
use yii\console\ExitCode;
 
/**
 * 日志
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class LogController extends Controller
{
    const SLEEP_TIME = 10 * 60; // 延缓执行 10 * 60 秒
    /**
     * 日志的删除
     */
    public function actionDelete()
    {
        /* 获取服务器当前时间,判断范围([0点,1点]),如果不在范围内,则延缓执行 10 * 60 秒,再退出 */
        $time = time(); // 当前时间
        $zeroTime = strtotime("today"); // 0点
        $oneTime = strtotime("today +1 hours"); // 1点
        if (!($time >= $zeroTime && $time < $oneTime)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出 */
        $logItem = Log::find()->select(['log_time'])->orderBy(['log_time' => SORT_ASC])->limit(1)->one();
        if (!isset($logItem)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
        $logTime = $logItem->log_time;
 
        /* 获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出 */
        $logDeleteReservedTime = Yii::$app->params['logDeleteReservedTime'];
        $reservedTime = strtotime("today -$logDeleteReservedTime days"); // 保留时间,0点
        if ($logTime >= $reservedTime) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足) */
        $logDate = date('Y-m-d', $logTime);
        $deadlineTime = strtotime("$logDate +1 days"); // 截止时间,0点
        if ($deadlineTime > $reservedTime) {
            $deadlineTime = $reservedTime;
        }
 
        /* 执行删除 SQL(),条件(日志时间 小于 $deadlineTime),即命令行 1 次运行仅删除 1 天的日志消息,延缓执行 60 秒,再退出 */
        Log::deleteAll(['<', 'log_time', $deadlineTime]);
        // 延缓执行 60 秒
        sleep(60);
        return ExitCode::OK;
    }
}
 

 

]]>
https://www.shuijingwanwq.com/2019/10/09/3550/feed/ 0
基于 Yii 2.0 实现的命令行脚本,持续运行期间,占用内存过高的分析与解决 https://www.shuijingwanwq.com/2019/07/24/3376/ https://www.shuijingwanwq.com/2019/07/24/3376/#respond Wed, 24 Jul 2019 08:16:11 +0000 http://www.shuijingwanwq.com/?p=3376 Post Views: 149

1、当在 MySQL 与 Redis 中的数据量较小时,Docker 容器的 CPU:0.02%,内存:298MB,如图1、图2

当在 MySQL 与 Redis 中的数据量较小时,Docker 容器的 CPU:0.02%,内存:298MB

图1

当在 MySQL 与 Redis 中的数据量较小时,Docker 容器的 CPU:0.02%,内存:298MB

图2

2、查看 MySQL 的实例监控情况,内存占用:2328MB,如图3

查看 MySQL 的实例监控情况,内存占用:2328MB

图3

3、查看 Redis 的实例监控情况,已使用容量:33MB,如图4

查看 Redis 的实例监控情况,已使用容量:33MB

图4

4、命令行脚本 \console\controllers\CmcConsoleUserController.php 、\console\controllers\ConfigColumnUserController.php 代码如下:

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\services\CmcConsoleUserService;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
use common\logics\http\im\IMIndependentMode;
 
/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     */
    public function actionSync()
    {
        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->all();
         
        /* 判断 $redisCmcConsoleUserIndexByGroupIdItems 是否为空,如果为空,则成功退出 */
        if (empty($redisCmcConsoleUserIndexByGroupIdItems)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);
             
            return ExitCode::OK;
        }
         
        foreach ($redisCmcConsoleUserIndexByGroupIdItems as $redisCmcConsoleUserIndexByGroupIdItemKey => $redisCmcConsoleUserIndexByGroupIdItemValue) {
 
            $isLockExist = RedisCmcConsoleUser::isLockExist($redisCmcConsoleUserIndexByGroupIdItemKey);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }
 
            // HTTP请求,获取租户ID下的用户列表
            $httpGetUserListData = [
                'groupId'  => $redisCmcConsoleUserIndexByGroupIdItemKey,
                'loginId'  => '',
                'loginTid' => '',
            ];
            $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);
             
            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');
             
            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where([ 'group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey ])->indexBy('id')->all();
             
            // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
            $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
            if (!empty($redisArrayDiffItems)) {
                $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                if (RedisCmcConsoleUser::deleteAllByIds($redisCmcConsoleUserIndexByGroupIdItemKey, $redisArrayDiffIds) === false) {
                    continue;
                }
            }
            $im = new IMIndependentMode();
            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where([ 'id' => $httpCmcConsoleUserItemValue['id'] ])->one();
                 
                $attributes = [
                    'id' => $httpCmcConsoleUserItemValue['id'],
                    'group_id' => $getUserListData['group_info']['group_id'],
                    'login_name' => $httpCmcConsoleUserItemValue['login_name'],
                    'user_token' => $httpCmcConsoleUserItemValue['user_token'],
                    'user_nick' => $httpCmcConsoleUserItemValue['user_nick'],
                    'user_pic' => $httpCmcConsoleUserItemValue['user_pic'],
                    'user_mobile' => $httpCmcConsoleUserItemValue['user_mobile'] ? $httpCmcConsoleUserItemValue['user_mobile'] : '',
                    'user_email' => $httpCmcConsoleUserItemValue['user_email'] ? $httpCmcConsoleUserItemValue['user_email'] : '',
                    'user_sex' => $httpCmcConsoleUserItemValue['user_sex'],
                    'user_type' => $httpCmcConsoleUserItemValue['user_type'],
                    'user_birthday' => $httpCmcConsoleUserItemValue['user_birthday'],
                    'user_chat_id' => $httpCmcConsoleUserItemValue['user_chat_id'] ? $httpCmcConsoleUserItemValue['user_chat_id'] : '',
                    'is_open' => $httpCmcConsoleUserItemValue['is_open'],
                    'add_time' => $httpCmcConsoleUserItemValue['add_time'],
                    'update_time' => $httpCmcConsoleUserItemValue['update_time'],
                    'im_identity' => md5($getUserListData['group_info']['group_id'] . $httpCmcConsoleUserItemValue['login_name']),
                ];
                 
                if (!isset($redisCmcConsoleUserItem)) {
                     
                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();
                     
                } else if (isset($redisCmcConsoleUserItem) && $httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {
                     
                    $redisCmcConsoleUserItem->attributes = $attributes;
                    $redisCmcConsoleUserItem->save();
                     
                }
                $this->addIMAccount($im, $attributes['im_identity'], $attributes['user_nick'], $attributes['user_pic']);
            }
        }
         
        // 延缓执行 60 秒
        sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
         
        return ExitCode::OK;
         
    }
     
    private function addIMAccount(IMIndependentMode $im, $identifier, $nick, $faceUrl)
    {
        $im->generateUserSig();
        $ret = $im->getAccountProfile($identifier);
        if (isset($ret['ActionStatus']) && $ret['ActionStatus'] != 'OK') {
            $im->accountImport($identifier, $nick, $faceUrl);
        }
    }
}
 
<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/03/01
 * Time: 13:17
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\models\ConfigColumnUser;
use yii\console\Controller;
use yii\console\ExitCode;
 
/**
 * 栏目人员配置
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class ConfigColumnUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Redis)至栏目人员配置模型(MySQL)
     *
     */
    public function actionSync()
    {
        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->all();
         
        /* 判断 $redisCmcConsoleUserIndexByGroupIdItems 是否为空,如果为空,则成功退出 */
        if (empty($redisCmcConsoleUserIndexByGroupIdItems)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['configColumnUser']['isEmptyYesSleepTime']);
             
            return ExitCode::OK;
        }
         
        foreach ($redisCmcConsoleUserIndexByGroupIdItems as $redisCmcConsoleUserIndexByGroupIdItemKey => $redisCmcConsoleUserIndexByGroupIdItemValue) {
 
            $isLockExist = RedisCmcConsoleUser::isLockExist($redisCmcConsoleUserIndexByGroupIdItemKey);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }
             
            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where([ 'group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey ])->indexBy('id')->all();
 
            // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
            $configColumnUserItems = ConfigColumnUser::find()->where([ 'group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey ])->isDeletedNo()->indexBy('user_id')->all();
             
            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Redis) 中的记录
            $diffItems = array_diff_key($configColumnUserItems, $redisCmcConsoleUserItems);
            if (!empty($diffItems)) {
                foreach ($diffItems as $diffItem) {
                    /* @var $diffItem \console\models\ConfigColumnUser */
                    $diffItem->softDelete();
                }
                continue;
            }
        }
         
        // 延缓执行 60 秒
        sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);
         
        return ExitCode::OK;
         
    }
}

5、命令行脚本的运行基于 Supervisor 提供支持,/etc/supervisord.d/yii-cmc-console-user-sync.ini、/etc/supervisord.d/yii-config-column-user-sync.ini,如图5

命令行脚本的运行基于 Supervisor 提供支持,/etc/supervisord.d/yii-cmc-console-user-sync.ini、/etc/supervisord.d/yii-config-column-user-sync.ini

图5


[program:yii-cmc-console-user-sync]
command = php /sobey/www/pcs-api/yii cmc-console-user/sync
autorestart = true
startsecs = 0
stopwaitsecs = 10
stderr_logfile = /data/logs/yii-cmc-console-user-sync-stderr.log
stdout_logfile = /data/logs/yii-cmc-console-user-sync-stdout.log



[program:yii-config-column-user-sync]
command = php /sobey/www/pcs-api/yii config-column-user/sync
autorestart = true
startsecs = 0
stopwaitsecs = 10
stderr_logfile = /data/logs/yii-config-column-user-sync-stderr.log
stdout_logfile = /data/logs/yii-config-column-user-sync-stdout.log


6、在 8 个租户下批量导入用户数据,让每个租户下的导入的用户数量为 999 个,总计 8000 左右,如图6

租户名称:广东省、广州市、深圳市、南山区、罗湖区、蛇口街道、招商街道、DEFAULT租户

在 8 个租户下批量导入用户数据,让每个租户下的导入的用户数量为 999 个,总计 8000 左右

图6

7、在本地环境的 Redis 中,用户数量为 8223,在开发环境的 Redis 中,对接的同一个框架,但用户数量一直为 3000 左右,始终未达到 8223,如图7、图8

在本地环境的 Redis 中,用户数量为 8223

图7

在开发环境的 Redis 中,对接的同一个框架,但用户数量一直为 3000 左右,始终未达到 8223

图8

8、最终发现问题所在,原因在于有 2 个容器在同时运行,每个容器中皆在运行命令行:cmc-console-user/sync,数据相互冲突覆盖,停止掉另一个容器中的命令行脚本(计划后续将命令行单独部署至一个容器中,隔离开,以为后续集群部署做准备),如图9

最终发现问题所在,原因在于有 2 个容器在同时运行,每个容器中皆在运行命令行:cmc-console-user/sync,数据相互冲突覆盖,停止掉另一个容器中的命令行脚本(计划后续将命令行单独部署至一个容器中,隔离开,以为后续集群部署做准备)

图9


[root@79af01f496bb pcs-api]# supervisorctl status
cronolog                         RUNNING   pid 426, uptime 2:08:17
nginx                            RUNNING   pid 422, uptime 2:08:17
php-fpm                          RUNNING   pid 424, uptime 2:08:17
yii-cmc-console-user-sync        RUNNING   pid 20685, uptime 0:04:42
yii-config-column-user-sync      RUNNING   pid 23504, uptime 0:00:45
[root@79af01f496bb pcs-api]# supervisorctl stop yii-cmc-console-user-sync
yii-cmc-console-user-sync: stopped
[root@79af01f496bb pcs-api]# supervisorctl status
cronolog                         RUNNING   pid 426, uptime 2:08:41
nginx                            RUNNING   pid 422, uptime 2:08:41
php-fpm                          RUNNING   pid 424, uptime 2:08:41
yii-cmc-console-user-sync        STOPPED   Jul 08 01:32 PM
yii-config-column-user-sync      RUN


9、在本地环境的 Redis 中,用户数量为 8223,在开发环境的 Redis 中,用户数量为 8223,已经一致,如图10

在本地环境的 Redis 中,用户数量为 8223,在开发环境的 Redis 中,用户数量为 8223,已经一致

图10

10、在 8 个租户的栏目设置中,每个租户下添加 2 个栏目,每个栏目中添加全部用户,即每个租户下添加的数据数量为 2000 左右,总计 16000 左右,如图11

在 8 个租户的栏目设置中,每个租户下添加 2 个栏目,每个栏目中添加全部用户,即每个租户下添加的数据数量为 2000 左右,总计 16000 左右

图11

11、在 第 6 步骤执行完毕后,Docker 容器的 CPU:0.05%,内存:382MB,内存增加了 80MB 左右,每 1000 条数据的添加,内存会增加 10 MB 左右。在 第 10 步骤执行完毕后,Docker 容器的 CPU:9.9%,内存:541MB,内存增加了 160MB 左右,每 1000 条数据的添加,内存会增加 20 MB 左右。因此,优化的重点在于第 2 个命令行脚本。如图12、图13

在 第 6 步骤执行完毕后,Docker 容器的 CPU:0.05%,内存:382MB,内存增加了 80MB 左右,每 1000 条数据的添加,内存会增加 10 MB 左右。

图12

在 第 10 步骤执行完毕后,Docker 容器的 CPU:9.9%,内存:541MB,内存增加了 160MB 左右,每 1000 条数据的添加,内存会增加 20 MB 左右。因此,优化的重点在于第 2 个命令行脚本。

图13

12、编辑 \console\controllers\CmcConsoleUserController.php、\console\controllers\ConfigColumnUserController.php,查看实际使用的内存量、系统分配总的内存量


        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

        return ExitCode::OK;



        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

        return ExitCode::OK;


13、运行第 1 个命令行脚本,日志如下:


8.9912643432617MB



40.378349304199MB


14、运行第 2 个命令行脚本,日志如下:总结:与第 1 个命令行脚本的主要差异在于实际使用的内存量上,相差 17 MB 左右。


26.464874267578MB



40.366722106934MB


15、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,基于 unset() 实现,实际使用的内存量减少 2 MB 左右,系统分配总的内存量无变化

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\services\CmcConsoleUserService;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
use common\logics\http\im\IMIndependentMode;
 
/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     */
    public function actionSync()
    {
        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->all();
 
        /* 判断 $redisCmcConsoleUserIndexByGroupIdItems 是否为空,如果为空,则成功退出 */
        if (empty($redisCmcConsoleUserIndexByGroupIdItems)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);
 
            return ExitCode::OK;
        }
 
        foreach ($redisCmcConsoleUserIndexByGroupIdItems as $redisCmcConsoleUserIndexByGroupIdItemKey => $redisCmcConsoleUserIndexByGroupIdItemValue) {
 
            $isLockExist = RedisCmcConsoleUser::isLockExist($redisCmcConsoleUserIndexByGroupIdItemKey);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }
 
            // HTTP请求,获取租户ID下的用户列表
            $httpGetUserListData = [
                'groupId' => $redisCmcConsoleUserIndexByGroupIdItemKey,
                'loginId' => '',
                'loginTid' => '',
            ];
            $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);
 
            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');
 
            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey])->indexBy('id')->all();
 
            // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
            $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
            // 销毁变量
            unset($redisCmcConsoleUserItems);
            if (!empty($redisArrayDiffItems)) {
                $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                // 销毁变量
                unset($redisArrayDiffItems);
                if (RedisCmcConsoleUser::deleteAllByIds($redisCmcConsoleUserIndexByGroupIdItemKey, $redisArrayDiffIds) === false) {
                    continue;
                }
            }
            $im = new IMIndependentMode();
            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();
 
                $attributes = [
                    'id' => $httpCmcConsoleUserItemValue['id'],
                    'group_id' => $getUserListData['group_info']['group_id'],
                    'login_name' => $httpCmcConsoleUserItemValue['login_name'],
                    'user_token' => $httpCmcConsoleUserItemValue['user_token'],
                    'user_nick' => $httpCmcConsoleUserItemValue['user_nick'],
                    'user_pic' => $httpCmcConsoleUserItemValue['user_pic'],
                    'user_mobile' => $httpCmcConsoleUserItemValue['user_mobile'] ? $httpCmcConsoleUserItemValue['user_mobile'] : '',
                    'user_email' => $httpCmcConsoleUserItemValue['user_email'] ? $httpCmcConsoleUserItemValue['user_email'] : '',
                    'user_sex' => $httpCmcConsoleUserItemValue['user_sex'],
                    'user_type' => $httpCmcConsoleUserItemValue['user_type'],
                    'user_birthday' => $httpCmcConsoleUserItemValue['user_birthday'],
                    'user_chat_id' => $httpCmcConsoleUserItemValue['user_chat_id'] ? $httpCmcConsoleUserItemValue['user_chat_id'] : '',
                    'is_open' => $httpCmcConsoleUserItemValue['is_open'],
                    'add_time' => $httpCmcConsoleUserItemValue['add_time'],
                    'update_time' => $httpCmcConsoleUserItemValue['update_time'],
                    'im_identity' => md5($getUserListData['group_info']['group_id'] . $httpCmcConsoleUserItemValue['login_name']),
                ];
 
                if (!isset($redisCmcConsoleUserItem)) {
 
                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();
 
                } else {
                    if (isset($redisCmcConsoleUserItem) && $httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {
 
                        $redisCmcConsoleUserItem->attributes = $attributes;
                        $redisCmcConsoleUserItem->save();
 
                    }
                }
                $this->addIMAccount($im, $attributes['im_identity'], $attributes['user_nick'], $attributes['user_pic']);
            }
        }
 
        // 延缓执行 60 秒
        sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
 
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
        return ExitCode::OK;
 
    }
 
    private function addIMAccount(IMIndependentMode $im, $identifier, $nick, $faceUrl)
    {
        $im->generateUserSig();
        $ret = $im->getAccountProfile($identifier);
        if (isset($ret['ActionStatus']) && $ret['ActionStatus'] != 'OK') {
            $im->accountImport($identifier, $nick, $faceUrl);
        }
    }
}
 

8.9912643432617MB //优化前
6.6167755126953MB //优化后



40.378349304199MB //优化前
40.378349304199MB //优化后


16、分析优化第 2 个命令行脚本,\console\controllers\ConfigColumnUserController.php,基于 unset() 实现,实际使用的内存量减少 9 MB 左右,系统分配总的内存量无变化

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/03/01
 * Time: 13:17
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\models\ConfigColumnUser;
use yii\console\Controller;
use yii\console\ExitCode;
 
/**
 * 栏目人员配置
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class ConfigColumnUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Redis)至栏目人员配置模型(MySQL)
     *
     */
    public function actionSync()
    {
        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->all();
 
        /* 判断 $redisCmcConsoleUserIndexByGroupIdItems 是否为空,如果为空,则成功退出 */
        if (empty($redisCmcConsoleUserIndexByGroupIdItems)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['configColumnUser']['isEmptyYesSleepTime']);
 
            return ExitCode::OK;
        }
 
        foreach ($redisCmcConsoleUserIndexByGroupIdItems as $redisCmcConsoleUserIndexByGroupIdItemKey => $redisCmcConsoleUserIndexByGroupIdItemValue) {
 
            $isLockExist = RedisCmcConsoleUser::isLockExist($redisCmcConsoleUserIndexByGroupIdItemKey);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }
 
            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey])->indexBy('id')->all();
 
            // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
            $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey])->isDeletedNo()->indexBy('user_id')->all();
 
            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Redis) 中的记录
            $diffItems = array_diff_key($configColumnUserItems, $redisCmcConsoleUserItems);
            // 销毁变量
            unset($redisCmcConsoleUserItems, $configColumnUserItems);
            if (!empty($diffItems)) {
                foreach ($diffItems as $diffItem) {
                    /* @var $diffItem ConfigColumnUser */
                    $diffItem->softDelete();
                }
                continue;
            }
        }
 
        // 延缓执行 60 秒
        sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);
 
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
        return ExitCode::OK;
 
    }
}
 

26.464874267578MB //优化前
17.696464538574MB //优化后



40.366722106934MB //优化前
40.366722106934MB //优化后


17、升级至开发环境,Docker 容器的 CPU:5.4%,内存:371MB,内存减少了 (541MB – 371MB) = 170MB 左右,证明基于 unset() 实现的方案是可行的。如图14

升级至开发环境,Docker 容器的 CPU:5.4%,内存:371MB,内存减少了 (541MB - 371MB) = 170MB 左右,证明基于 unset() 实现的方案是可行的。

图14

18、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,以减少执行时间为目标,从 30 分钟左右减少至 1 分钟左右。删除掉:addIMAccount() 相关的实现,其在遍历过程中,执行 HTTP 请求,消耗大量的时间。实际使用的内存量减少 0.09 MB 左右,系统分配总的内存量减少 0.002 MB 左右

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\services\CmcConsoleUserService;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     */
    public function actionSync()
    {
        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->all();
 
        /* 判断 $redisCmcConsoleUserIndexByGroupIdItems 是否为空,如果为空,则成功退出 */
        if (empty($redisCmcConsoleUserIndexByGroupIdItems)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);
 
            return ExitCode::OK;
        }
 
        foreach ($redisCmcConsoleUserIndexByGroupIdItems as $redisCmcConsoleUserIndexByGroupIdItemKey => $redisCmcConsoleUserIndexByGroupIdItemValue) {
 
            $isLockExist = RedisCmcConsoleUser::isLockExist($redisCmcConsoleUserIndexByGroupIdItemKey);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }
 
            // HTTP请求,获取租户ID下的用户列表
            $httpGetUserListData = [
                'groupId' => $redisCmcConsoleUserIndexByGroupIdItemKey,
                'loginId' => '',
                'loginTid' => '',
            ];
            $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);
 
            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');
 
            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey])->indexBy('id')->all();
 
            // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
            $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
            // 销毁变量
            unset($redisCmcConsoleUserItems);
            if (!empty($redisArrayDiffItems)) {
                $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                // 销毁变量
                unset($redisArrayDiffItems);
                if (RedisCmcConsoleUser::deleteAllByIds($redisCmcConsoleUserIndexByGroupIdItemKey, $redisArrayDiffIds) === false) {
                    continue;
                }
            }
 
            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();
 
                $attributes = [
                    'id' => $httpCmcConsoleUserItemValue['id'],
                    'group_id' => $getUserListData['group_info']['group_id'],
                    'login_name' => $httpCmcConsoleUserItemValue['login_name'],
                    'user_token' => $httpCmcConsoleUserItemValue['user_token'],
                    'user_nick' => $httpCmcConsoleUserItemValue['user_nick'],
                    'user_pic' => $httpCmcConsoleUserItemValue['user_pic'],
                    'user_mobile' => $httpCmcConsoleUserItemValue['user_mobile'] ? $httpCmcConsoleUserItemValue['user_mobile'] : '',
                    'user_email' => $httpCmcConsoleUserItemValue['user_email'] ? $httpCmcConsoleUserItemValue['user_email'] : '',
                    'user_sex' => $httpCmcConsoleUserItemValue['user_sex'],
                    'user_type' => $httpCmcConsoleUserItemValue['user_type'],
                    'user_birthday' => $httpCmcConsoleUserItemValue['user_birthday'],
                    'user_chat_id' => $httpCmcConsoleUserItemValue['user_chat_id'] ? $httpCmcConsoleUserItemValue['user_chat_id'] : '',
                    'is_open' => $httpCmcConsoleUserItemValue['is_open'],
                    'add_time' => $httpCmcConsoleUserItemValue['add_time'],
                    'update_time' => $httpCmcConsoleUserItemValue['update_time'],
                    'im_identity' => md5($getUserListData['group_info']['group_id'] . $httpCmcConsoleUserItemValue['login_name']),
                ];
 
                if (!isset($redisCmcConsoleUserItem)) {
 
                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();
 
                } else {
                    if (isset($redisCmcConsoleUserItem) && $httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {
 
                        $redisCmcConsoleUserItem->attributes = $attributes;
                        $redisCmcConsoleUserItem->save();
 
                    }
                }
 
            }
        }
 
        // 延缓执行 60 秒
        sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
 
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
        return ExitCode::OK;
 
    }
}
 

6.6167755126953MB //优化前
6.5267791748047MB //优化后



40.378349304199MB //优化前
40.37670135498MB //优化后


19、升级至开发环境,Docker 容器的 CPU:5.4%,内存:280MB,内存减少了 (371MB – 371MB) = 0MB 左右,证明减少命令行的执行时间,对于内存的使用优化无意义。虽然在本地开发环境中,实际使用的内存量减少 0.09 MB 左右,系统分配总的内存量减少 0.002 MB 左右,减少的内存量很小。如图15

升级至开发环境,Docker 容器的 CPU:5.4%,内存:280MB,内存减少了 (371MB - 371MB) = 0MB 左右,证明减少命令行的执行时间,对于内存的使用优化无意义。虽然在本地开发环境中,实际使用的内存量减少 0.09 MB 左右,系统分配总的内存量减少 0.002 MB 左右,减少的内存量很小。

图15

20、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,以数组形式获取数据,在查询方法前调用 asArray() 方法,来获取 PHP 数组形式的结果。实际使用的内存量减少 0.09 MB 左右,系统分配总的内存量减少 8.96 MB 左右


        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->asArray()->all();

            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey])->indexBy('id')->asArray()->all();



6.5267791748047MB //优化前
6.4357604980469MB //优化后



40.37670135498MB //优化前
31.42064666748MB //优化后


21、分析优化第 2 个命令行脚本,\console\controllers\ConfigColumnUserController.php,以数组形式获取数据,在查询方法前调用 asArray() 方法,来获取 PHP 数组形式的结果。实际使用的内存量减少 12.72 MB 左右,系统分配总的内存量减少 8.95 MB 左右

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/03/01
 * Time: 13:17
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\models\ConfigColumnUser;
use yii\console\Controller;
use yii\console\ExitCode;
 
/**
 * 栏目人员配置
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class ConfigColumnUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Redis)至栏目人员配置模型(MySQL)
     *
     */
    public function actionSync()
    {
        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->asArray()->all();
 
        /* 判断 $redisCmcConsoleUserIndexByGroupIdItems 是否为空,如果为空,则成功退出 */
        if (empty($redisCmcConsoleUserIndexByGroupIdItems)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['configColumnUser']['isEmptyYesSleepTime']);
 
            return ExitCode::OK;
        }
 
        foreach ($redisCmcConsoleUserIndexByGroupIdItems as $redisCmcConsoleUserIndexByGroupIdItemKey => $redisCmcConsoleUserIndexByGroupIdItemValue) {
 
            $isLockExist = RedisCmcConsoleUser::isLockExist($redisCmcConsoleUserIndexByGroupIdItemKey);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }
 
            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey])->indexBy('id')->asArray()->all();
 
            // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
            $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey])->isDeletedNo()->indexBy('user_id')->asArray()->all();
 
            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Redis) 中的记录
            $diffItems = array_diff_key($configColumnUserItems, $redisCmcConsoleUserItems);
 
            // 销毁变量
            unset($redisCmcConsoleUserItems, $configColumnUserItems);
            if (!empty($diffItems)) {
                // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                    'and',
                    ['group_id' => $redisCmcConsoleUserIndexByGroupIdItemKey],
                    ['in', 'user_id', $diffItems],
                ])->isDeletedNo()->all();
                foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                    /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                    $toBeDeletedConfigColumnUserItem->softDelete();
                }
                continue;
            }
        }
 
        // 延缓执行 60 秒
        sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);
 
        // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
        return ExitCode::OK;
 
    }
}
 

17.696464538574MB //优化前
4.9719772338867MB //优化后



40.366722106934MB //优化前
31.416854858398MB //优化后


22、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,变量 $attributes 在每一次遍历中皆有定义,实际上在大部分遍历中皆未使用,因此,仅在有使用的情况下才定义。实际使用的内存量减少 0.0007 MB 左右,系统分配总的内存量无变化


            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();

                if (!isset($redisCmcConsoleUserItem)) {

                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $attributes = $this->getAttributes($getUserListData['group_info']['group_id'], $httpCmcConsoleUserItemValue);
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();

                } else {
                    if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {

                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'], $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUserItem->attributes = $attributes;
                        $redisCmcConsoleUserItem->save();

                    }
                }

            }

    /**
     * 获取属性列表
     * @param string $groupId 租户ID
     * @param array $httpCmcConsoleUser 框架服务控制台的用户模型(Http)
     *
     * @return array
     */
    private function getAttributes($groupId, $httpCmcConsoleUser) {
        return [
            'id' => $httpCmcConsoleUser['id'],
            'group_id' => $groupId,
            'login_name' => $httpCmcConsoleUser['login_name'],
            'user_token' => $httpCmcConsoleUser['user_token'],
            'user_nick' => $httpCmcConsoleUser['user_nick'],
            'user_pic' => $httpCmcConsoleUser['user_pic'],
            'user_mobile' => $httpCmcConsoleUser['user_mobile'] ? $httpCmcConsoleUser['user_mobile'] : '',
            'user_email' => $httpCmcConsoleUser['user_email'] ? $httpCmcConsoleUser['user_email'] : '',
            'user_sex' => $httpCmcConsoleUser['user_sex'],
            'user_type' => $httpCmcConsoleUser['user_type'],
            'user_birthday' => $httpCmcConsoleUser['user_birthday'],
            'user_chat_id' => $httpCmcConsoleUser['user_chat_id'] ? $httpCmcConsoleUser['user_chat_id'] : '',
            'is_open' => $httpCmcConsoleUser['is_open'],
            'add_time' => $httpCmcConsoleUser['add_time'],
            'update_time' => $httpCmcConsoleUser['update_time'],
            'im_identity' => md5($groupId . $httpCmcConsoleUser['login_name']),
        ];
    }



6.4357604980469MB //优化前
6.4350357055664MB //优化后



31.42064666748MB //优化前
31.42064666748MB //优化后


23、升级至开发环境,Docker 容器的 CPU:0.04%,内存:283MB,内存减少了 (371MB – 283MB) = 90MB 左右,证明以数组形式获取数据,在查询方法前调用 asArray() 方法,来获取 PHP 数组形式的结果。优化的方案是可行的。如图16

升级至开发环境,Docker 容器的 CPU:0.04%,内存:283MB,内存减少了 (371MB - 283MB) = 90MB 左右,证明以数组形式获取数据,在查询方法前调用 asArray() 方法,来获取 PHP 数组形式的结果。优化的方案是可行的。

图16

24、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,基于 unset() 实现,实际使用的内存量减少 0.067 MB 左右,系统分配总的内存量无变化


            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');

            // 销毁变量
            unset($getUserListData['list']);



6.4350357055664MB //优化前
6.3685455322266MB //优化后



31.42064666748MB //优化前
31.42064666748MB //优化后


25、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,得出结论,内存占用过大的根本原因在于 RedisCmcConsoleUser::find()。其在 8000 左右的 Redis AR 模型记录中查找出 8 条记录。


        // 查询框架服务控制台的用户模型(Redis)的租户ID(以租户 ID 索引结果集)
        $redisCmcConsoleUserIndexByGroupIdItems = RedisCmcConsoleUser::find()->indexBy('group_id')->asArray()->all();

        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');



3.6807098388672MB //RedisCmcConsoleUser::find() 执行后
6.3685455322266MB //命令行执行后



31.42064666748MB //RedisCmcConsoleUser::find() 执行后
31.42064666748MB //命令行执行后


26、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,删除 RedisCmcConsoleUser::find(),以另外一种形式来获取租户ID列表。


        // HTTP 请求,获取开通有效服务的租户ID列表
        $cmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();

        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

        exit;



3.6807098388672MB //RedisCmcConsoleUser::find() 执行后
3.1283721923828MB //CmcApiGroupService::httpGetGroupIds() 执行后



31.42064666748MB //RedisCmcConsoleUser::find() 执行后
3.1799774169922MB //CmcApiGroupService::httpGetGroupIds() 执行后


27、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,删除 RedisCmcConsoleUser::find(),以另外一种形式来获取租户ID列表,实际使用的内存量减少 1.72 MB 左右,系统分配总的内存量减少 21.17 MB 左右


    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     */
    public function actionSync()
    {
        // HTTP 请求,获取开通有效服务的租户ID列表
        $cmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();

        /* 判断 $cmcApiGroupIds 是否为空,如果为空,则成功退出 */
        if (empty($cmcApiGroupIds)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);

            return ExitCode::OK;
        }

        foreach ($cmcApiGroupIds as $cmcApiGroupId) {

            $isLockExist = RedisCmcConsoleUser::isLockExist($cmcApiGroupId);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }

            // HTTP请求,获取租户ID下的用户列表
            $httpGetUserListData = [
                'groupId' => $cmcApiGroupId,
                'loginId' => '',
                'loginTid' => '',
            ];
            $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);

            // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
            $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');

            // 销毁变量
            unset($getUserListData['list']);

            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $cmcApiGroupId])->indexBy('id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
            $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
            // 销毁变量
            unset($redisCmcConsoleUserItems);
            if (!empty($redisArrayDiffItems)) {
                $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                // 销毁变量
                unset($redisArrayDiffItems);
                if (RedisCmcConsoleUser::deleteAllByIds($cmcApiGroupId, $redisArrayDiffIds) === false) {
                    continue;
                }
            }

            // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
            foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();

                if (!isset($redisCmcConsoleUserItem)) {

                    $redisCmcConsoleUser = new RedisCmcConsoleUser();
                    $attributes = $this->getAttributes($getUserListData['group_info']['group_id'], $httpCmcConsoleUserItemValue);
                    $redisCmcConsoleUser->attributes = $attributes;
                    $redisCmcConsoleUser->insert();

                } else {
                    if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {

                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'], $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUserItem->attributes = $attributes;
                        $redisCmcConsoleUserItem->save();

                    }
                }

            }
        }

        // 延缓执行 60 秒
        sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);

        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

        return ExitCode::OK;

    }



6.3685455322266MB //优化前
4.6499099731445MB //优化后



31.42064666748MB //优化前
10.250221252441MB //优化后


28、分析优化第 2 个命令行脚本,\console\controllers\ConfigColumnUserController.php,删除 RedisCmcConsoleUser::find(),以另外一种形式来获取租户ID列表,实际使用的内存量增加 0.37 MB 左右,系统分配总的内存量减少 19.39 MB 左右


    /**
     * 同步框架服务控制台的用户模型(Redis)至栏目人员配置模型(MySQL)
     *
     */
    public function actionSync()
    {
        // HTTP 请求,获取开通有效服务的租户ID列表
        $cmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();

        /* 判断 $cmcApiGroupIds 是否为空,如果为空,则成功退出 */
        if (empty($cmcApiGroupIds)) {
            // 延缓执行 60 * 60 秒
            sleep(Yii::$app->params['configColumnUser']['isEmptyYesSleepTime']);

            return ExitCode::OK;
        }

        foreach ($cmcApiGroupIds as $cmcApiGroupId) {

            $isLockExist = RedisCmcConsoleUser::isLockExist($cmcApiGroupId);
            // 返回 true,表示锁定存在,即已经被其他客户端锁定
            if ($isLockExist === true) {
                continue;
            }

            // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
            $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $cmcApiGroupId])->indexBy('id')->asArray()->all();

            // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
            $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $cmcApiGroupId])->isDeletedNo()->indexBy('user_id')->asArray()->all();

            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Redis) 中的记录
            $diffItems = array_diff_key($configColumnUserItems, $redisCmcConsoleUserItems);

            // 销毁变量
            unset($redisCmcConsoleUserItems, $configColumnUserItems);
            if (!empty($diffItems)) {
                // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                    'and',
                    ['group_id' => $cmcApiGroupId],
                    ['in', 'user_id', $diffItems],
                ])->isDeletedNo()->all();
                foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                    /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                    $toBeDeletedConfigColumnUserItem->softDelete();
                }
                continue;
            }
        }

        // 延缓执行 60 秒
        sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);

        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
        file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');

        return ExitCode::OK;

    }



4.9719772338867MB //优化前
5.3451461791992MB //优化后



31.416854858398MB //优化前
12.024124145508MB //优化后


29、升级至开发环境,Docker 容器的 CPU:92%,内存:435MB,CPU 增加了 (92% – 0.04%) = 91.96% 左右,内存增加了 (435MB – 283MB) = 152MB 左右。如图17、图18

升级至开发环境,Docker 容器的 CPU:92%,内存:435MB,CPU 增加了 (92% - 0.04%) = 91.96% 左右

图17

 

升级至开发环境,Docker 容器的 CPU:92%,内存:435MB,内存增加了 (435MB - 283MB) = 152MB 左右

图18

30、分析具体原因,基于 Yii 2 的 HTTP 客户端扩展在 Linux 中必须添加:setData([]),否则响应 400,如图19、图20

响应 400

图19

 

分析具体原因,基于 Yii 2 的 HTTP 客户端扩展在 Linux 中必须添加:setData([]),否则响应 400

图20

31、解决了 响应 400 的 Bug 之后,升级至开发环境,Docker 容器的 CPU:0.04%,内存:341MB,CPU无变化,内存增加了 (341MB – 283MB) = 60MB 左右。证明实际使用的内存量增加,系统分配总的内存量减少,Docker 的内存占用是在增加的(主要受到实际使用的内存量的影响)。如图21、图22

解决了 响应 400 的 Bug 之后,升级至开发环境,Docker 容器的 CPU:0.04%,内存:341MB,CPU无变化

图21

 

解决了 响应 400 的 Bug 之后,升级至开发环境,Docker 容器的 CPU:0.04%,内存:341MB,CPU无变化,内存增加了 (341MB - 283MB) = 60MB 左右。证明实际使用的内存量增加,系统分配总的内存量减少,Docker 的内存占用是在增加的(主要受到实际使用的内存量的影响)。

图22

32、延缓执行 60 秒,决定不再使用 sleep(),因为,在 sleep() 期间,内存一直处于占用中的,实际上延长了内存占用的时间。虽然可能降低了 CPU 的占用百分比。


4.6499099731445MB //延缓执行 60 秒前
4.6499099731445MB //延缓执行 60 秒后



10.250221252441MB //延缓执行 60 秒前
10.250221252441MB //延缓执行 60 秒后


33、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,设置同步标识的缓存,如果不存在,则同步,将数据在缓存中保留 60 秒。如果存在,则不同步(注:在同步成功后的 60 秒内,内存占用会很小,见第 2 个优化后的内存占用情况)。

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\services\CmcApiGroupService;
use console\services\CmcConsoleUserService;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     */
    public function actionSync()
    {
        // 设置同步标识、租户ID列表的缓存键
        $redisCache = Yii::$app->redisCache;
        $redisCacheIdentityKey = 'cmc_console_user_sync';
        $redisCacheGroupIdsKey = 'cmc_api_group_ids';
 
        // 从缓存中取回同步标识、租户ID列表
        $redisCacheIdentityData = $redisCache[$redisCacheIdentityKey];
        $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];
 
        if ($redisCacheIdentityData === false) {
            if ($redisCacheGroupIdsData === false) {
                // HTTP 请求,获取开通有效服务的租户ID列表
                $cmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();
                // 将租户ID列表存放到缓存供下次使用,将数据在缓存中保留 60 * 60 秒
                $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds, Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);
            } else {
                $cmcApiGroupIds = $redisCacheGroupIdsData;
            }
 
            /* 判断 $cmcApiGroupIds 是否为空,如果为空,则成功退出 */
            if (empty($cmcApiGroupIds)) {
                // 延缓执行 60 * 60 秒
                sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);
 
                return ExitCode::OK;
            }
 
            foreach ($cmcApiGroupIds as $cmcApiGroupId) {
 
                $isLockExist = RedisCmcConsoleUser::isLockExist($cmcApiGroupId);
                // 返回 true,表示锁定存在,即已经被其他客户端锁定
                if ($isLockExist === true) {
                    continue;
                }
 
                // HTTP请求,获取租户ID下的用户列表
                $httpGetUserListData = [
                    'groupId' => $cmcApiGroupId,
                    'loginId' => '',
                    'loginTid' => '',
                ];
                $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);
 
                // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
                $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');
 
                // 销毁变量
                unset($getUserListData['list']);
 
                // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
                $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $cmcApiGroupId])->indexBy('id')->asArray()->all();
 
                // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
                $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
                // 销毁变量
                unset($redisCmcConsoleUserItems);
                if (!empty($redisArrayDiffItems)) {
                    $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                    // 销毁变量
                    unset($redisArrayDiffItems);
                    if (RedisCmcConsoleUser::deleteAllByIds($cmcApiGroupId, $redisArrayDiffIds) === false) {
                        continue;
                    }
                }
 
                // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
                foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                    $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();
 
                    if (!isset($redisCmcConsoleUserItem)) {
 
                        $redisCmcConsoleUser = new RedisCmcConsoleUser();
                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                            $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUser->attributes = $attributes;
                        $redisCmcConsoleUser->insert();
 
                    } else {
                        if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {
 
                            $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                                $httpCmcConsoleUserItemValue);
                            $redisCmcConsoleUserItem->attributes = $attributes;
                            $redisCmcConsoleUserItem->save();
 
                        }
                    }
 
                }
            }
 
            // 延缓执行 60 秒
            // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
            // 将同步标识存放到缓存供下次使用,将数据在缓存中保留 60 秒
            $redisCache->set($redisCacheIdentityKey, $redisCacheIdentityKey, Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
 
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
            return ExitCode::OK;
        } else {
            return ExitCode::OK;
        }
    }
 
    /**
     * 获取属性列表
     * @param string $groupId 租户ID
     * @param array $httpCmcConsoleUser 框架服务控制台的用户模型(Http)
     *
     * @return array
     */
    private function getAttributes($groupId, $httpCmcConsoleUser) {
        return [
            'id' => $httpCmcConsoleUser['id'],
            'group_id' => $groupId,
            'login_name' => $httpCmcConsoleUser['login_name'],
            'user_token' => $httpCmcConsoleUser['user_token'],
            'user_nick' => $httpCmcConsoleUser['user_nick'],
            'user_pic' => $httpCmcConsoleUser['user_pic'],
            'user_mobile' => $httpCmcConsoleUser['user_mobile'] ? $httpCmcConsoleUser['user_mobile'] : '',
            'user_email' => $httpCmcConsoleUser['user_email'] ? $httpCmcConsoleUser['user_email'] : '',
            'user_sex' => $httpCmcConsoleUser['user_sex'],
            'user_type' => $httpCmcConsoleUser['user_type'],
            'user_birthday' => $httpCmcConsoleUser['user_birthday'],
            'user_chat_id' => $httpCmcConsoleUser['user_chat_id'] ? $httpCmcConsoleUser['user_chat_id'] : '',
            'is_open' => $httpCmcConsoleUser['is_open'],
            'add_time' => $httpCmcConsoleUser['add_time'],
            'update_time' => $httpCmcConsoleUser['update_time'],
        ];
    }
}
 

4.6499099731445MB //优化前
4.6722030639648MB //优化后(有 HTTP 请求,有同步)
2.8900680541992MB //优化后(无 HTTP 请求,无同步)
4.6064605712891MB //优化后(无 HTTP 请求,有同步)



10.250221252441MB //优化前
10.41194152832MB //优化后(有 HTTP 请求,有同步)
2.9416198730469MB //优化后(无 HTTP 请求,无同步)
10.346199035645MB //优化后(无 HTTP 请求,有同步)


34、分析优化第 2 个命令行脚本,\console\controllers\ConfigColumnUserController.php,设置同步标识的缓存,如果不存在,则同步,将数据在缓存中保留 60 秒。如果存在,则不同步(注:在同步成功后的 60 秒内,内存占用会很小,见第 2 个优化后的内存占用情况)。

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/03/01
 * Time: 13:17
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\models\ConfigColumnUser;
use console\services\CmcApiGroupService;
use console\services\CmcConsoleUserService;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 栏目人员配置
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class ConfigColumnUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至栏目人员配置模型(MySQL)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     */
    public function actionSync()
    {
        // 设置同步标识、租户ID列表的缓存键
        $redisCache = Yii::$app->redisCache;
        $redisCacheIdentityKey = 'config_column_user_sync';
        $redisCacheGroupIdsKey = 'cmc_api_group_ids';
 
        // 从缓存中取回同步标识、租户ID列表
        $redisCacheIdentityData = $redisCache[$redisCacheIdentityKey];
        $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];
 
        if ($redisCacheIdentityData === false) {
            if ($redisCacheGroupIdsData === false) {
                // HTTP 请求,获取开通有效服务的租户ID列表
                // $cmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();
                // 将租户ID列表存放到缓存供下次使用,将数据在缓存中保留 60 * 60 秒
                // $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds, Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);
 
                return ExitCode::OK;
            } else {
                $cmcApiGroupIds = $redisCacheGroupIdsData;
            }
 
            /* 判断 $cmcApiGroupIds 是否为空,如果为空,则成功退出 */
            if (empty($cmcApiGroupIds)) {
                // 延缓执行 60 * 60 秒
                sleep(Yii::$app->params['configColumnUser']['isEmptyYesSleepTime']);
 
                return ExitCode::OK;
            }
 
            foreach ($cmcApiGroupIds as $cmcApiGroupId) {
 
                // HTTP请求,获取租户ID下的用户列表
                $httpGetUserListData = [
                    'groupId' => $cmcApiGroupId,
                    'loginId' => '',
                    'loginTid' => '',
                ];
                $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);
 
                // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
                $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');
 
                // 销毁变量
                unset($getUserListData);
 
                // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
                $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $cmcApiGroupId])->isDeletedNo()->indexBy('user_id')->asArray()->all();
 
                // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Http) 中的记录
                $diffItems = array_diff_key($configColumnUserItems, $httpCmcConsoleUserItems);
 
                // 销毁变量
                unset($httpCmcConsoleUserItems, $configColumnUserItems);
                if (!empty($diffItems)) {
                    // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                    $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                        'and',
                        ['group_id' => $cmcApiGroupId],
                        ['in', 'user_id', $diffItems],
                    ])->isDeletedNo()->all();
                    foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                        /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                        $toBeDeletedConfigColumnUserItem->softDelete();
                    }
                    continue;
                }
            }
 
            // 延缓执行 60 秒
            // sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);
            // 将同步标识存放到缓存供下次使用,将数据在缓存中保留 60 秒
            $redisCache->set($redisCacheIdentityKey, $redisCacheIdentityKey, Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
 
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
            return ExitCode::OK;
        } else {
            return ExitCode::OK;
        }
 
    }
}
 

5.3451461791992MB //优化前
5.5944900512695MB //优化后(有 HTTP 请求,有同步)
2.8861389160156MB //优化后(无 HTTP 请求,无同步)
5.3590927124023MB //优化后(无 HTTP 请求,有同步)



12.024124145508MB //优化前
11.753318786621MB //优化后(有 HTTP 请求,有同步)
2.9376907348633MB //优化后(无 HTTP 请求,无同步)
11.67618560791MB //优化后(无 HTTP 请求,有同步)


35、升级至开发环境,Docker 容器的 CPU:16%,内存:486MB,CPU 增加了 (16% – 0.04%) = 15.96% 左右,内存增加了 (486MB – 341MB) = 140MB 左右,证明同步成功后的 60 秒内,避免再次同步。优化的方案是不可行的(与预期不相符)。如图23、图24

升级至开发环境,Docker 容器的 CPU:16%,内存:486MB,CPU 增加了 (16% - 0.04%) = 15.96% 左右

图23

 

升级至开发环境,Docker 容器的 CPU:16%,内存:486MB,内存增加了 (486MB - 341MB) = 140MB 左右

图24

36、仔细对比与第 31 步骤上的差异,在于命令行每次执行快结束时,使用了 sleep(),决定在同步成功后的 60 秒内,使用 sleep(),在同步成功后的 60 秒内,仅会再执行一次命令行(且执行时间长度大于:60 秒),而不会执行多次。查看 supervisord 运行状态,发现命令行执行的时间很短、频率很高,尤其是第 2 个命令行。如图25

仔细对比与第 31 步骤上的差异,在于命令行每次执行快结束时,使用了 sleep(),决定在同步成功后的 60 秒内,使用 sleep(),在同步成功后的 60 秒内,仅会再执行一次命令行(且执行时间长度大于:60 秒),而不会执行多次。查看 supervisord 运行状态,发现命令行执行的时间很短、频率很高,尤其是第 2 个命令行。

图25


        if ($redisCacheIdentityData === false) {
        } else {
            // 延缓执行 60 秒
            sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
            return ExitCode::OK;
        }



        if ($redisCacheIdentityData === false) {
        } else {
            // 延缓执行 60 秒
            sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);
            return ExitCode::OK;
        }


37、升级至开发环境,Docker 容器的 CPU:0.04%,内存:283MB,CPU 无变化,内存减少了 (341MB – 283MB) = 60MB 左右。证明同步成功后的 60 秒内,避免再次同步。优化的方案是可行的。如图26、图27

升级至开发环境,Docker 容器的 CPU:0.04%,内存:283MB,CPU 无变化

图26

 

升级至开发环境,Docker 容器的 CPU:0.04%,内存:283MB,CPU 无变化,内存减少了 (341MB - 283MB) = 60MB 左右。证明同步成功后的 60 秒内,避免再次同步。优化的方案是可行的。

图27

38、决定再次降低同步的执行频率,同步成功后的 5 * 60 秒内,避免再次同步。查看 supervisord 运行状态,发现命令行执行的时间很长、频率很低。升级至开发环境,Docker 容器的监控数据无变化。证明当执行频率达到某个临界值之后,再次降低,优化的意义不大了的,顶多 CPU 的平均值更低一些(理论上)。如图28

决定再次降低同步的执行频率,同步成功后的 5 * 60 秒内,避免再次同步。查看 supervisord 运行状态,发现命令行执行的时间很长、频率很低。升级至开发环境,Docker 容器的监控数据无变化。证明当执行频率达到某个临界值之后,再次降低,优化的意义不大了的,顶多 CPU 的平均值更低一些(理论上)。

图28

39、避免在生产环境中,当租户数量过多时,一次同步所有租户下的用户,进而导致内存占用过大、运行时间过长,决定一次仅同步一个租户下的用户。

40、分析优化第 1 个命令行脚本,\console\controllers\CmcConsoleUserController.php,遍历租户ID列表时,break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表。在 有 HTTP 请求,有同步 的情况下,实际使用的内存量增加 1.54 MB 左右,系统分配总的内存量减少 0.75 MB 左右

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/01/09
 * Time: 13:55
 */
 
namespace console\controllers;
 
use Yii;
use console\models\redis\cmc_console\User as RedisCmcConsoleUser;
use console\services\CmcApiGroupService;
use console\services\CmcConsoleUserService;
use yii\base\InvalidArgumentException;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 框架服务控制台的用户
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class CmcConsoleUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至框架服务控制台的用户模型(Redis)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     * @throws InvalidArgumentException if the $direction or $sortFlag parameters do not have
     */
    public function actionSync()
    {
        // 设置同步标识的缓存键
        $redisCache = Yii::$app->redisCache;
        $redisCacheIdentityKey = 'cmc_console_user_sync';
 
        // 从缓存中取回同步标识
        $redisCacheIdentityData = $redisCache[$redisCacheIdentityKey];
 
        if ($redisCacheIdentityData === false) {
            // HTTP 请求,获取开通有效服务的租户ID列表
            $httpCmcApiGroupIds = CmcApiGroupService::httpGetGroupIds();
 
            /* 判断 $httpCmcApiGroupIds 是否为空,如果为空,则成功退出 */
            if (empty($httpCmcApiGroupIds)) {
                // 延缓执行 60 * 60 秒
                sleep(Yii::$app->params['cmcConsoleUser']['isEmptyYesSleepTime']);
 
                return ExitCode::OK;
            }
 
            // 设置租户ID列表的缓存键
            $redisCacheGroupIdsKey = 'cmc_api_group_ids';
 
            // 从缓存中取回租户ID列表
            $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];
 
            // 是否设置租户ID列表的缓存,默认:否
            $isSetRedisCacheGroupIds = false;
 
            if ($redisCacheGroupIdsData === false) {
                $cmcApiGroupIds = [];
                foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                    $cmcApiGroupIds[] = [
                        'group_id' => $httpCmcApiGroupId,
                        'cmc_console_user_last_synced_at' => 0, //上次同步时间
                        'config_column_user_last_synced_at' => 0, //上次同步时间
                    ];
                }
                // 是否设置租户ID列表的缓存:是
                $isSetRedisCacheGroupIds = true;
            } else {
                // 获取 group_id 值列表
                $redisCacheGroupIds = ArrayHelper::getColumn($redisCacheGroupIdsData, 'group_id');
                $cmcApiGroupIds = $redisCacheGroupIdsData;
                foreach ($httpCmcApiGroupIds as $httpCmcApiGroupId) {
                    if (!in_array($httpCmcApiGroupId, $redisCacheGroupIds)) {
                        $cmcApiGroupIds[] = [
                            'group_id' => $httpCmcApiGroupId,
                            'cmc_console_user_last_synced_at' => 0, //上次同步时间
                            'config_column_user_last_synced_at' => 0, //上次同步时间
                        ];
                        // 是否设置租户ID列表的缓存:是
                        $isSetRedisCacheGroupIds = true;
                    }
                }
            }
 
            // 判断是否设置租户ID列表的缓存
            if ($isSetRedisCacheGroupIds) {
                // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
                $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);
            }
 
 
            // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
            $sortCmcApiGroupIds = $cmcApiGroupIds;
            ArrayHelper::multisort($sortCmcApiGroupIds, 'cmc_console_user_last_synced_at', SORT_ASC);
 
            foreach ($sortCmcApiGroupIds as $sortCmcApiGroupId) {
                $isLockExist = RedisCmcConsoleUser::isLockExist($sortCmcApiGroupId['group_id']);
                // 返回 true,表示锁定存在,即已经被其他客户端锁定
                if ($isLockExist === true) {
                    continue;
                }
 
                // HTTP请求,获取租户ID下的用户列表
                $httpGetUserListData = [
                    'groupId' => $sortCmcApiGroupId['group_id'],
                    'loginId' => '',
                    'loginTid' => '',
                ];
                $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);
 
                // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
                $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');
 
                // 销毁变量
                unset($getUserListData['list']);
 
                // 基于租户ID查询框架服务控制台的用户模型(Redis)(以 ID 索引结果集)
                $redisCmcConsoleUserItems = RedisCmcConsoleUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->indexBy('id')->asArray()->all();
 
                // 使用键名比较计算数组的差集,如果不为空,则删除出现在 Redis 中但是未出现在 Http 中的记录
                $redisArrayDiffItems = array_diff_key($redisCmcConsoleUserItems, $httpCmcConsoleUserItems);
                // 销毁变量
                unset($redisCmcConsoleUserItems);
                if (!empty($redisArrayDiffItems)) {
                    $redisArrayDiffIds = array_keys($redisArrayDiffItems);
                    // 销毁变量
                    unset($redisArrayDiffItems);
                    if (RedisCmcConsoleUser::deleteAllByIds($sortCmcApiGroupId['group_id'], $redisArrayDiffIds) === false) {
                        continue;
                    }
                }
 
                // 遍历框架服务控制台的用户模型(Http),判断在 Redis 中是否存在,如果不存在,则插入,如果存在且更新时间不相等,则更新
                foreach ($httpCmcConsoleUserItems as $httpCmcConsoleUserItemValue) {
                    $redisCmcConsoleUserItem = RedisCmcConsoleUser::find()->where(['id' => $httpCmcConsoleUserItemValue['id']])->one();
 
                    if (!isset($redisCmcConsoleUserItem)) {
 
                        $redisCmcConsoleUser = new RedisCmcConsoleUser();
                        $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                            $httpCmcConsoleUserItemValue);
                        $redisCmcConsoleUser->attributes = $attributes;
                        $redisCmcConsoleUser->insert();
 
                    } else {
                        if ($httpCmcConsoleUserItemValue['update_time'] != $redisCmcConsoleUserItem['update_time']) {
 
                            $attributes = $this->getAttributes($getUserListData['group_info']['group_id'],
                                $httpCmcConsoleUserItemValue);
                            $redisCmcConsoleUserItem->attributes = $attributes;
                            $redisCmcConsoleUserItem->save();
 
                        }
                    }
 
                }
 
                // 从缓存中取回租户ID列表
                $cmcApiGroupIds = $redisCache[$redisCacheGroupIdsKey];
                // 设置当前租户的上次同步时间
                foreach ($cmcApiGroupIds as $cmcApiGroupIdKey => $cmcApiGroupId) {
                    if ($cmcApiGroupId['group_id'] == $sortCmcApiGroupId['group_id']) {
                        $cmcApiGroupIds[$cmcApiGroupIdKey]['cmc_console_user_last_synced_at'] = time();
                        break;
                    }
                }
 
                // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
                $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);
 
                // break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表
                break;
            }
 
            // 延缓执行 60 秒
            // sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
            // 将同步标识存放到缓存供下次使用,将数据在缓存中保留 60 秒
            $redisCache->set($redisCacheIdentityKey, $redisCacheIdentityKey, Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
 
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/cmc-console-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
            return ExitCode::OK;
        } else {
            // 延缓执行 60 秒
            sleep(Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
            return ExitCode::OK;
        }
    }
 
    /**
     * 获取属性列表
     * @param string $groupId 租户ID
     * @param array $httpCmcConsoleUser 框架服务控制台的用户模型(Http)
     *
     * @return array
     */
    private function getAttributes($groupId, $httpCmcConsoleUser) {
        return [
            'id' => $httpCmcConsoleUser['id'],
            'group_id' => $groupId,
            'login_name' => $httpCmcConsoleUser['login_name'],
            'user_token' => $httpCmcConsoleUser['user_token'],
            'user_nick' => $httpCmcConsoleUser['user_nick'],
            'user_pic' => $httpCmcConsoleUser['user_pic'],
            'user_mobile' => $httpCmcConsoleUser['user_mobile'] ? $httpCmcConsoleUser['user_mobile'] : '',
            'user_email' => $httpCmcConsoleUser['user_email'] ? $httpCmcConsoleUser['user_email'] : '',
            'user_sex' => $httpCmcConsoleUser['user_sex'],
            'user_type' => $httpCmcConsoleUser['user_type'],
            'user_birthday' => $httpCmcConsoleUser['user_birthday'],
            'user_chat_id' => $httpCmcConsoleUser['user_chat_id'] ? $httpCmcConsoleUser['user_chat_id'] : '',
            'is_open' => $httpCmcConsoleUser['is_open'],
            'add_time' => $httpCmcConsoleUser['add_time'],
            'update_time' => $httpCmcConsoleUser['update_time'],
        ];
    }
}
 

4.6722030639648MB //优化前(有 HTTP 请求,有同步)
2.8900680541992MB //优化前(无 HTTP 请求,无同步)
4.6064605712891MB //优化前(无 HTTP 请求,有同步)
6.2168731689453MB //优化后(有 HTTP 请求,有同步)
2.8925552368164MB //优化后(无 HTTP 请求,无同步)



10.41194152832MB //优化前(有 HTTP 请求,有同步)
2.9416198730469MB //优化前(无 HTTP 请求,无同步)
10.346199035645MB //优化前(无 HTTP 请求,有同步)
9.6655578613281MB //优化后(有 HTTP 请求,有同步)
2.9470977783203MB //优化后(无 HTTP 请求,无同步)


41、分析优化第 2 个命令行脚本,\console\controllers\ConfigColumnUserController.php,遍历租户ID列表时,break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表。在 无 HTTP 请求,有同步 的情况下,实际使用的内存量减少 0.45 MB 左右,系统分配总的内存量无变化

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/03/01
 * Time: 13:17
 */
 
namespace console\controllers;
 
use Yii;
use console\models\ConfigColumnUser;
use console\services\CmcConsoleUserService;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 栏目人员配置
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class ConfigColumnUserController extends Controller
{
    /**
     * 同步框架服务控制台的用户模型(Http)至栏目人员配置模型(MySQL)
     *
     * @throws ServerErrorHttpException
     * @throws HttpException 如果登录超时
     */
    public function actionSync()
    {
        // 设置同步标识的缓存键
        $redisCache = Yii::$app->redisCache;
        $redisCacheIdentityKey = 'config_column_user_sync';
 
        // 从缓存中取回同步标识
        $redisCacheIdentityData = $redisCache[$redisCacheIdentityKey];
 
        if ($redisCacheIdentityData === false) {
 
            // 设置租户ID列表的缓存键
            $redisCacheGroupIdsKey = 'cmc_api_group_ids';
 
            // 从缓存中取回租户ID列表
            $redisCacheGroupIdsData = $redisCache[$redisCacheGroupIdsKey];
 
            if ($redisCacheGroupIdsData === false) {
                return ExitCode::OK;
            } else {
                $cmcApiGroupIds = $redisCacheGroupIdsData;
            }
 
            /* 判断 $cmcApiGroupIds 是否为空,如果为空,则成功退出 */
            if (empty($cmcApiGroupIds)) {
                // 延缓执行 60 * 60 秒
                sleep(Yii::$app->params['configColumnUser']['isEmptyYesSleepTime']);
 
                return ExitCode::OK;
            }
 
            // 基于上次同步时间顺序排列,赋值给:$sortCmcApiGroupIds
            $sortCmcApiGroupIds = $cmcApiGroupIds;
            ArrayHelper::multisort($sortCmcApiGroupIds, 'config_column_user_last_synced_at', SORT_ASC);
 
            foreach ($sortCmcApiGroupIds as $sortCmcApiGroupId) {
 
                // HTTP请求,获取租户ID下的用户列表
                $httpGetUserListData = [
                    'groupId' => $sortCmcApiGroupId['group_id'],
                    'loginId' => '',
                    'loginTid' => '',
                ];
                $getUserListData = CmcConsoleUserService::httpGetUserList($httpGetUserListData);
 
                // 框架服务控制台的用户模型(Http),重建数组索引(以 ID 索引结果集)
                $httpCmcConsoleUserItems = ArrayHelper::index($getUserListData['list'], 'id');
 
                // 销毁变量
                unset($getUserListData);
 
                // 基于租户ID查询栏目人员配置模型(MySQL)(以 用户ID 索引结果集)
                $configColumnUserItems = ConfigColumnUser::find()->where(['group_id' => $sortCmcApiGroupId['group_id']])->isDeletedNo()->indexBy('user_id')->asArray()->all();
 
                // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 栏目人员配置模型(MySQL) 中但是未出现在 框架服务控制台的用户模型(Http) 中的记录
                $diffItems = array_diff_key($configColumnUserItems, $httpCmcConsoleUserItems);
 
                // 销毁变量
                unset($httpCmcConsoleUserItems, $configColumnUserItems);
                if (!empty($diffItems)) {
                    // 基于租户ID、用户ID查询栏目人员配置模型(MySQL)(待删除)
                    $toBeDeletedConfigColumnUserItems = ConfigColumnUser::find()->where([
                        'and',
                        ['group_id' => $sortCmcApiGroupId['group_id']],
                        ['in', 'user_id', $diffItems],
                    ])->isDeletedNo()->all();
                    foreach ($toBeDeletedConfigColumnUserItems as $toBeDeletedConfigColumnUserItem) {
                        /* @var $toBeDeletedConfigColumnUserItem ConfigColumnUser */
                        $toBeDeletedConfigColumnUserItem->softDelete();
                    }
                }
 
                // 从缓存中取回租户ID列表
                $cmcApiGroupIds = $redisCache[$redisCacheGroupIdsKey];
                // 设置当前租户的上次同步时间
                foreach ($cmcApiGroupIds as $cmcApiGroupIdKey => $cmcApiGroupId) {
                    if ($cmcApiGroupId['group_id'] == $sortCmcApiGroupId['group_id']) {
                        $cmcApiGroupIds[$cmcApiGroupIdKey]['config_column_user_last_synced_at'] = time();
                        break;
                    }
                }
 
                // 将 $cmcApiGroupIds 存放到缓存供下次使用,将数据在缓存中永久保留
                $redisCache->set($redisCacheGroupIdsKey, $cmcApiGroupIds);
 
                // break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表
                break;
            }
 
            // 延缓执行 60 秒
            // sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);
            // 将同步标识存放到缓存供下次使用,将数据在缓存中保留 60 秒
            $redisCache->set($redisCacheIdentityKey, $redisCacheIdentityKey, Yii::$app->params['cmcConsoleUser']['isEmptyNoSleepTime']);
 
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-usage-' . time() . '.txt', memory_get_usage() / 1024 / 1024 . 'MB');
            // file_put_contents('E:/wwwroot/pcs-api/console/runtime/logs/config-column-user-sync-memory-get-peak-usage-' . time() . '.txt', memory_get_peak_usage() / 1024 / 1024 . 'MB');
 
            return ExitCode::OK;
        } else {
            // 延缓执行 60 秒
            sleep(Yii::$app->params['configColumnUser']['isEmptyNoSleepTime']);
            return ExitCode::OK;
        }
 
    }
}

5.5944900512695MB //优化前(有 HTTP 请求,有同步)
2.8861389160156MB //优化前(无 HTTP 请求,无同步)
5.3590927124023MB //优化前(无 HTTP 请求,有同步)
4.9042816162109MB //优化后(无 HTTP 请求,有同步)
2.8837127685547MB //优化后(无 HTTP 请求,无同步)



11.753318786621MB //优化前(有 HTTP 请求,有同步)
2.9376907348633MB //优化前(无 HTTP 请求,无同步)
11.67618560791MB //优化前(无 HTTP 请求,有同步)
10.693077087402MB //优化后(无 HTTP 请求,有同步)
2.9382553100586MB //优化后(无 HTTP 请求,无同步)


42、升级至开发环境,Docker 容器的 CPU:0.07%,内存:283MB,CPU 增加了 (0.07% – 0.04%) = 0.03% 左右,内存减少了 (283MB – 276MB) = 7MB 左右。证明遍历租户ID列表时,break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表。优化的方案是不可行的,意义不大。如图29、图30

升级至开发环境,Docker 容器的 CPU:0.07%,内存:283MB,CPU 增加了 (0.07% - 0.04%) = 0.03% 左右
图29
升级至开发环境,Docker 容器的 CPU:0.07%,内存:283MB,CPU 增加了 (0.07% - 0.04%) = 0.03% 左右,内存减少了 (283MB - 276MB) = 7MB 左右。证明遍历租户ID列表时,break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表。优化的方案是不可行的,意义不大。
图30

43、查看 MySQL 的实例监控情况,内存占用:6166MB,参考价值不大,因为是与其他产品共享同一个数据库,如图31

查看 MySQL 的实例监控情况,内存占用:6166MB,参考价值不大,因为是与其他产品共享同一个数据库
图31

44、查看 Redis 的实例监控情况,已使用容量:75MB,如图32

查看 Redis 的实例监控情况,已使用容量:75MB
图32

45、占用内存的减少实现方案,总结如下:

(1)数据量的增加,会导致内存的持续增加,后期当数据量达致一定的量级时,需要支持集群部署
(2)Docker 容器的 CPU:5.4%,内存:371MB,内存减少了 (541MB – 371MB) = 170MB 左右,证明基于 unset() 实现的方案是可行的
(3)Docker 容器的 CPU:5.4%,内存:280MB,内存减少了 (371MB – 371MB) = 0MB 左右,证明减少命令行的执行时间,对于内存的使用优化无意义。
(4)Docker 容器的 CPU:0.04%,内存:283MB,内存减少了 (371MB – 283MB) = 90MB 左右,证明变量仅在使用时才定义,以数组形式获取数据,在查询方法前调用 asArray() 方法,来获取 PHP 数组形式的结果。优化的方案是可行的。
(5)Docker 容器的 CPU:92%,内存:435MB,CPU 增加了 (92% – 0.04%) = 91.96% 左右,内存增加了 (435MB – 283MB) = 152MB 左右。HTTP 请求响应 400 时,命令行抛出异常时,CPU 与内存皆增加得很厉害
(6)解决了 响应 400 的 Bug 之后,升级至开发环境,Docker 容器的 CPU:0.04%,内存:341MB,CPU无变化,内存增加了 (341MB – 283MB) = 60MB 左右。删除 RedisCmcConsoleUser::find(),以另外一种形式(HTTP 请求)来获取租户ID列表,实际使用的内存量增加 0.37 MB 左右,系统分配总的内存量减少 19.39 MB 左右。证明实际使用的内存量增加,系统分配总的内存量减少,Docker 的内存占用是在增加的(主要受到实际使用的内存量的影响)
(7)Docker 容器的 CPU:16%,内存:486MB,CPU 增加了 (16% – 0.04%) = 15.96% 左右,内存增加了 (486MB – 341MB) = 140MB 左右,查看 supervisord 运行状态,发现命令行执行的时间很短、频率很高时,CPU 与内存皆增加得一般厉害
(8)Docker 容器的 CPU:0.04%,内存:283MB,CPU 无变化,内存减少了 (341MB – 283MB) = 60MB 左右。证明同步成功后的 60 秒内,避免再次同步。优化的方案是可行的
(9)决定再次降低同步的执行频率,同步成功后的 5 * 60 秒内,避免再次同步。当执行频率达到某个临界值之后,再次降低,优化的意义不大了的,顶多 CPU 的平均值更低一些(理论上)
(10)遍历租户ID列表时,break 结束当前 foreach 结构的执行,即命令行的每一次运行,仅同步成功一个租户下的用户列表。Docker 容器升级后,性能指标未变化。优化的意义不大了的,虽然命令行同步一次的运行时间大幅度减少(原因可能在于开发环境的租户数量不多,总的数据量不大,优化的意义未体现出来。当租户数量更多时,内存占用的优化意义才能够体现出来)

 

]]>
https://www.shuijingwanwq.com/2019/07/24/3376/feed/ 0