lock – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Sun, 31 May 2026 03:52:39 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 Go复习笔记:Goroutine与Channel协同工作(解决加锁加休眠隐患) https://www.shuijingwanwq.com/2026/05/11/10311/ https://www.shuijingwanwq.com/2026/05/11/10311/#respond Mon, 11 May 2026 03:48:56 +0000 https://www.shuijingwanwq.com/?p=10311 浏览量: 102

在之前的Go并发复习中,我通过阶乘案例重点剖析了“全局变量加锁同步”的诸多弊端,尤其是“加锁加休眠仍有隐患”这一核心问题——手动设置休眠时间无法精准匹配协程执行节奏,过长浪费资源、过短导致结果丢失,即便双重加锁也难以彻底规避资源竞争风险。本次复习我聚焦Goroutine与Channel的协同工作,通过一个完整实操案例,巩固二者结合的核心逻辑,彻底解决此前遗留的并发同步隐患,同时复盘协程与管道协同的关键细节,夯实自己的并发编程基础。

一、复习核心目标:解决“加锁加休眠”的固有隐患
回顾上一轮阶乘案例的复习,我发现:使用全局变量加锁同步实现多Goroutine通信时,最大的痛点是“主线程与协程的同步问题”。为了让主线程等待协程完成任务,我不得不手动添加time.Sleep()休眠,但这种方式完全依赖主观估算,无法适配实际运行中的系统负载、任务复杂度变化,本质上是一种“治标不治本”的解决方案。
而Goroutine与Channel的协同,正是Go语言为解决这一问题提供的原生方案——无需手动加锁(Channel本身并发安全),无需手动休眠(借助Channel的阻塞特性实现精准同步),让多协程之间的通信与同步更高效、更安全、更灵活。本次复习我的核心目标,就是通过实操案例,吃透二者协同的底层逻辑,彻底解决“加锁加休眠仍有隐患”的问题。

二、协同案例需求与思路拆解(个人复习复盘)
本次我选取的协同案例,是最基础也最具代表性的“生产者-消费者”模型简化版,需求明确且贴合实际,能够快速串联Goroutine与Channel的核心用法,具体需求如下:

  1. 开启1个writeData协程,向intChan管道中写入50个整数(生产者);
  2. 开启1个readData协程,从同一个intChan管道中读取数据(消费者);
  3. 主线程必须等待writeData和readData两个协程全部完成工作后,才能正常退出,杜绝协程未执行完主线程就退出的问题。
    站在个人复习的角度,这个案例的核心思路其实很清晰,本质是利用Channel的两大特性解决同步问题,我自己梳理了两点核心逻辑:
  • 用intChan作为数据传递管道,实现writeData与readData两个协程之间的通信,无需共享全局变量,从根源上避免资源竞争,替代我此前使用的“全局变量+互斥锁”方案;
  • 用exitChan作为信号同步管道,让readData协程在读取完所有数据后,向主线程发送“完成信号”,主线程通过监听exitChan的信号,精准等待所有协程完成,替代我此前使用的手动休眠方案。
    这里我重点复盘一个细节:管道的关闭(close())是协同工作的关键。writeData协程写入完所有数据后,必须关闭intChan,否则readData协程会一直阻塞在读取操作上,导致死锁;而exitChan的关闭则可根据需求选择,核心作用是向主线程传递“协程完成”的信号,确保主线程精准退出,这也是我此前复习中容易忽略的点。

三、完整代码实现与细节复盘
结合上述思路,我整理了完整的代码实现(沿用提供的案例代码),同时复盘其中的关键细节,记录自己复习时关注的重点:

package main

import (
	"fmt"
	// 此处无需引入time包,彻底摆脱手动休眠的依赖
)

// writeData 协程:向intChan写入50个整数(生产者)
func writeData(intChan chan int) {
	// 循环写入50个整数,逻辑简单但需注意循环边界
	for i := 1; i <= 50; i++ {
		// 向管道写入数据,有缓冲管道会自动调节写入节奏
		intChan <- i
		fmt.Println("writeData ", i)
		// 此处可注释time.Sleep,验证Channel的缓冲特性,无需手动控制写入速度
		// time.Sleep(time.Second)
	}
	// 关键操作:写入完成后关闭管道,告知readData协程“数据已写完”
	close(intChan)
}

// readData 协程:从intChan读取数据,读取完成后向exitChan发送信号(消费者)
func readData(intChan chan int, exitChan chan bool) {
	// 无限循环读取管道数据,直到管道关闭且无数据可读
	for {
		// 核心语法:v接收数据,ok判断管道是否关闭(true=有数据/未关闭,false=管道关闭且无数据)
		v, ok := <-intChan
		if !ok { // 管道关闭且无数据,说明读取完成,退出循环
			break
		}
		fmt.Printf("readData 读到数据=%v\n", v)
	}
	// readData 读取完数据后,即任务完成
	exitChan <- true
	// 关闭exitChan(可选,此处关闭是为了让主线程读取信号后正常退出,避免阻塞)
	close(exitChan)
}

func main() {
	// 创建两个管道
	// 1. 创建数据管道intChan:有缓冲管道,容量设为10,平衡读写节奏
	// 复盘:容量可根据实际需求调整,此处10足够承载writeData的写入速度,避免频繁阻塞
	intChan := make(chan int, 10)
	// 2. 创建信号管道exitChan:用于传递“协程完成”信号,容量设为1即可(仅需传递1个信号)
	exitChan := make(chan bool, 1)

	// 启动两个协程,共享同一个intChan,实现协同工作
	go writeData(intChan)
	go readData(intChan, exitChan)

	// 主线程监听exitChan,等待readData协程发送完成信号,精准退出
	// 复盘:此处替代了此前的time.Sleep,彻底解决休眠时间估算不准的问题
	for {
		_, ok := <-exitChan
		if !ok { // exitChan关闭且无信号,说明所有协程都已完成
			break
		}
	}
	// 主线程退出前可添加提示,验证协同效果
	fmt.Println("所有协程执行完成,主线程正常退出")
}

关键细节复盘(个人复习重点)
结合代码,我从自身复习视角,梳理了几个容易忽略但至关重要的细节,这些细节直接决定协程与管道协同的稳定性,也是我此前复习中踩过的坑:
细节1:管道的关闭时机与作用
writeData协程在写入完50个整数后,必须调用close(intChan)。我总结了原因:如果不关闭管道,readData协程会一直执行for循环,尝试从intChan中读取数据,当intChan中无数据时,会一直阻塞,最终导致整个程序死锁。
我自己补充复盘:close()仅能由“写方”调用,读方不能关闭管道;管道关闭后,仍可读取管道中剩余的数据,读取完后ok值会变为false,这是判断管道是否读取完成的核心依据,也是我需要牢记的语法细节。
细节2:exitChan的核心作用——替代手动休眠
我梳理出exitChan的核心作用:实现“主线程与协程的精准同步”。readData协程读取完所有数据后,向exitChan写入true,主线程通过监听exitChan的信号,确认readData协程完成;而writeData协程的完成,由intChan的关闭间接确认(readData能读取完所有数据,说明writeData已写入完成并关闭管道)。
这就彻底解决了我此前遇到的“加锁加休眠”隐患——无需估算协程执行时间,主线程仅在收到“完成信号”后才退出,既不浪费资源,也不会导致协程未执行完就被销毁,这也是Channel相比全局变量加锁的核心优势之一。
细节3:有缓冲管道的选择与优势
本次案例中,intChan设置为容量10的有缓冲管道,而非无缓冲管道。我复盘后总结:有缓冲管道的优势在于“平衡读写节奏”——writeData可以一次性写入多个数据(不超过容量),无需等待readData立即读取;readData可以根据自身节奏读取数据,避免频繁阻塞。
我自己测试过,若改为无缓冲管道,writeData写入数据时会直接阻塞,直到readData读取数据后才能继续写入,虽然也能实现协同,但效率会降低,更适合对数据实时性要求极高的场景,这点我也记录在了自己的复习笔记中。
细节4:协程与管道的绑定逻辑
两个协程操作的是同一个intChan,这是协程间通信的核心——Channel作为“桥梁”,实现了数据的安全传递,无需共享全局变量,也就无需手动添加互斥锁,从根源上避免了资源竞争问题。这正是Channel相比“全局变量+加锁”的核心优势,也是我本次复习需要重点巩固的核心逻辑。

四、运行结果与问题验证
我运行了上述代码,整理出核心运行结果(截取部分),方便自己后续回顾:如图1

我运行了上述代码,整理出核心运行结果(截取部分),方便自己后续回顾:如图1
/app/go-atguigu/channel-apply # go run main.go
writeData  1
writeData  2
writeData  3
writeData  4
writeData  5
writeData  6
writeData  7
writeData  8
writeData  9
writeData  10
writeData  11
readData 读到数据=1
readData 读到数据=2
readData 读到数据=3
readData 读到数据=4
readData 读到数据=5
readData 读到数据=6
readData 读到数据=7
readData 读到数据=8
readData 读到数据=9
readData 读到数据=10
readData 读到数据=11
readData 读到数据=12
writeData  12
writeData  13
writeData  14
writeData  15
writeData  16
writeData  17
writeData  18
writeData  19
writeData  20
writeData  21
writeData  22
writeData  23
readData 读到数据=13
readData 读到数据=14
readData 读到数据=15
readData 读到数据=16
readData 读到数据=17
readData 读到数据=18
readData 读到数据=19
readData 读到数据=20
readData 读到数据=21
readData 读到数据=22
readData 读到数据=23
readData 读到数据=24
writeData  24
writeData  25
writeData  26
writeData  27
writeData  28
writeData  29
writeData  30
writeData  31
writeData  32
writeData  33
writeData  34
writeData  35
readData 读到数据=25
readData 读到数据=26
readData 读到数据=27
readData 读到数据=28
readData 读到数据=29
readData 读到数据=30
readData 读到数据=31
readData 读到数据=32
readData 读到数据=33
readData 读到数据=34
readData 读到数据=35
readData 读到数据=36
writeData  36
writeData  37
writeData  38
writeData  39
writeData  40
writeData  41
writeData  42
writeData  43
writeData  44
writeData  45
writeData  46
writeData  47
readData 读到数据=37
readData 读到数据=38
readData 读到数据=39
readData 读到数据=40
readData 读到数据=41
readData 读到数据=42
readData 读到数据=43
readData 读到数据=44
readData 读到数据=45
readData 读到数据=46
readData 读到数据=47
readData 读到数据=48
writeData  48
writeData  49
writeData  50
readData 读到数据=49
readData 读到数据=50
/app/go-atguigu/channel-apply # 

所有协程执行完成,主线程正常退出
从运行结果中,我验证了以下几点,也进一步巩固了知识点:

  1. writeData协程成功写入50个整数,readData协程成功读取所有数据,二者协同正常,达到了预期效果;
  2. 主线程没有添加任何手动休眠,仅通过监听exitChan的信号,就实现了等待两个协程完成后再退出,彻底解决了我此前“休眠时间估算不准”的隐患;
  3. 整个过程无需手动加锁,没有出现任何资源竞争报错,印证了Channel的并发安全特性,也让我更坚信Channel在并发编程中的实用性。
    我还做了补充测试:若注释掉writeData协程中的close(intChan),运行后会触发死锁——readData协程会一直阻塞在读取操作上,exitChan无法收到完成信号,主线程也会一直阻塞,这也再次验证了“管道关闭时机”的重要性,也让我加深了对这一细节的记忆。

五、复习总结与核心收获
本次通过Goroutine与Channel的协同案例复习,我不仅巩固了二者的基础用法,更重要的是彻底解决了此前阶乘案例中“加锁加休眠仍有隐患”的核心问题,结合自身复习情况,梳理出以下核心收获:

  1. Goroutine与Channel的协同,是我目前掌握的Go并发编程最优方案之一——Channel实现协程间安全通信,无需共享全局变量和手动加锁;其阻塞特性实现协程与主线程的精准同步,无需手动休眠,从根源上规避了同步隐患,解决了我此前的核心痛点。
  2. 管道的关闭(close())是协同工作的关键,写方完成写入后必须关闭管道,否则读方会一直阻塞,导致死锁;读方通过“v, ok := <-chan”判断管道是否关闭,是实现协同退出的核心语法,这是我本次复习重点记忆的细节。
  3. 有缓冲与无缓冲管道的选择,需结合实际场景,我自己总结了适配场景:有缓冲管道适合平衡读写节奏,提升效率;无缓冲管道适合实时性要求高的场景,实现读写“同步阻塞”,后续开发中可根据需求灵活选择。
  4. 对比我此前使用的“全局变量+加锁”方案,Channel的优势不仅在于简化代码、避免资源竞争,更在于其“精准同步”的能力,让并发程序更稳定、更可维护,这也让我更深刻理解了Go语言“通过通信来共享内存”的设计哲学。
    本次复习我已完成Goroutine与Channel协同的核心实操,成功解决了遗留的并发同步隐患。后续我将继续复盘更复杂的协同场景(如多生产者、多消费者模型),进一步深化对Channel高级用法的理解,确保在实际开发中能够灵活运用二者,写出高效、安全的并发代码,夯实自己的Go并发编程核心基础。
]]>
https://www.shuijingwanwq.com/2026/05/11/10311/feed/ 0
Go并发学习:从阶乘案例看Channel的必要性 https://www.shuijingwanwq.com/2026/05/02/9660/ https://www.shuijingwanwq.com/2026/05/02/9660/#comments Sat, 02 May 2026 07:34:39 +0000 https://www.shuijingwanwq.com/?p=9660 浏览量: 219

在Go语言并发编程中,Goroutine是实现高效并发的核心,但多个Goroutine之间的通信与同步,往往是新手容易踩坑的地方。本文将以“计算1-200各数阶乘并存入map”为案例,结合学习过程中遇到的无锁、无休眠等问题,逐步分析全局变量加锁同步的局限性,最终理解Channel出现的必要性,记录自己的学习心得与思考。

一、案例需求与初始思路
本次学习的核心需求:使用Goroutine计算1-200每个数的阶乘,将结果存入map中,最终打印所有结果。
初始思路很直接,参考PPT中的引导:

  1. 编写阶乘计算函数,负责计算单个数字的阶乘,并将结果存入map;
  2. 启动多个Goroutine(对应1-200的每个数),并发执行阶乘计算与结果存储;
  3. 将map设为全局变量,方便所有Goroutine访问和修改。
    按照这个思路,我先写出了基础代码框架,但在实际运行中,出现了各种问题,也正是这些问题,让我逐步理解了Go并发同步的核心逻辑。

二、问题拆解:三种异常场景分析
结合PPT内容和自己的调试过程,我将问题分为“无锁场景”“无休眠场景”“加锁加休眠仍有隐患场景”三类,逐一分析问题本质和现象。

场景1:无锁状态——并发安全问题(concurrent map writes)
首先,我去掉了代码中的互斥锁(lock.Lock()和lock.Unlock()),仅保留全局map和Goroutine,代码核心片段如下:

var myMap = make(map[int]int, 10)

// 无锁版本test函数
func test(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	myMap[n] = res // 直接写入全局map,无任何锁保护
}

func main() {
	for i := 1; i <= 20; i++ {
		go test(i) // 启动多个Goroutine
	}
	// 未加休眠,未加锁打印
	for i, v := range myMap {
		fmt.Printf("map[%d]=%d\n", i, v)
	}
}

直接运行上述代码,报错并非concurrent map writes,而是fatal error: concurrent map iteration and map write,实际运行报错如下:

/app/go-atguigu/goroutine-demo02 # go run main.go
map[1]=1
map[2]=2
map[8]=40320
fatal error: concurrent map iteration and map write

goroutine 1 [running]:
internal/runtime/maps.fatal({0x4ccdcf?, 0x38ce22c48040?})
        /usr/local/go/src/runtime/panic.go:1181 +0x18
internal/runtime/maps.(*Iter).Next(0x0?)
        /usr/local/go/src/internal/runtime/maps/table.go:819 +0x86
main.main()
        /app/go-atguigu/goroutine-demo02/main.go:33 +0x119
exit status 2

只有给主线程添加休眠代码(//time.Sleep(time.Second * 5)),让主线程等待所有Goroutine先执行写操作,避免“读+写”并发,报错才会变为concurrent map writes,具体报错如下:

/app/go-atguigu/goroutine-demo02 # go run main.go
fatal error: concurrent map writes

goroutine 19 [running]:
internal/runtime/maps.fatal({0x4c8de5?, 0x0?})
        /usr/local/go/src/runtime/panic.go:1181 +0x18
main.test(...)
        /app/go-atguigu/goroutine-demo02/main.go:25
created by main.main in goroutine 1
        /app/go-atguigu/goroutine-demo02/main.go:30 +0x4b
exit status 2

这里需要重点纠正一个细节:上述无锁代码直接运行时,报错并非PPT中提到的concurrent map writes,而是concurrent map iteration and map write——原因是主线程在未加休眠的情况下,一边执行map的读操作(循环打印),一边有多个Goroutine执行map的写操作,属于“读+写”并发冲突;只有给主线程添加休眠代码,让主线程暂停执行读操作,等待所有Goroutine先执行写操作,此时多个Goroutine同时写map,才会报concurrent map writes错误,这两种报错本质都是map的并发安全问题,也是PPT中重点提到的“资源争夺问题”。如图1

原因是主线程在未加休眠的情况下,一边执行map的读操作(循环打印),一边有多个Goroutine执行map的写操作,属于“读+写”并发冲突;只有给主线程添加休眠代码,让主线程暂停执行读操作,等待所有Goroutine先执行写操作,此时多个Goroutine同时写map,才会报concurrent map writes错误


问题本质:Go中的map是非并发安全的数据结构(这也是Go并发编程的基础知识点),多个Goroutine同时对map执行写操作时,会出现资源竞争——多个Goroutine同时抢占map的内存资源,导致数据写入混乱、程序崩溃。正如PPT所讲,判断是否存在资源竞争的方法很简单,在编译程序时添加-race参数,就能清晰看到竞争点。
补充说明:不仅写操作会有竞争,即使是“读+写”同时进行,也会出现资源竞争。这是因为全局变量被所有Goroutine共享,没有任何限制的情况下,多个Goroutine的读写操作会相互干扰,这也是并发编程中“共享内存”模式的固有问题。

场景2:无休眠状态——主线程提前退出,结果丢失
解决无锁问题后,我添加了互斥锁,确保map的读写安全,但去掉了主线程的休眠代码(//time.Sleep(time.Second * 5)),代码核心片段如下:

package main
import (
	"fmt"
	//"time"
	"sync"
)

// 需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到map中。
// 最后显示出来。要求使用goroutine完成 

// 思路
// 1. 编写一个函数,来计算各个数的阶乘,并放入到 map中.
// 2. 我们启动的协程多个,统计的将结果放入到 map中
// 3. map 应该做出一个全局的.

var (
	myMap = make(map[int]int, 10)  
	//声明一个全局的互斥锁
	//lock 是一个全局的互斥锁, 
	//sync 是包: synchornized 同步
	//Mutex : 是互斥
	lock sync.Mutex
)

// test 函数就是计算 n!, 让将这个结果放入到 myMap
func test(n int) {
	
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}

	//这里我们将 res 放入到myMap
	//加锁
	lock.Lock()
	myMap[n] = res //concurrent map writes?
	//解锁
	lock.Unlock()
}

func main() {

	// 我们这里开启多个协程完成这个任务[200个]
	for i := 1; i <= 20; i++ {
		go test(i)
	}


	//休眠10秒钟【第二个问题 】
	//time.Sleep(time.Second * 5)

	//这里我们输出结果,变量这个结果
	lock.Lock()
	for i, v := range myMap {
		fmt.Printf("map[%d]=%d\n", i, v)
	}
	lock.Unlock()

}

运行后,控制台要么打印空结果,要么只打印少量结果,大部分阶乘结果丢失。如图2

运行后,控制台要么打印空结果,要么只打印少量结果,大部分阶乘结果丢失。
/app/go-atguigu/goroutine-demo02 # go run main.go
/app/go-atguigu/goroutine-demo02 # go run main.go
map[1]=1
map[3]=6
map[7]=5040
map[2]=2
/app/go-atguigu/goroutine-demo02 # go run main.go
/app/go-atguigu/goroutine-demo02 # go run main.go
map[10]=3628800
map[15]=1307674368000
map[2]=2
map[13]=6227020800
map[14]=87178291200
map[6]=720
map[12]=479001600
map[5]=120
map[11]=39916800
map[1]=1
map[3]=6
map[4]=24
map[8]=40320
map[7]=5040
map[9]=362880
/app/go-atguigu/goroutine-demo02 # 

问题本质:Goroutine是轻量级的执行单元,由Go runtime调度,而主线程(main函数对应的Goroutine)不会等待其他Goroutine执行完毕,会直接继续执行后续代码,执行完打印操作后就退出程序。一旦主线程退出,所有未执行完毕的Goroutine会被强制销毁,导致它们的计算结果无法写入map,最终出现结果丢失的情况。
这就是PPT中提到的“第二个问题”:主线程和Goroutine的执行节奏不一致,主线程没有等待其他Goroutine完成任务,导致结果无法正常获取。

场景3:加锁加休眠——看似可行,仍有隐患且不灵活
结合前两个问题,我同时添加了互斥锁和主线程休眠代码,也就是用户提供的“最终代码”,看似解决了所有问题,但深入调试后发现,这种方式依然存在隐患,且不够灵活。
首先,关于“打印时为什么也要加锁”,PPT中给出了关键解释:虽然我们主观估算10秒足够所有Goroutine执行完毕,但主线程并不知道其他Goroutine的执行状态。即使设置了休眠时间,底层调度仍可能出现“Goroutine未执行完,主线程就开始打印”的情况,此时打印操作(读map)和Goroutine的写操作会产生资源竞争,因此打印时也需要加锁保护。
其次,这种方式的核心隐患的是:休眠时间无法精准控制。

  • 如果休眠时间设置过长(比如10秒),会造成不必要的等待,浪费系统资源,降低程序效率;
  • 如果休眠时间设置过短(比如1秒),部分Goroutine可能还未执行完毕,依然会出现结果丢失的问题;
  • 实际开发中,Goroutine的执行时间受任务复杂度、系统负载等因素影响,无法提前精准估算,硬编码休眠时间,会导致程序的稳定性和可扩展性极差。
    除此之外,全局变量加锁同步的方式,还存在一个核心问题:多个Goroutine通过共享全局map通信,所有读写操作都需要通过锁来控制,不仅增加了代码复杂度,还容易出现死锁(比如忘记解锁)、锁粒度不合理等问题,不利于后续维护和扩展。正如PPT所讲,这种方式“不完美”,无法满足灵活、安全的并发通信需求。

三、总结:为什么Channel是必要的?
通过上面三个场景的分析,结合PPT中的引导,我深刻理解了Channel出现的必要性——它正是为了解决“全局变量加锁同步”的诸多弊端,实现Goroutine之间更安全、更灵活的通信与同步。
结合本次阶乘案例,总结Channel的必要性如下:

  1. 解决资源竞争问题,无需手动加锁
    Channel是Go语言中用于Goroutine之间通信的“管道”,遵循“通过通信来共享内存,而不是通过共享内存来通信”的设计哲学。它本身是并发安全的,多个Goroutine通过Channel发送和接收数据时,无需手动添加互斥锁,就能避免资源竞争问题。
    在阶乘案例中,如果使用Channel替代全局map,Goroutine计算出阶乘后,将结果发送到Channel中,主线程从Channel中接收结果并打印,就能彻底避免map的并发读写问题,代码更简洁、更安全。
  2. 精准同步Goroutine,无需手动休眠
    Channel具有“阻塞特性”:无缓冲Channel的发送和接收操作会相互阻塞,直到双方都准备好;有缓冲Channel在缓冲区满时会阻塞发送,缓冲区空时会阻塞接收。利用这种特性,主线程可以精准等待所有Goroutine执行完毕,无需手动设置休眠时间。
    比如,我们可以启动200个Goroutine,每个Goroutine计算完阶乘后,向Channel发送一个“完成信号”,主线程接收200个信号后,再执行打印操作,这样就能确保所有Goroutine都已执行完毕,既不会浪费时间,也不会丢失结果。
  3. 降低代码复杂度,提升可维护性
    全局变量加锁同步的方式,需要手动管理锁的加锁和解锁,容易出现遗漏解锁、死锁等问题,且全局变量的共享会导致代码耦合度升高。而Channel将“数据传递”和“同步控制”融为一体,Goroutine之间无需共享全局资源,通过Channel传递数据和信号,代码逻辑更清晰,维护成本更低。
  4. 适配更复杂的并发场景
    随着并发场景的复杂化(比如多生产者、多消费者模式),全局变量加锁的方式会越来越难以控制,而Channel支持单向通道、select多路复用等特性,能够轻松应对复杂的并发通信需求。例如,我们可以通过有缓冲Channel平衡生产者(计算阶乘的Goroutine)和消费者(打印结果的主线程)的速度,避免一方等待另一方的情况。

四、学习感悟
通过本次阶乘案例的学习,我深刻体会到:Goroutine让Go的并发变得简单,但真正的难点在于Goroutine之间的通信与同步。全局变量加锁同步是一种基础的解决方案,但它的局限性很明显,而Channel作为Go并发编程的核心原语,完美解决了这些问题。
Go语言的设计哲学告诉我们:“不要通过共享内存来通信,而要通过通信来共享内存”。Channel正是这种哲学的体现,它让并发编程变得更安全、更优雅、更可维护。本次学习不仅解决了阶乘案例中的具体问题,更让我理解了Go并发的核心逻辑,为后续学习更复杂的并发场景(如Worker Pool、select多路复用)打下了基础。
后续,我将继续深入学习Channel的使用(无缓冲、有缓冲、单向通道等),结合更多案例,熟练运用Channel实现Goroutine之间的通信与同步,真正掌握Go并发编程的精髓。

]]>
https://www.shuijingwanwq.com/2026/05/02/9660/feed/ 1
Redis 的锁定实现,基于 setnx 不具备过期时间的功能,弥补的方案一、二、三的对比分析 https://www.shuijingwanwq.com/2021/12/11/5520/ https://www.shuijingwanwq.com/2021/12/11/5520/#respond Sat, 11 Dec 2021 09:25:56 +0000 https://www.shuijingwanwq.com/?p=5520 浏览量: 140 1、参考网址:https://www.shuijingwanwq.com/2017/01/08/1505/ ,在 Yii2.0 下实现 Redis 的锁定机制的流程,其核心是使用 Redis setnx。 2、一般来说,在加锁成功后,执行相应的业务逻辑,然后删除锁。但是,如果业务逻辑因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在。因此,需要给锁加一个过期时间以防万一。 3、由于 Redis setnx 不具备过期时间的功能。方案一:借助 Expire 来设置,同时我们需要把两者用 Multi/Exec 包裹起来以确保请求的原子性,以免 setnx 成功了 Expire 却失败了。并且只有当加锁成功后,才设置过期时间。Lua 脚本如下所示:


local key = KEYS[1]
local value = KEYS[2]
local ttl = KEYS[3]

local ok = redis.call('setnx', key, value)

if ok == 1 then
redis.call('expire', key, ttl)
end

return ok


4、由于要使用到 Lua 脚本,还是过于麻烦了些。其实 Redis 从 2.6.12 起,SET 涵盖了 SETEX 的功能,并且 SET 本身已经包含了设置过期时间的功能,也就是说,我们前面需要的功能只用 SET 就可以实现。方案二的代码如下所示:
<pre class="wp-block-syntaxhighlighter-code">

<?php

$ok = $redis->set($key, $value, array('nx', 'ex' => $ttl));

if ($ok) {
    // 业务逻辑代码
    $redis->del($key);
}

?>

</pre>
5、但是如上实现仍然存在问题,设想一下,如果一个请求业务逻辑代码的执行时间比较长,甚至比锁的有效期还要长,导致在执行过程中,锁就失效了,此时另一个请求会获取锁,但前一个请求在执行完毕的时候,如果不加以判断直接删除锁,就会出现误删除其它请求创建的锁的情况,所以我们在创建锁的时候需要引入一个随机值。方案二的优化代码如下所示:
<pre class="wp-block-syntaxhighlighter-code">

<?php

$ok = $redis->set($key, $random, array('nx', 'ex' => $ttl));

if ($ok) {
    // 业务逻辑代码

    if ($redis->get($key) == $random) {
        $redis->del($key);
    }
}

?>

</pre>
6、在 Yii2.0 下实现 Redis 的锁定机制的流程 属于 方案三。其核心是使用 Redis setnx,且未使用 Expire 来设置过期时间。
<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' //锁定键名,如比赛分类
     *
     * @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;
    }
    
    /**
     * 判断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、其具体方案为加锁时,设置 value 的值为:当前服务器时间 + 过期时间。在加锁时,即使已经被其他客户端锁定,为了防止死锁,获取当前锁的过期时间。通过与当前服务器时间的比较,判断是否过期。再使用 getset,仍然有可能加锁成功。总体来看,方案三在实际的生产环境中经受了考验,不存在 Bug。 8、方案三 无法解决 方案二在步骤5中存在的问题。一般而言,在方案三下,如果要想避免步骤5中存在的问题。只能够尽量避免出现执行时间大于过期时间的情况了的。 9、且不论是方案一、二、三,还存在另外一个问题,与步骤5中的问题类似,即如果一个请求业务逻辑代码的执行时间比较长,甚至比锁的有效期还要长,导致在执行过程中,锁就失效了,此时另一个请求会获取锁,然后业务逻辑代码可能就会重复执行,甚至是并行执行,如果业务逻辑代码不支持重复执行与并行执行的话,就会产生新的问题。最终导致出现预期之外的脏数据之类的问题。此问题的解决方案为最好在业务逻辑代码层面再增加一个 MySQL 的乐观锁之类的实现。以避免出现重复或者并行执行的问题。以最大概率的降低此类问题出现的概率。 10、参考网址:http://www.redis.cn/commands/set.html 。方案三 的代码实现逻辑仍然过于复杂,相对于 方案二 而言。且为了尽量避免执行到直接加锁的流程,还实现了一个判断锁定是否存在的方法。计划后续有空余时间后,准备将 锁定实现 调整为 方案二。方案二的逻辑更为简单,可读性更好。由于SET命令加上选项已经可以完全取代SETNXSETEXPSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。如图1
由于SET命令加上选项已经可以完全取代SETNX, SETEX, PSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。

图1

 ]]>
https://www.shuijingwanwq.com/2021/12/11/5520/feed/ 0
在 Yii 2.0 中,Redis ActiveRecord 出现主键 ID 重复的情况的分析解决 https://www.shuijingwanwq.com/2020/05/27/4178/ https://www.shuijingwanwq.com/2020/05/27/4178/#respond Wed, 27 May 2020 07:17:23 +0000 https://www.shuijingwanwq.com/?p=4178 浏览量: 123

1、请求接口,响应参数中资源总数量为 30 个。包含 id 等于 37918 的资源是重复的。总计为 2 个。如图1

请求接口,响应参数中资源总数量为 30 个。包含 id 等于 37918 的资源是重复的。总计为 2 个。

图1


{
  "code": 10000,
  "message": "获取 CMC 用户列表成功",
  "data": {
    "items": [
      {
        "user_birthday": "1990-01-01",
        "login_name": "xianwanzhou",
        "add_time": "2020-03-31 09:41:07",
        "user_email": "",
        "group_id": "643d80843ae23bcfa95b75bae30a7656",
        "id": "37918",
        "user_pic": "http://uploads.cmc.lzgbdst.com/uploads/cmc_user_avatar/default_header.png",
        "update_time": "2020-03-31 09:43:16",
        "is_open": "1",
        "user_nick": "鲜万州",
        "user_mobile": "15208396209",
        "user_token": "6a1de11e594d61790963eaaf1d9bee8d",
        "user_chat_id": "f98e3b834f577cc3d83d74323cd3094d",
        "user_type": "2",
        "user_sex": "2"
      },
      {
        "user_birthday": "1990-01-01",
        "login_name": "xianwanzhou",
        "add_time": "2020-03-31 09:41:07",
        "user_email": "",
        "group_id": "643d80843ae23bcfa95b75bae30a7656",
        "id": "37918",
        "user_pic": "http://uploads.cmc.lzgbdst.com/uploads/cmc_user_avatar/default_header.png",
        "update_time": "2020-03-31 09:43:16",
        "is_open": "1",
        "user_nick": "鲜万州",
        "user_mobile": "15208396209",
        "user_token": "6a1de11e594d61790963eaaf1d9bee8d",
        "user_chat_id": "f98e3b834f577cc3d83d74323cd3094d",
        "user_type": "2",
        "user_sex": "2"
      }
    ],
    "_links": {
      "self": {
        "href": "http://pcsapi.cmc.lzgbdst.com/v1/cmc-users?login_id=7309cba1c93fc0a80663007612b784b8&login_tid=25f49c543b8ec31c6d905def0ad99913&per-page=30&page=1"
      }
    },
    "_meta": {
      "totalCount": 30,
      "pageCount": 1,
      "currentPage": 1,
      "perPage": 30
    }
  }
}


2、查看模型类,/common/models/redis/cmc_console/User.php

<?php
 
namespace common\models\redis\cmc_console;
 
use Yii;
use common\components\redis\ActiveRecord;
 
/**
 * This is the model class for table "{{%user}}".
 *
 * @property string $id
 * @property string $group_id
 * @property string $login_name
 * @property string $user_token
 * @property string $user_nick
 * @property string $user_pic
 * @property string $user_mobile
 * @property string $user_email
 * @property string $user_sex
 * @property string $user_birthday
 * @property string $user_type
 * @property string $user_chat_id
 * @property string $is_open
 * @property string $add_time
 * @property string $update_time
 */
class User extends ActiveRecord
{
    /**
     * @return array the list of attributes for this record
     */
    public function attributes()
    {
        return ['id', 'group_id', 'login_name', 'user_token', 'user_nick', 'user_pic', 'user_mobile', 'user_email', 'user_sex', 'user_birthday', 'user_type', 'user_chat_id', 'is_open', 'add_time', 'update_time'];
    }
     
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['id', 'group_id', 'login_name', 'user_token', 'user_nick', 'user_pic', 'user_mobile', 'user_email', 'user_sex', 'user_birthday', 'user_type', 'user_chat_id', 'is_open', 'add_time', 'update_time'], 'safe'],
        ];
    }
     
    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('model/redis/cmc-console/user', 'ID'),
            'group_id' => Yii::t('model/redis/cmc-console/user', 'Group ID'),
            'login_name' => Yii::t('model/redis/cmc-console/user', 'Login Name'),
            'user_token' => Yii::t('model/redis/cmc-console/user', 'User Token'),
            'user_nick' => Yii::t('model/redis/cmc-console/user', 'User Nick'),
            'user_pic' => Yii::t('model/redis/cmc-console/user', 'User Pic'),
            'user_mobile' => Yii::t('model/redis/cmc-console/user', 'User Mobile'),
            'user_email' => Yii::t('model/redis/cmc-console/user', 'User Email'),
            'user_sex' => Yii::t('model/redis/cmc-console/user', 'User Sex'),
            'user_type' => Yii::t('model/redis/cmc-console/user', 'User Type'),
            'user_birthday' => Yii::t('model/redis/cmc-console/user', 'User Birthday'),
            'user_chat_id' => Yii::t('model/redis/cmc-console/user', 'User Chat Id'),
            'is_open' => Yii::t('model/redis/cmc-console/user', 'Is Open'),
            'add_time' => Yii::t('model/redis/cmc-console/user', 'Add Time'),
            'update_time' => Yii::t('model/redis/cmc-console/user', 'Update Time'),
        ];
    }
}
 
 

3、查看 /common/components/redis/ActiveRecord.php ,定义了前缀,适用于所有 AR 键。

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/02/05
 * Time: 9:47
 */
 
namespace common\components\redis;
 
use Yii;
 
class ActiveRecord extends \yii\redis\ActiveRecord
{
    /**
     * Declares prefix of the key that represents the keys that store this records in redis.
     * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]].
     * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
     * 'order_item'. You may override this method if you want different key naming.
     * @return string the prefix to apply to all AR keys
     */
    public static function keyPrefix()
    {
        return Yii::$app->params['redisActiveRecord']['keyPrefix'] . parent::keyPrefix();
    }
}
 

4、打开 RedisDesktopManager,查看 用户 总数,确定为 29 个,如图2

打开 RedisDesktopManager,查看 用户 总数,确定为 29 个

图2

5、打开 RedisDesktopManager,确定主键值为 37918 的记录为 1 个,并未重复。如图3

打开 RedisDesktopManager,确定主键值为 37918 的记录为 1 个,并未重复。

图3

6、查看 pa:ar:user 的值,发现行 29、30 的值皆为 37918。由此确定,是此键的值导致了 id 等于 37918 的资源数等于 2 个。如图4

查看 pa:ar:user 的值,发现行 29、30 的值皆为 37918。由此确定,是此键的值导致了 id 等于 37918 的资源数等于 2 个。

图4

7、删除 pa:ar:user 的第 30 行,如图5

删除 pa:ar:user 的第 30 行

图5

8、再次请求接口,响应参数中资源总数量为 29 个。包含 id 等于 37918 的资源未重复。仅剩下 1 个。


{
  "code": 10000,
  "message": "获取 CMC 用户列表成功",
  "data": {
    "items": [
      {
        "user_birthday": "1990-01-01",
        "login_name": "xianwanzhou",
        "add_time": "2020-03-31 09:41:07",
        "user_email": "",
        "group_id": "643d80843ae23bcfa95b75bae30a7656",
        "id": "37918",
        "user_pic": "http://uploads.cmc.lzgbdst.com/uploads/cmc_user_avatar/default_header.png",
        "update_time": "2020-03-31 09:43:16",
        "is_open": "1",
        "user_nick": "鲜万州",
        "user_mobile": "15208396209",
        "user_token": "6a1de11e594d61790963eaaf1d9bee8d",
        "user_chat_id": "f98e3b834f577cc3d83d74323cd3094d",
        "user_type": "2",
        "user_sex": "2"
      }
    ],
    "_links": {
      "self": {
        "href": "http://pcsapi.cmc.lzgbdst.com/v1/cmc-users?login_id=7309cba1c93fc0a80663007612b784b8&login_tid=25f49c543b8ec31c6d905def0ad99913&per-page=29&page=1"
      }
    },
    "_meta": {
      "totalCount": 29,
      "pageCount": 1,
      "currentPage": 1,
      "perPage": 29
    }
  }
}


9、准备在本地环境复现一下 ID 主键重复的情况。请求接口,响应参数中资源总数量为 13 个。ID 主键未重复。如图6

准备在本地环境复现一下 ID 主键重复的情况。请求接口,响应参数中资源总数量为 13 个。ID 主键未重复。

图6


{
  "code": 10000,
  "message": "获取 CMC 用户列表成功",
  "data": {
    "items": [
      {
        "login_name": "13281105967",
        "update_time": "2019-12-11 14:28:26",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "13281105967",
        "user_sex": "2",
        "user_mobile": "13281105967",
        "user_type": "1",
        "is_open": "1",
        "user_chat_id": "ede7616c5e4232896453202cd0c3f7ec",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/20190219/1550570817-4LLQJJ.png",
        "id": "3",
        "add_time": "2018-04-26 10:05:28",
        "user_birthday": "1990-01-01",
        "user_email": "13281105967@chinamcloud.com",
        "user_token": "fb46626f0e71e423ca8ab4c750620a85"
      },
      {
        "login_name": "test10",
        "update_time": "2019-12-11 15:00:38",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test10",
        "user_sex": "2",
        "user_mobile": "13980074657",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "ce524778954bd10d5a0ff65dadc0354b",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "4",
        "add_time": "2019-12-11 14:55:07",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "bc8daab7ba8620c132cdf4e5de1d4758"
      },
      {
        "login_name": "jmj12130003",
        "update_time": "2019-12-13 14:41:55",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "jmj1213",
        "user_sex": "2",
        "user_mobile": "18412130003",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "84e1bb32ffa895e56d950bbfa24fefc7",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "49",
        "add_time": "2019-12-13 14:41:55",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "ba0ca65da7c3048fd9d449bb0d21a39b"
      },
      {
        "login_name": "test11",
        "update_time": "2019-12-17 11:14:53",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test11",
        "user_sex": "2",
        "user_mobile": "13980074650",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "bc1650a6834fc490de65a4527dfc8ae5",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "64",
        "add_time": "2019-12-17 11:14:53",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "04532bb62deb99bf229698c9e1a81da1"
      },
      {
        "login_name": "test12",
        "update_time": "2019-12-17 11:15:14",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test12",
        "user_sex": "2",
        "user_mobile": "13980074651",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "fa154673421e5a3731991498397d712d",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "65",
        "add_time": "2019-12-17 11:15:14",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "bcdc9e3781180b7bc18bc0e38777a467"
      },
      {
        "login_name": "test13",
        "update_time": "2019-12-17 11:15:35",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test13",
        "user_sex": "2",
        "user_mobile": "13980074652",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "35ce7075c1c57bb6c66d0f67a7840383",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "66",
        "add_time": "2019-12-17 11:15:35",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "633cae769e274cabd4441acb7be1c5a1"
      },
      {
        "login_name": "test14",
        "update_time": "2019-12-17 11:15:55",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test14",
        "user_sex": "2",
        "user_mobile": "13980074654",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "3190c55b297b51b38463da4eae6bbd95",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "67",
        "add_time": "2019-12-17 11:15:55",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "84c5871da19f7a489234af73c491f74f"
      },
      {
        "login_name": "test15",
        "update_time": "2019-12-17 11:16:16",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test15",
        "user_sex": "2",
        "user_mobile": "13980074655",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "fd072f598ec1a978f5fb968dfa386f0d",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "68",
        "add_time": "2019-12-17 11:16:16",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "5bb37b4045e9ad6eb1cc398d3170d33e"
      },
      {
        "login_name": "test16",
        "update_time": "2019-12-17 11:16:36",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test16",
        "user_sex": "2",
        "user_mobile": "13980074656",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "88178648656ae259bae213ee00ccb4c7",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "69",
        "add_time": "2019-12-17 11:16:36",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "2926796eb8e990aa8bd726e121cb91e8"
      },
      {
        "login_name": "test17",
        "update_time": "2019-12-17 11:17:07",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test17",
        "user_sex": "2",
        "user_mobile": "13980074653",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "b03d6e7b4db061d52b3d420844a5dde8",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "70",
        "add_time": "2019-12-17 11:17:07",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "bf028bc0353bf8ff070bfd62490ae284"
      },
      {
        "login_name": "test18",
        "update_time": "2019-12-17 11:17:25",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test18",
        "user_sex": "2",
        "user_mobile": "13980074658",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "c1eddd55f8efcec8da1e0cc6fc1a5624",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "71",
        "add_time": "2019-12-17 11:17:25",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "4adbe2313b7e4967ac518e4e0a73f5c9"
      },
      {
        "login_name": "test19",
        "update_time": "2019-12-17 11:17:41",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "test19",
        "user_sex": "2",
        "user_mobile": "13980074659",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "5bf1e9f7e4bf9aa2d8a633eddf628710",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "72",
        "add_time": "2019-12-17 11:17:41",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "dd7ab3f1fb3e8651b35ba070d10594c7"
      },
      {
        "login_name": "15708495493",
        "update_time": "2020-03-02 14:31:36",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "clover",
        "user_sex": "2",
        "user_mobile": "15708495493",
        "user_type": "2",
        "is_open": "1",
        "user_chat_id": "251f91338781d5354e08c1351ca3342d",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/default_header.png",
        "id": "166",
        "add_time": "2020-03-02 14:31:36",
        "user_birthday": "1990-01-01",
        "user_email": "",
        "user_token": "ff94d3f7b1dafada193ae7d6a82fa628"
      }
    ],
    "_links": {
      "self": {
        "href": "http://api.pcs-api.localhost/v1/cmc-users?login_id=2e368664c41b8bf511bcc9c65d86dbc3&login_tid=efda7e64b7747f8c4360b29aa9c18f77&per-page=13&page=1"
      }
    },
    "_meta": {
      "totalCount": 13,
      "pageCount": 1,
      "currentPage": 1,
      "perPage": 13
    }
  }
}


10、在程序执行过程中,插入数据至 Redis 中时,会判断 ID 是否存在,存在则更新,不存在则插入。初步怀疑是在并发请求的情况下。皆执行了插入的操作。


            $redisCmcConsoleUser = new RedisCmcConsoleUser();
            $redisCmcConsoleUserResult = $redisCmcConsoleUser->initSync(['group_id' => $groupInfo['group_id']], $loginId, $loginTid);
            if ($redisCmcConsoleUserResult === false) {
                throw new NotFoundHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '201041'), ['id' => $loginId])), 201041);
            }

            $redisCmcConsoleUserItem = $redisCmcConsoleUser::find()->where(['id' => $userInfo['id'], 'group_id' => Yii::$app->params['groupId']])->one();

            /* 如果资源不存在,则插入;否则更新 */
            $redisCmcConsoleUserAttributes = [
                'id' => $userInfo['id'],
                'group_id' => $groupInfo['group_id'],
                'login_name' => $userInfo['login_name'],
                'user_token' => $userInfo['user_token'],
                'user_nick' => $userInfo['user_nick'],
                'user_pic' => $userInfo['user_pic'],
                'user_mobile' => $userInfo['user_mobile'] ? $userInfo['user_mobile'] : '',
                'user_email' => $userInfo['user_email'] ? $userInfo['user_email'] : '',
                'user_sex' => $userInfo['user_sex'],
                'user_type' => $userInfo['user_type'],
                'user_birthday' => $userInfo['user_birthday'],
                'user_chat_id' => $userInfo['user_chat_id'] ? $userInfo['user_chat_id'] : '',
                'is_open' => $userInfo['is_open'],
                'add_time' => $userInfo['add_time'],
                'update_time' => $userInfo['update_time'],
            ];
            if (!isset($redisCmcConsoleUserItem)) {
                $redisCmcConsoleUser->attributes = $redisCmcConsoleUserAttributes;
                $redisCmcConsoleUser->insert();
                // 设置用户身份为已认证
                Yii::$app->user->setIdentity($redisCmcConsoleUser);
            } else {
                $redisCmcConsoleUserItem->attributes = $redisCmcConsoleUserAttributes;
                $redisCmcConsoleUserItem->save();
                // 设置用户身份为已认证
                Yii::$app->user->setIdentity($redisCmcConsoleUserItem);
            }


11、在程序执行过程中,插入数据至 Redis 中时,不论 ID 是否存在,皆插入。请求接口,响应参数中资源总数量为 14 个。包含 id 等于 3 的资源是重复的。总计为 2 个。如图7

在程序执行过程中,插入数据至 Redis 中时,不论 ID 是否存在,皆插入。请求接口,响应参数中资源总数量为 14 个。包含 id 等于 3 的资源是重复的。总计为 2 个。

图7


            if (!isset($redisCmcConsoleUserItem)) {
                $redisCmcConsoleUser->attributes = $redisCmcConsoleUserAttributes;
                $redisCmcConsoleUser->insert();
                // 设置用户身份为已认证
                Yii::$app->user->setIdentity($redisCmcConsoleUser);
            } else {
                $redisCmcConsoleUserItem->attributes = $redisCmcConsoleUserAttributes;
                $redisCmcConsoleUserItem->insert();
                // 设置用户身份为已认证
                Yii::$app->user->setIdentity($redisCmcConsoleUserItem);
            }



{
  "code": 10000,
  "message": "获取 CMC 用户列表成功",
  "data": {
    "items": [
      {
        "login_name": "13281105967",
        "update_time": "2019-12-11 14:28:26",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "13281105967",
        "user_sex": "2",
        "user_mobile": "13281105967",
        "user_type": "1",
        "is_open": "1",
        "user_chat_id": "ede7616c5e4232896453202cd0c3f7ec",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/20190219/1550570817-4LLQJJ.png",
        "id": "3",
        "add_time": "2018-04-26 10:05:28",
        "user_birthday": "1990-01-01",
        "user_email": "13281105967@chinamcloud.com",
        "user_token": "fb46626f0e71e423ca8ab4c750620a85"
      },
      {
        "login_name": "13281105967",
        "update_time": "2019-12-11 14:28:26",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "13281105967",
        "user_sex": "2",
        "user_mobile": "13281105967",
        "user_type": "1",
        "is_open": "1",
        "user_chat_id": "ede7616c5e4232896453202cd0c3f7ec",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/20190219/1550570817-4LLQJJ.png",
        "id": "3",
        "add_time": "2018-04-26 10:05:28",
        "user_birthday": "1990-01-01",
        "user_email": "13281105967@chinamcloud.com",
        "user_token": "fb46626f0e71e423ca8ab4c750620a85"
      }
    ],
    "_links": {
      "self": {
        "href": "http://api.pcs-api.localhost/v1/cmc-users?login_id=2e368664c41b8bf511bcc9c65d86dbc3&login_tid=efda7e64b7747f8c4360b29aa9c18f77&per-page=14&page=1"
      }
    },
    "_meta": {
      "totalCount": 14,
      "pageCount": 1,
      "currentPage": 1,
      "perPage": 14
    }
  }
}


12、由此可以确认,Redis ActiveRecord 不能够确保主键 ID 的唯一性的。现阶段有 2 种方案,第 1 种方案是插入记录时基于 Redis 实现唯一性的锁定。第 2 种方案是查询时去重。决定采用第 2 种方案。由于 redis 不支持 SQL 查询,因此查询 API 仅限于以下方法: where(),limit(),offset(),orderBy() 和 indexBy()。 (orderBy() 尚未实现:#1305)。基于 indexBy()。请求接口,响应参数中资源总数量为 13 个。ID 主键未重复。但是,totalCount 的值为 14。统计错误。如图8

由此可以确认,Redis ActiveRecord 不能够确保主键 ID 的唯一性的。现阶段有 2 种方案,第 1 种方案是插入记录时基于 Redis 实现唯一性的锁定。第 2 种方案是查询时去重。决定采用第 2 种方案。由于 redis 不支持 SQL 查询,因此查询 API 仅限于以下方法: where(),limit(),offset(),orderBy() 和 indexBy()。 (orderBy() 尚未实现:#1305)。基于 indexBy()。请求接口,响应参数中资源总数量为 13 个。ID 主键未重复。但是,totalCount 的值为 14。统计错误。

图8


        // 查询当前用户所属租户 ID 下的 CMC 的用户模型(Redis)
        $query = $modelClass::find()
            ->where([
                'group_id' => $identity->group_id,
                'is_open' => $modelClass::STATUS_ENABLED
            ])
            ->indexBy('id');



{
  "code": 10000,
  "message": "获取 CMC 用户列表成功",
  "data": {
    "items": [
      {
        "login_name": "13281105967",
        "update_time": "2019-12-11 14:28:26",
        "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
        "user_nick": "13281105967",
        "user_sex": "2",
        "user_mobile": "13281105967",
        "user_type": "1",
        "is_open": "1",
        "user_chat_id": "ede7616c5e4232896453202cd0c3f7ec",
        "user_pic": "https://pgcupload.flydev.chinamcloud.cn/uploads/cmc_user_avatar/20190219/1550570817-4LLQJJ.png",
        "id": "3",
        "add_time": "2018-04-26 10:05:28",
        "user_birthday": "1990-01-01",
        "user_email": "13281105967@chinamcloud.com",
        "user_token": "fb46626f0e71e423ca8ab4c750620a85"
      }
    ],
    "_links": {
      "self": {
        "href": "http://api.pcs-api.localhost/v1/cmc-users?login_id=2e368664c41b8bf511bcc9c65d86dbc3&login_tid=efda7e64b7747f8c4360b29aa9c18f77&per-page=14&page=1"
      }
    },
    "_meta": {
      "totalCount": 14,
      "pageCount": 1,
      "currentPage": 1,
      "perPage": 14
    }
  }
}


13、因此,尝试采用第 1 种方案。不过由于 此处程序实现 是所有请求皆会执行到的流程,担心锁定实现降低程序性能,最终决定,放弃插入,仅做更新。如果 Redis 中用户不存在,则插入;否则更新。调整为:如果 Redis 中用户不存在,则响应 404;否则更新。


            /* 如果资源不存在,则响应 404 */
            if (!isset($redisCmcConsoleUserItem)) {
                throw new NotFoundHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '201011'), ['user_nick' => $userInfo['user_nick']])), 201011);
            }

            /* 更新 */
            $redisCmcConsoleUserItem->attributes = $redisCmcConsoleUserAttributes;
            $redisCmcConsoleUserItem->save();
            // 设置用户身份为已认证
            Yii::$app->user->setIdentity($redisCmcConsoleUserItem);


14、如果 Redis 中用户不存在,则响应 404。如图9

如果 Redis 中用户不存在,则响应 404。

图9

15、但是,此修复仅防止以后出现用户重复的问题,之前已经重复的用户,仍然是重复的。如果要去重,需要手动操作 Redis。且此修复会衍生出一个新的影响体验的问题,即在框架处新添加了一个用户 B 后,如果用户 B 在 Redis 中不存在,则用户 B 无法使用策划指挥。需要等待用户 B 同步至 Redis 后,才可使用。

 

]]>
https://www.shuijingwanwq.com/2020/05/27/4178/feed/ 0
在 Rancher 中实现集群的部署与测试 ( 负载均衡 与 命令行下的 Redis 锁定、性能提升 ) https://www.shuijingwanwq.com/2020/03/09/3998/ https://www.shuijingwanwq.com/2020/03/09/3998/#respond Mon, 09 Mar 2020 02:10:11 +0000 https://www.shuijingwanwq.com/?p=3998 浏览量: 91 1、在 Rancher 中,克隆已经存在的 Docker 容器 channel-pub-api-prev 为 channel-pub-api-prev-1、channel-pub-api-prev-2,如图1
在 Rancher 中,克隆已经存在的 Docker 容器 channel-pub-api-prev 为 channel-pub-api-prev-1、channel-pub-api-prev-2

图1

2、最终 3 个容器组成了一个集群,如图2
最终 3 个容器组成了一个集群

图2

3、在负载均衡中,添加服务规则,设置域名与目标容器的映射关系,如图3
在负载均衡中,添加服务规则,设置域名与目标容器的映射关系

图3

4、在 Postman 中执行了 2 次请求,第 1 次为 GET,第 2 次为 POST,最终在容器 channel-pub-api-prev 中有一条 GET 请求日志,在容器 channel-pub-api-prev-1 中有一条 POST 请求日志,在容器 channel-pub-api-prev-2 中无请求日志,负载均衡设置符合预期,如图4
在 Postman 中执行了 2 次请求,第 1 次为 GET,第 2 次为 POST,最终在容器 channel-pub-api-prev 中有一条 GET 请求日志,在容器 channel-pub-api-prev-1 中有一条 POST 请求日志,在容器 channel-pub-api-prev-2 中无请求日志,负载均衡设置符合预期

图4

5、在 3 个容器中,均在基于 Supervisor 监听队列,在队列所调用的服务中,输出日志至文件中,最终在容器 channel-pub-api-prev 中有一条服务日志,表明队列作业在容器 channel-pub-api-prev 中执行的。如图5
在 3 个容器中,均在基于 Supervisor 监听队列,在队列所调用的服务中,输出日志至文件中,最终在容器 channel-pub-api-prev 中有一条服务日志,表明队列作业在容器 channel-pub-api-prev 中执行的。

图5

6、查看 命令行下的 Redis 锁定日志,不仅存在 判断Redis模型的锁定是否存在(已存在),而且存在 Redis模型的锁定实现时失败的 日志。时间:14:58 – 14:50 = 9 分钟。锁定次数:29 + 31 + 10 = 70 。锁定频率:70 / 9 = 8,即每分钟 8 次。如图6
查看 命令行下的 Redis 锁定日志,不仅存在 判断Redis模型的锁定是否存在(已存在),而且存在 Redis模型的锁定实现时失败的 日志。时间:14:58 - 14:50 = 9 分钟。锁定次数:29 + 31 + 10 = 70 。锁定频率:70 / 9 = 8,即每分钟 8 次

图6



[root@7edbf272f55f logs]# ls -ltr
total 136
-rw-r--r-- 1 root root 16556 Mar  4 14:50 app.log
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-lock-2-1583304715.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304842.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304846.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304851.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304855.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304859.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304863.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304868.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304872.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304876.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304880.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304885.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304889.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304893.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304897.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304902.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304906.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304910.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304914.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304919.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304923.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304927.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305042.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305046.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305051.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305063.txt
-rw-r--r-- 1 root root     1 Mar  4 14:58 qq-cw-transaction-article-sync-is-lock-exist-2-1583305084.txt
-rw-r--r-- 1 root root     1 Mar  4 14:58 qq-cw-transaction-article-sync-is-lock-exist-2-1583305093.txt
-rw-r--r-- 1 root root     1 Mar  4 14:58 qq-cw-transaction-article-sync-is-lock-exist-2-1583305097.txt

[root@22f7ada2d88d logs]# ls -ltr
total 144
-rw-r--r-- 1 root root 16547 Mar  4 14:50 app.log
-rw-r--r-- 1 root root     1 Mar  4 14:50 qq-cw-transaction-article-sync-is-lock-exist-2-1583304652.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304665.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304669.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304673.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304677.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304682.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304686.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304690.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304694.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304699.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304703.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304707.txt
-rw-r--r-- 1 root root     1 Mar  4 14:51 qq-cw-transaction-article-sync-is-lock-exist-2-1583304711.txt
-rw-r--r-- 1 root root     1 Mar  4 14:52 qq-cw-transaction-article-sync-is-lock-exist-2-1583304720.txt
-rw-r--r-- 1 root root     1 Mar  4 14:53 qq-cw-transaction-article-sync-is-lock-exist-2-1583304816.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304846.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304851.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304855.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304859.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304864.txt
-rw-r--r-- 1 root root     1 Mar  4 14:54 qq-cw-transaction-article-sync-is-lock-exist-2-1583304868.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304949.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304954.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304958.txt
-rw-r--r-- 1 root root     1 Mar  4 14:56 qq-cw-transaction-article-sync-is-lock-exist-2-1583304962.txt
-rw-r--r-- 1 root root     1 Mar  4 14:56 qq-cw-transaction-article-sync-is-lock-exist-2-1583304988.txt
-rw-r--r-- 1 root root     1 Mar  4 14:56 qq-cw-transaction-article-sync-is-lock-exist-2-1583304996.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305055.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305060.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305072.txt
-rw-r--r-- 1 root root     1 Mar  4 14:58 qq-cw-transaction-article-sync-is-lock-exist-2-1583305081.txt

[root@54d07ee806e6 logs]# ls -ltr
total 60
-rw-r--r-- 1 root root 16556 Mar  4 14:53 app.log
-rw-r--r-- 1 root root     1 Mar  4 14:53 qq-cw-transaction-article-sync-is-lock-exist-2-1583304824.txt
-rw-r--r-- 1 root root     1 Mar  4 14:53 qq-cw-transaction-article-sync-is-lock-exist-2-1583304829.txt
-rw-r--r-- 1 root root     1 Mar  4 14:53 qq-cw-transaction-article-sync-is-lock-exist-2-1583304833.txt
-rw-r--r-- 1 root root     1 Mar  4 14:53 qq-cw-transaction-article-sync-is-lock-exist-2-1583304837.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304932.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304936.txt
-rw-r--r-- 1 root root     1 Mar  4 14:55 qq-cw-transaction-article-sync-is-lock-exist-2-1583304941.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305029.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305033.txt
-rw-r--r-- 1 root root     1 Mar  4 14:57 qq-cw-transaction-article-sync-is-lock-exist-2-1583305038.txt


7、锁定频率过高的根源应该在于命令行中仅有同一条任务在执行。决定模拟出 6 条任务,锁定频率应该会有所下降。时间:15:52 – 15:47 = 6 分钟。锁定次数:47 + 34 + 45 = 126 。锁定频率:126 / 6 = 9,即每分钟 20 次。不符合预期。


[root@7edbf272f55f logs]# ls -ltr
total 188
-rw-r--r-- 1 root root 1 Mar  4 15:47 qq-cw-transaction-video-sync-is-lock-exist-4-1583308059.txt
-rw-r--r-- 1 root root 1 Mar  4 15:47 qq-cw-transaction-video-sync-lock-5-1583308059.txt
-rw-r--r-- 1 root root 1 Mar  4 15:47 qq-cw-transaction-article-sync-is-lock-exist-6-1583308069.txt
-rw-r--r-- 1 root root 1 Mar  4 15:47 qq-cw-transaction-article-sync-is-lock-exist-8-1583308069.txt
-rw-r--r-- 1 root root 1 Mar  4 15:48 qq-cw-transaction-video-sync-is-lock-exist-9-1583308096.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-7-1583308161.txt
-rw-r--r-- 1 root root 2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-11-1583308161.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-6-1583308166.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-8-1583308166.txt
-rw-r--r-- 1 root root 2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-12-1583308167.txt
-rw-r--r-- 1 root root 2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-11-1583308187.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308222.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308222.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308222.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308226.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308226.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308226.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308230.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308230.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308230.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308230.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308234.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308234.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308239.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308239.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308239.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308239.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308243.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308243.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308243.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308247.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308248.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308251.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308256.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-11-1583308260.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-11-1583308264.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-11-1583308268.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-11-1583308272.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308277.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308281.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308290.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308294.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308303.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308307.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308311.txt
-rw-r--r-- 1 root root 2 Mar  4 15:52 qq-cw-transaction-article-sync-is-lock-exist-12-1583308332.txt
-rw-r--r-- 1 root root 2 Mar  4 15:52 qq-cw-transaction-article-sync-is-lock-exist-12-1583308344.txt

[root@22f7ada2d88d logs]# ls -ltr
total 136
-rw-r--r-- 1 root root 1 Mar  4 15:47 qq-cw-transaction-video-sync-is-lock-exist-5-1583308059.txt
-rw-r--r-- 1 root root 2 Mar  4 15:48 qq-cw-transaction-video-sync-is-lock-exist-10-1583308096.txt
-rw-r--r-- 1 root root 1 Mar  4 15:48 qq-cw-transaction-article-sync-is-lock-exist-8-1583308126.txt
-rw-r--r-- 1 root root 2 Mar  4 15:48 qq-cw-transaction-article-sync-is-lock-exist-11-1583308126.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-6-1583308177.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-6-1583308182.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-8-1583308183.txt
-rw-r--r-- 1 root root 2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-12-1583308183.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-6-1583308187.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-6-1583308196.txt
-rw-r--r-- 1 root root 1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-8-1583308196.txt
-rw-r--r-- 1 root root 2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-12-1583308196.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308206.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308222.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308222.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308226.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308226.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308226.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308230.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308230.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308230.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308235.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308235.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308235.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308239.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308239.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308239.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308243.txt
-rw-r--r-- 1 root root 1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308243.txt
-rw-r--r-- 1 root root 2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308244.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308286.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308298.txt
-rw-r--r-- 1 root root 2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308315.txt
-rw-r--r-- 1 root root 2 Mar  4 15:52 qq-cw-transaction-article-sync-is-lock-exist-12-1583308323.txt

[root@54d07ee806e6 logs]# ls -ltr
total 196
-rw-r--r-- 1 root root     1 Mar  4 15:47 qq-cw-transaction-video-sync-is-lock-exist-4-1583308059.txt
-rw-r--r-- 1 root root     1 Mar  4 15:47 qq-cw-transaction-article-sync-is-lock-exist-7-1583308069.txt
-rw-r--r-- 1 root root     1 Mar  4 15:48 qq-cw-transaction-article-sync-is-lock-exist-8-1583308087.txt
-rw-r--r-- 1 root root 17361 Mar  4 15:48 app.log
-rw-r--r-- 1 root root     2 Mar  4 15:48 qq-cw-transaction-video-sync-is-lock-exist-10-1583308096.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-6-1583308160.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-8-1583308161.txt
-rw-r--r-- 1 root root     2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-12-1583308161.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-7-1583308166.txt
-rw-r--r-- 1 root root     2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-11-1583308167.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-7-1583308177.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-7-1583308182.txt
-rw-r--r-- 1 root root     2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-11-1583308183.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-7-1583308187.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-8-1583308187.txt
-rw-r--r-- 1 root root     2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-12-1583308187.txt
-rw-r--r-- 1 root root     1 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-7-1583308196.txt
-rw-r--r-- 1 root root     2 Mar  4 15:49 qq-cw-transaction-article-sync-is-lock-exist-11-1583308196.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308205.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308206.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308226.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-lock-8-1583308226.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-7-1583308230.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308230.txt
-rw-r--r-- 1 root root     2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308230.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308234.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308235.txt
-rw-r--r-- 1 root root     2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308235.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308239.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308239.txt
-rw-r--r-- 1 root root     2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308239.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-6-1583308243.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-lock-7-1583308243.txt
-rw-r--r-- 1 root root     2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-11-1583308243.txt
-rw-r--r-- 1 root root     2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308243.txt
-rw-r--r-- 1 root root     1 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-8-1583308248.txt
-rw-r--r-- 1 root root     2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308251.txt
-rw-r--r-- 1 root root     2 Mar  4 15:50 qq-cw-transaction-article-sync-is-lock-exist-12-1583308256.txt
-rw-r--r-- 1 root root     2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308260.txt
-rw-r--r-- 1 root root     2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308264.txt
-rw-r--r-- 1 root root     2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308269.txt
-rw-r--r-- 1 root root     2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-12-1583308273.txt
-rw-r--r-- 1 root root     2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-11-1583308277.txt
-rw-r--r-- 1 root root     2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-11-1583308281.txt
-rw-r--r-- 1 root root     2 Mar  4 15:51 qq-cw-transaction-article-sync-is-lock-exist-11-1583308285.txt


8、决定调整 bash sleep 3 为 bash sleep 60,即命令行运行一次后,间隔 60 秒后再次运行,而不是 3 秒。重复第 7 步骤,不存在 Redis 锁定日志。符合预期。 9、基于同步时间、ID顺序排列(企鹅号的事务)。某一任务开始执行时,更新企鹅号的事务的同步时间为当前时间 ,以提升执行性能,且再次降低锁定频率。


        // 查询企鹅号的应用类型,cw:内容网站应用 && 类型,1:文章 && 状态,3:处理中 && 文章发布状态:审核中的5条记录,基于同步时间、ID顺序排列(企鹅号的事务)
        $qqTransactionArticleItems = QqTransaction::find()->where(['qq_app_type' => QqArticle::QQ_APP_TYPE_CW, 'type' => QqTransaction::TYPE_ARTICLE, 'article_pub_flag' => HttpQqApiTransaction::ARTICLE_PUB_FLAG_REVIEW])->isDeletedNo()->processing()->orderBy(['synced_at' => SORT_ASC, 'id' => SORT_ASC])->limit(5)->all();

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

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

                // 基于ID查找单个数据模型(企鹅号的事务)
                $qqTransactionArticleItem = QqTransaction::findOne($qqTransactionArticleItem->id);

                // 更新企鹅号的事务的同步时间
                $qqTransactionArticleItem->synced_at = time();
                $qqTransactionArticleItem->update();
                file_put_contents('/mcloud/www/channel-pub-api/console/runtime/logs/qq-cw-transaction-article-sync-' . $qqTransactionArticleItem->id . '-' . time() . '.txt', $qqTransactionArticleItem->id);
            }
        }


10、模拟出 3 条任务。事务ID为 41、42、43 的同步时间(分钟)分别为:9、9、8 。事务ID为 41、42、43 的同步次数分别为:22、20、17 。事务ID为 41、42、43 的同步频率分别为:9 * 60 / 22 = 25、9 * 60 / 20 = 27、8 * 60 / 17 = 28。得出平均值:(25 + 27 + 28) / 3 = 27。理论上的计算公式,进一法取整(事务数量 / 5) / 容器数量 * 60。符合预期。如图7
模拟出 3 条任务。事务ID为 41、42、43 的同步时间(分钟)分别为:9、9、8 。事务ID为 41、42、43 的同步次数分别为:22、20、17 。事务ID为 41、42、43 的同步频率分别为:9 * 60 / 22 = 25、9 * 60 / 20 = 27、8 * 60 / 17 = 28。得出平均值:(25 + 27 + 28) / 3 = 27。理论上的计算公式,进一法取整(事务数量 / 5) / 容器数量 * 60。符合预期

图7



[root@21325d776096 logs]# ls -ltr
total 64
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-41-1583399050.txt
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-43-1583399050.txt
-rw-r--r-- 1 root root 2 Mar  5 17:05 qq-cw-transaction-article-sync-41-1583399112.txt
-rw-r--r-- 1 root root 2 Mar  5 17:05 qq-cw-transaction-article-sync-42-1583399112.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-41-1583399173.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-42-1583399174.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-41-1583399235.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-42-1583399235.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-43-1583399236.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-41-1583399297.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-43-1583399298.txt
-rw-r--r-- 1 root root 2 Mar  5 17:09 qq-cw-transaction-article-sync-41-1583399359.txt
-rw-r--r-- 1 root root 2 Mar  5 17:09 qq-cw-transaction-article-sync-42-1583399359.txt
-rw-r--r-- 1 root root 2 Mar  5 17:10 qq-cw-transaction-article-sync-41-1583399421.txt
-rw-r--r-- 1 root root 2 Mar  5 17:10 qq-cw-transaction-article-sync-42-1583399421.txt
-rw-r--r-- 1 root root 2 Mar  5 17:11 qq-cw-transaction-article-sync-42-1583399483.txt

[root@e9565f71310d logs]# ls -ltr
total 100
-rw-r--r-- 1 root root 2 Mar  5 17:03 qq-cw-transaction-article-sync-41-1583399012.txt
-rw-r--r-- 1 root root 2 Mar  5 17:03 qq-cw-transaction-article-sync-42-1583399013.txt
-rw-r--r-- 1 root root 2 Mar  5 17:03 qq-cw-transaction-article-sync-43-1583399013.txt
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-41-1583399074.txt
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-42-1583399075.txt
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-43-1583399076.txt
-rw-r--r-- 1 root root 2 Mar  5 17:05 qq-cw-transaction-article-sync-41-1583399137.txt
-rw-r--r-- 1 root root 2 Mar  5 17:05 qq-cw-transaction-article-sync-42-1583399138.txt
-rw-r--r-- 1 root root 2 Mar  5 17:05 qq-cw-transaction-article-sync-43-1583399139.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-41-1583399200.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-42-1583399201.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-43-1583399201.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-41-1583399263.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-42-1583399264.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-43-1583399264.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-41-1583399325.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-42-1583399326.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-43-1583399326.txt
-rw-r--r-- 1 root root 2 Mar  5 17:09 qq-cw-transaction-article-sync-42-1583399388.txt
-rw-r--r-- 1 root root 2 Mar  5 17:09 qq-cw-transaction-article-sync-43-1583399388.txt
-rw-r--r-- 1 root root 2 Mar  5 17:09 qq-cw-transaction-article-sync-41-1583399388.txt
-rw-r--r-- 1 root root 2 Mar  5 17:10 qq-cw-transaction-article-sync-42-1583399450.txt
-rw-r--r-- 1 root root 2 Mar  5 17:10 qq-cw-transaction-article-sync-43-1583399451.txt
-rw-r--r-- 1 root root 2 Mar  5 17:10 qq-cw-transaction-article-sync-41-1583399451.txt
-rw-r--r-- 1 root root 2 Mar  5 17:11 qq-cw-transaction-article-sync-42-1583399512.txt

[root@7c7a4390ba3f logs]# ls -ltr
total 72
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-42-1583399050.txt
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-43-1583399050.txt
-rw-r--r-- 1 root root 2 Mar  5 17:04 qq-cw-transaction-article-sync-41-1583399050.txt
-rw-r--r-- 1 root root 2 Mar  5 17:05 qq-cw-transaction-article-sync-43-1583399112.txt
-rw-r--r-- 1 root root 2 Mar  5 17:05 qq-cw-transaction-article-sync-41-1583399112.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-43-1583399174.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-41-1583399174.txt
-rw-r--r-- 1 root root 2 Mar  5 17:06 qq-cw-transaction-article-sync-42-1583399174.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-41-1583399236.txt
-rw-r--r-- 1 root root 2 Mar  5 17:07 qq-cw-transaction-article-sync-42-1583399236.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-42-1583399298.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-43-1583399298.txt
-rw-r--r-- 1 root root 2 Mar  5 17:08 qq-cw-transaction-article-sync-41-1583399298.txt
-rw-r--r-- 1 root root 2 Mar  5 17:09 qq-cw-transaction-article-sync-43-1583399360.txt
-rw-r--r-- 1 root root 2 Mar  5 17:09 qq-cw-transaction-article-sync-41-1583399360.txt
-rw-r--r-- 1 root root 2 Mar  5 17:10 qq-cw-transaction-article-sync-43-1583399421.txt
-rw-r--r-- 1 root root 2 Mar  5 17:10 qq-cw-transaction-article-sync-41-1583399422.txt
-rw-r--r-- 1 root root 2 Mar  5 17:11 qq-cw-transaction-article-sync-42-1583399483.txt


11、模拟出 5 条任务。事务ID为 47、48、49、52、53 的同步时间(分钟)分别为:4、11、4、5、5 。事务ID为 47、48、49、52、53 的同步次数分别为:10、20、10、9、10 。事务ID为 47、48、49、52、53 的同步频率分别为:4 * 60 / 10 = 24、11 * 60 / 20 = 33、4 * 60 / 10 = 24、5 * 60 / 9 = 33、5 * 60 / 10 = 30。得出平均值:(24 + 33 + 24 + 33 + 30) / 5 = 29。理论上的计算公式,进一法取整(事务数量 / 5) / 容器数量 * 60。符合预期。如图8
模拟出 5 条任务。事务ID为 47、48、49、52、53 的同步时间(分钟)分别为:4、11、4、5、5 。事务ID为 47、48、49、52、53 的同步次数分别为:10、20、10、9、10 。事务ID为 47、48、49、52、53 的同步频率分别为:4 * 60 / 10 = 24、11 * 60 / 20 = 33、4 * 60 / 10 = 24、5 * 60 / 9 = 33、5 * 60 / 10 = 30。得出平均值:(24 + 33 + 24 + 33 + 30) / 5 = 29。理论上的计算公式,进一法取整(事务数量 / 5) / 容器数量 * 60。符合预期

图8



[root@21325d776096 logs]# ls -ltr
total 88
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-47-1583459743.txt
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-48-1583459744.txt
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-49-1583459744.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-52-1583459806.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-47-1583459807.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-48-1583459807.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-49-1583459807.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-53-1583459807.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-47-1583459869.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-48-1583459869.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-52-1583459870.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-49-1583459870.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-53-1583459870.txt
-rw-r--r-- 1 root root 2 Mar  6 09:58 qq-cw-transaction-article-sync-48-1583459932.txt
-rw-r--r-- 1 root root 2 Mar  6 09:58 qq-cw-transaction-article-sync-52-1583459932.txt
-rw-r--r-- 1 root root 2 Mar  6 09:58 qq-cw-transaction-article-sync-53-1583459932.txt
-rw-r--r-- 1 root root 2 Mar  6 09:59 qq-cw-transaction-article-sync-48-1583459994.txt
-rw-r--r-- 1 root root 2 Mar  6 10:00 qq-cw-transaction-article-sync-48-1583460055.txt
-rw-r--r-- 1 root root 2 Mar  6 10:01 qq-cw-transaction-article-sync-48-1583460117.txt
-rw-r--r-- 1 root root 2 Mar  6 10:02 qq-cw-transaction-article-sync-48-1583460179.txt
-rw-r--r-- 1 root root 2 Mar  6 10:04 qq-cw-transaction-article-sync-48-1583460240.txt
-rw-r--r-- 1 root root 2 Mar  6 10:05 qq-cw-transaction-article-sync-48-1583460302.txt

[root@e9565f71310d logs]# ls -ltr
total 96
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-52-1583459751.txt
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-53-1583459752.txt
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-47-1583459752.txt
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-48-1583459752.txt
-rw-r--r-- 1 root root 2 Mar  6 09:55 qq-cw-transaction-article-sync-49-1583459752.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-52-1583459813.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-47-1583459813.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-48-1583459814.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-49-1583459814.txt
-rw-r--r-- 1 root root 2 Mar  6 09:56 qq-cw-transaction-article-sync-53-1583459814.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-47-1583459876.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-48-1583459877.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-49-1583459877.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-52-1583459878.txt
-rw-r--r-- 1 root root 2 Mar  6 09:57 qq-cw-transaction-article-sync-53-1583459878.txt
-rw-r--r-- 1 root root 2 Mar  6 09:59 qq-cw-transaction-article-sync-48-1583459940.txt
-rw-r--r-- 1 root root 2 Mar  6 09:59 qq-cw-transaction-article-sync-52-1583459940.txt
-rw-r--r-- 1 root root 2 Mar  6 09:59 qq-cw-transaction-article-sync-53-1583459941.txt
-rw-r--r-- 1 root root 2 Mar  6 10:00 qq-cw-transaction-article-sync-48-1583460002.txt
-rw-r--r-- 1 root root 2 Mar  6 10:01 qq-cw-transaction-article-sync-48-1583460063.txt
-rw-r--r-- 1 root root 2 Mar  6 10:02 qq-cw-transaction-article-sync-48-1583460125.txt
-rw-r--r-- 1 root root 2 Mar  6 10:03 qq-cw-transaction-article-sync-48-1583460187.txt
-rw-r--r-- 1 root root 2 Mar  6 10:04 qq-cw-transaction-article-sync-48-1583460248.txt
-rw-r--r-- 1 root root 2 Mar  6 10:05 qq-cw-transaction-article-sync-48-1583460310.txt

[root@7c7a4390ba3f logs]# ls -ltr
total 120
-rw-r--r-- 1 root root 17367 Mar  6 09:55 app.log
-rw-r--r-- 1 root root     2 Mar  6 09:55 qq-cw-transaction-article-sync-47-1583459731.txt
-rw-r--r-- 1 root root     2 Mar  6 09:55 qq-cw-transaction-article-sync-48-1583459732.txt
-rw-r--r-- 1 root root     2 Mar  6 09:55 qq-cw-transaction-article-sync-49-1583459732.txt
-rw-r--r-- 1 root root     2 Mar  6 09:56 qq-cw-transaction-article-sync-52-1583459793.txt
-rw-r--r-- 1 root root     2 Mar  6 09:56 qq-cw-transaction-article-sync-47-1583459794.txt
-rw-r--r-- 1 root root     2 Mar  6 09:56 qq-cw-transaction-article-sync-48-1583459794.txt
-rw-r--r-- 1 root root     2 Mar  6 09:56 qq-cw-transaction-article-sync-49-1583459794.txt
-rw-r--r-- 1 root root     2 Mar  6 09:56 qq-cw-transaction-article-sync-53-1583459795.txt
-rw-r--r-- 1 root root     2 Mar  6 09:57 qq-cw-transaction-article-sync-47-1583459856.txt
-rw-r--r-- 1 root root     2 Mar  6 09:57 qq-cw-transaction-article-sync-52-1583459857.txt
-rw-r--r-- 1 root root     2 Mar  6 09:57 qq-cw-transaction-article-sync-48-1583459857.txt
-rw-r--r-- 1 root root     2 Mar  6 09:57 qq-cw-transaction-article-sync-49-1583459858.txt
-rw-r--r-- 1 root root     2 Mar  6 09:57 qq-cw-transaction-article-sync-53-1583459859.txt
-rw-r--r-- 1 root root     2 Mar  6 09:58 qq-cw-transaction-article-sync-47-1583459920.txt
-rw-r--r-- 1 root root     2 Mar  6 09:58 qq-cw-transaction-article-sync-48-1583459921.txt
-rw-r--r-- 1 root root     2 Mar  6 09:58 qq-cw-transaction-article-sync-49-1583459922.txt
-rw-r--r-- 1 root root     2 Mar  6 09:58 qq-cw-transaction-article-sync-52-1583459922.txt
-rw-r--r-- 1 root root     2 Mar  6 09:58 qq-cw-transaction-article-sync-53-1583459922.txt
-rw-r--r-- 1 root root     2 Mar  6 09:59 qq-cw-transaction-article-sync-48-1583459984.txt
-rw-r--r-- 1 root root     2 Mar  6 10:00 qq-cw-transaction-article-sync-48-1583460045.txt
-rw-r--r-- 1 root root     2 Mar  6 10:01 qq-cw-transaction-article-sync-48-1583460107.txt
-rw-r--r-- 1 root root     2 Mar  6 10:02 qq-cw-transaction-article-sync-48-1583460169.txt
-rw-r--r-- 1 root root     2 Mar  6 10:03 qq-cw-transaction-article-sync-48-1583460231.txt
-rw-r--r-- 1 root root     2 Mar  6 10:04 qq-cw-transaction-article-sync-48-1583460293.txt
-rw-r--r-- 1 root root     2 Mar  6 10:05 qq-cw-transaction-article-sync-48-1583460355.txt


12、模拟出 8 条任务。事务ID为 57、58、59、63、64、65、68、69 的同步时间(分钟)分别为:11、12、13、12、11、12、11、12 。事务ID为 57、58、59、63、64、65、68、69 的同步次数分别为:21、22、22、19、17、5、17、17 。事务ID为 57、58、59、63、64、65、68、69 的同步频率分别为:11 * 60 / 21 = 31、12 * 60 / 22 = 33、13 * 60 / 22 = 35、12 * 60 / 19 = 38、11 * 60 / 17 = 39、12 * 60 / 5 = 144、11 * 60 / 17 = 39、12 * 60 / 17 = 42。得出平均值:(31 + 33 + 35 + 38 + 39 + 144 + 39 + 42) / 8 = 50。理论上的计算公式,进一法取整(事务数量 / 5) / 容器数量 * 60。符合预期。如图9
模拟出 8 条任务。事务ID为 57、58、59、63、64、65、68、69 的同步时间(分钟)分别为:11、12、13、12、11、12、11、12 。事务ID为 57、58、59、63、64、65、68、69 的同步次数分别为:21、22、22、19、17、5、17、17 。事务ID为 57、58、59、63、64、65、68、69 的同步频率分别为:11 * 60 / 21 = 31、12 * 60 / 22 = 33、13 * 60 / 22 = 35、12 * 60 / 19 = 38、11 * 60 / 17 = 39、12 * 60 / 5 = 144、11 * 60 / 17 = 39、12 * 60 / 17 = 42。得出平均值:(31 + 33 + 35 + 38 + 39 + 144 + 39 + 42) / 8 = 50。理论上的计算公式,进一法取整(事务数量 / 5) / 容器数量 * 60。符合预期

图9



[root@21325d776096 logs]# ls -ltr
total 220
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-57-1583461834.txt
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-58-1583461834.txt
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-59-1583461834.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-68-1583461896.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-69-1583461896.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-57-1583461897.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-58-1583461897.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-63-1583461959.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-64-1583461959.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-68-1583461959.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-69-1583461959.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-58-1583462021.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-59-1583462022.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-63-1583462022.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-57-1583462022.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-68-1583462084.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-58-1583462084.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-69-1583462085.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-59-1583462085.txt
-rw-r--r-- 1 root root 34566 Mar  6 10:35 app.log
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-63-1583462146.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-64-1583462147.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-68-1583462147.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-57-1583462147.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-59-1583462209.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-63-1583462209.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-69-1583462210.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-57-1583462210.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-64-1583462271.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-68-1583462272.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-59-1583462272.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-57-1583462272.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-63-1583462334.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-64-1583462334.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-69-1583462335.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-57-1583462335.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-59-1583462396.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-63-1583462397.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-68-1583462397.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-57-1583462397.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-59-1583462459.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-64-1583462459.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-69-1583462460.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-65-1583462460.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-58-1583462460.txt
-rw-r--r-- 1 root root     2 Mar  6 10:42 qq-cw-transaction-article-sync-69-1583462522.txt
-rw-r--r-- 1 root root     2 Mar  6 10:42 qq-cw-transaction-article-sync-59-1583462523.txt

[root@e9565f71310d logs]# ls -ltr
total 212
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-63-1583461842.txt
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-64-1583461842.txt
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-65-1583461843.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-59-1583461905.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-63-1583461905.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-64-1583461906.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-68-1583461906.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-57-1583461968.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-58-1583461968.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-59-1583461969.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-63-1583461969.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-64-1583462030.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-68-1583462031.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-69-1583462032.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-58-1583462032.txt
-rw-r--r-- 1 root root 33937 Mar  6 10:34 app.log
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-57-1583462093.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-63-1583462094.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-64-1583462094.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-68-1583462094.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-58-1583462155.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-59-1583462156.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-69-1583462156.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-63-1583462156.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-58-1583462218.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-64-1583462218.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-68-1583462218.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-59-1583462219.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-58-1583462280.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-63-1583462281.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-69-1583462281.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-64-1583462281.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-58-1583462343.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-59-1583462344.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-68-1583462344.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-63-1583462344.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-58-1583462406.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-64-1583462406.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-69-1583462406.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-59-1583462406.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-63-1583462468.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-68-1583462468.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-59-1583462469.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-58-1583462469.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-65-1583462470.txt

[root@7c7a4390ba3f logs]# ls -ltr
total 220
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-57-1583461824.txt
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-58-1583461825.txt
-rw-r--r-- 1 root root     2 Mar  6 10:30 qq-cw-transaction-article-sync-59-1583461825.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-57-1583461886.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-58-1583461886.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-59-1583461886.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-63-1583461887.txt
-rw-r--r-- 1 root root     2 Mar  6 10:31 qq-cw-transaction-article-sync-64-1583461887.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-69-1583461948.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-57-1583461949.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-58-1583461949.txt
-rw-r--r-- 1 root root     2 Mar  6 10:32 qq-cw-transaction-article-sync-59-1583461949.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-64-1583462011.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-68-1583462011.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-69-1583462011.txt
-rw-r--r-- 1 root root     2 Mar  6 10:33 qq-cw-transaction-article-sync-57-1583462011.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-59-1583462073.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-57-1583462074.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-63-1583462074.txt
-rw-r--r-- 1 root root     2 Mar  6 10:34 qq-cw-transaction-article-sync-64-1583462074.txt
-rw-r--r-- 1 root root 17200 Mar  6 10:35 app.log
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-58-1583462136.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-59-1583462136.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-69-1583462136.txt
-rw-r--r-- 1 root root     2 Mar  6 10:35 qq-cw-transaction-article-sync-57-1583462136.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-57-1583462198.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-64-1583462199.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-68-1583462199.txt
-rw-r--r-- 1 root root     2 Mar  6 10:36 qq-cw-transaction-article-sync-58-1583462199.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-63-1583462261.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-57-1583462261.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-69-1583462261.txt
-rw-r--r-- 1 root root     2 Mar  6 10:37 qq-cw-transaction-article-sync-58-1583462261.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-57-1583462323.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-59-1583462324.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-68-1583462324.txt
-rw-r--r-- 1 root root     2 Mar  6 10:38 qq-cw-transaction-article-sync-58-1583462324.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-64-1583462386.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-57-1583462386.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-69-1583462386.txt
-rw-r--r-- 1 root root     2 Mar  6 10:39 qq-cw-transaction-article-sync-58-1583462386.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-65-1583462448.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-57-1583462448.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-63-1583462449.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-68-1583462449.txt
-rw-r--r-- 1 root root     2 Mar  6 10:40 qq-cw-transaction-article-sync-58-1583462449.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-69-1583462511.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-63-1583462511.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-68-1583462512.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-59-1583462512.txt
-rw-r--r-- 1 root root     2 Mar  6 10:41 qq-cw-transaction-article-sync-65-1583462512.txt


 ]]>
https://www.shuijingwanwq.com/2020/03/09/3998/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 浏览量: 70 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
在 Yii2.0 下实现 Redis 的锁定机制的流程 https://www.shuijingwanwq.com/2017/01/08/1505/ https://www.shuijingwanwq.com/2017/01/08/1505/#respond Sun, 08 Jan 2017 11:04:00 +0000 http://www.shuijingwanwq.com/?p=1505 浏览量: 131 1、设置锁定的过期时间:当前的 Unix 时间戳 + Redis锁定超时时间,单位为秒(3),编辑文件:\common\config\params.php,如图1


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


设置锁定的过期时间:当前的 Unix 时间戳 + Redis锁定超时时间,单位为秒(3)

图1

2、获取相关的设置参数,编辑文件:\api\models\redis\GameCategory.php,如图2


            // 设置锁定的过期时间
            $time = time();
            $lockKey = Yii::$app->params['lock']['keyPrefix'] . 'game_category';
            $lockExpire = $time + Yii::$app->params['lock']['timeOut'];


获取相关的设置参数

图2

3、获取 Redis 连接,以执行相关命令,编辑文件:\api\models\redis\GameCategory.php,如图3


            // 获取 Redis 连接,以执行相关命令
            $redis = Yii::$app->redis;


获取 Redis 连接,以执行相关命令

图3

4、获取锁定,如图4


            // 获取锁定
            $executeCommandResult = $redis->setnx($lockKey, $lockExpire);


注:SETNX key value 将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。
获取锁定

图4

5、返回0,表示已经被其他客户端锁定,如图5、6、7


            // 返回0,表示已经被其他客户端锁定
            if ($executeCommandResult == 0) {
                // $fileName = microtime(true);
                // file_put_contents('./../runtime/0-' . $fileName . '.txt', '1');
                // 防止死锁,获取当前锁的过期时间
                $lockCurrentExpire = $redis->get($lockKey);
                // $fileName = microtime(true);
                // file_put_contents('./../runtime/6-' . $fileName . $lockCurrentExpire . '.txt', '1');
                // 判断锁是否过期,如果已经过期
                if ($time > $lockCurrentExpire) {
                    // $fileName = microtime(true);
                    // file_put_contents('./../runtime/1-' . $fileName . '.txt', '1');
                    // 释放锁定
                    // $redis->del($lockKey);
                    // 获取锁定
                    // $executeCommandResult = $redis->setnx($lockKey, $lockExpire);
                    // 防止并发锁定,检查存储在 key 的旧值是否仍然是过期的时间戳,如果是,则获取锁定,否则返回假
                    $executeCommandResult = $redis->getset($lockKey, $lockExpire);
                    if ($lockCurrentExpire != $executeCommandResult) {
                        // $fileName = microtime(true);
                        // file_put_contents('./../runtime/2-' . $fileName . '.txt', '1');
                        return ['status' => false, 'code' => 0, 'message' => ''];
                    }
                    // $fileName = microtime(true);
                    // file_put_contents('./../runtime/3-' . $fileName . '.txt', '1');
                }
                
                // 返回0,表示已经被其他客户端锁定,且不存在死锁,返回假
                if ($executeCommandResult == 0) {
                    // $fileName = microtime(true);
                    // file_put_contents('./../runtime/4-' . $fileName . '.txt', '1');
                    return ['status' => false, 'code' => 0, 'message' => ''];
                }

            }
            
            // $fileName = microtime(true);
            // file_put_contents('./../runtime/5-' . $fileName . '.txt', '1');


返回0,表示已经被其他客户端锁定

图5

返回0,表示已经被其他客户端锁定

图6

返回0,表示已经被其他客户端锁定

图7

6、释放锁定,如图8 注:DEL key [key …] 如果删除的key不存在,则直接忽略。
释放锁定

图8

7、对于在第5点,判断锁是否过期,如果已经过期,注释掉释放锁定与获取锁定,为了防止并发锁定,可做以下测试流程以验证 8、先注释掉释放锁定,以模拟:如果客户端失败,崩溃或者无法释放锁,会发生什么?的问题,如图9
先注释掉释放锁定,以模拟:如果客户端失败,崩溃或者无法释放锁,会发生什么?的问题

图9

9、在判断锁是否过期,如果已经过期,这处代码段中,采用第一种算法,且将file_put_contents全部取消注释,如图10
在判断锁是否过期,如果已经过期,这处代码段中,采用第一种算法,且将file_put_contents全部取消注释

图10

10、在Redis中,执行命令:FLUSHDB,清空所有key,如图11
在Redis中,执行命令:FLUSHDB,清空所有key

图11

11、执行并发请求测试,设置线程数为10,如图12、13
执行并发请求测试,设置线程数为10

图12

执行并发请求测试,设置线程数为10

图13

12、查看\api\runtime目录下所生成的文件,以0、4、6开头的文件数量皆为9,以5开头的文件数量为1,正常,如图14
查看\api\runtime目录下所生成的文件,以0、4、6开头的文件数量皆为9,以5开头的文件数量为1,正常

图14

13、在Redis中,删除除了lock:game_category外的所有业务相关key,即以game_category开头的key,以模拟:锁定已经过期,如图15
在Redis中,删除除了lock:game_category外的所有业务相关key,即以game_category开头的key,以模拟:锁定已经过期

图15

14、删除\api\runtime目录下所生成的文件,如图16
删除\api\runtime目录下所生成的文件

图16

15、执行并发请求测试,设置线程数为10,如图17、18
执行并发请求测试,设置线程数为10

图17

执行并发请求测试,设置线程数为10

图18

16、查看\api\runtime目录下所生成的文件,以0、6开头的文件数量为10,以1、4开头的文件数量皆为8,以5开头的文件数量为2,如图19 注1:以5开头的文件数量为2,只能够为1,大于1的话,表示锁定未成功 注2:判断锁是否过期,如果已经过期,当这种情况发生时,不能只是调用DEL来释放锁,然后基于SETNX获取锁定,因为这里有一个竞争关系,当多个客户端检测到一个过期的锁,并均释放锁,然后获取,则都获得了锁定。
查看\api\runtime目录下所生成的文件,以0、6开头的文件数量为10,以1、4开头的文件数量皆为8,以5开头的文件数量为2

图19

17、在判断锁是否过期,如果已经过期,这处代码段中,采用第二种算法,且将file_put_contents全部取消注释,如图20
在判断锁是否过期,如果已经过期,这处代码段中,采用第二种算法,且将file_put_contents全部取消注释

图20

18、重复第13、14、15等3个步骤,查看\api\runtime目录下所生成的文件,以0、6开头的文件数量为10,以1开头的文件数量为7,以2开头的文件数量为6,以3开头的文件数量为1,以4开头的文件数量为3,以5开头的文件数量为1,正常,如图21 注1:以1开头的文件数量为7,以2开头的文件数量为6,以3开头的文件数量为1,后两者相加正好等于前者,表示在已经过期的7个线程中,只有一个获得了锁定,最终以5开头的文件数量也为1 注2:由于GETSET的特性,可以检查存储在 key 的旧值是否仍然是过期的时间戳,如果是,则获取锁,否则返回假
重复第13、14、15等3个步骤,查看\api\runtime目录下所生成的文件,以0、6开头的文件数量为10,以1开头的文件数量为7,以2开头的文件数量为6,以3开头的文件数量为1,以4开头的文件数量为3,以5开头的文件数量为1,正常

图21

19、清理掉所有方便于开发期间测试的代码,如file_put_contents等,如图22


            // 设置锁定的过期时间,获取相关锁定参数
            $time = time();
            $lockKey = Yii::$app->params['lock']['keyPrefix'] . 'game_category';
            $lockExpire = $time + Yii::$app->params['lock']['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 ['status' => false, 'code' => 0, 'message' => ''];
                    }
                }
                
                // 返回0,表示已经被其他客户端锁定,且不存在死锁,返回假
                if ($executeCommandResult == 0) {
                    return ['status' => false, 'code' => 0, 'message' => ''];
                }

            }


清理掉所有方便于开发期间测试的代码,如file_put_contents等

图22

20、将获取锁定与释放锁定抽象为一个类文件,\common\models\redis\Lock.php,如图23、24  
<pre class="wp-block-syntaxhighlighter-code">

<?php namespace common\models\redis; use Yii; /** * This is the model class for table "{{%lock}}". * */ class Lock extends \yii\redis\ActiveRecord { /** * Redis模型的锁定实现 * @param string $lockKeyName 锁定键名 * 格式如下: * * 'game_category' //锁定键名,如比赛分类 * * @return integer 成功返回对象数组/失败返回错误信息 * 格式如下: * * [ * 'status' => true //状态
     * ]
     * 
     * 或者
     * 
     * [
     *     'status' => false, //状态
     *     'code' => 0, //返回码
     *     'message' => '', //说明
     * ]
     *
     */
    public function lock($lockKeyName)
    {
        // 设置锁定的过期时间,获取相关锁定参数
        $time = time();
        $lockKey = Yii::$app->params['lock']['keyPrefix'] . $lockKeyName;
        $lockExpire = $time + Yii::$app->params['lock']['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 ['status' => false, 'code' => 0, 'message' => ''];
                }
            }

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

        }
    }

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

</pre>
将获取锁定与释放锁定抽象为一个类文件,\common\models\redis\Lock.php

图23

将获取锁定与释放锁定抽象为一个类文件,\common\models\redis\Lock.php

图24

21、编辑文件:\api\models\redis\GameCategory.php,如图25、26、27  


            /* Redis模型的锁定实现 */
            $lockKeyName = 'game_category';
            $lock = new Lock();
            $lockResult = $lock->lock($lockKeyName);
            // 返回 false,表示已经被其他客户端锁定
            if ($lockResult['status'] === false) {
                return ['status' => false, 'code' => 0, 'message' => ''];
            }


编辑文件:\api\models\redis\GameCategory.php

图25

编辑文件:\api\models\redis\GameCategory.php

图25

编辑文件:\api\models\redis\GameCategory.php

图27

]]>
https://www.shuijingwanwq.com/2017/01/08/1505/feed/ 0