upyun / lua-resty-sync

Synchronizing data based on version changes
41 stars 6 forks source link

sync timer 唯一性问题 #1

Open fankeke opened 6 years ago

fankeke commented 6 years ago

https://github.com/upyun/lua-resty-sync/blob/master/lib/resty/sync.lua#L324

if var and var ~= id then release_lock(lock) return true end

没有看具体逻辑,但感觉这里应该要释放lock(抢到后)

tokers commented 6 years ago

这里不加不会影响到功能,但是从代码严谨的角度来说是要加。欢迎提交 PR。

fankeke commented 6 years ago

@tokers 另外,感觉用lock来做唯一性的timer没有那么优雅,可以用worker id来做。 在初始化每个sync的时候,可以random(worker的数量)一个值赋值给sync,然后该sync就可以由这个random值出来的id所对应的worker来同步。这个也避免了LOCK_KEY和LOCK_TIME_KEY的使用。

(另外os.time的精度不够,连续起两个sync会导致同样的LOCK_TIME_KEY也是一个问题)

如果可行的话,我提个PR

tokers commented 6 years ago

@fankeke 你的方案,如果持有 sync timer 的 worker 进程 crash 了,怎么保证新起来的 worker 可以重新创建出 sync timer 来?

fankeke commented 6 years ago

新的worker再走一遍init_worker阶段 ,也能够创建。

function _M.new(...) ..... local sync = { .... worker_id = random(ngx.worker.count()) } ..... }

function _M.start(self) ..... local worker_id = self.worker_id if worker_id ~= ngx.worker.id() then return end ....... end

基本思想和上面一样

tokers commented 6 years ago

那这个 worker_id 应该要放到共享内存里。

tokers commented 6 years ago

从你的伪代码来看,持有 sync timer 的 worker 如果挂了,新起来一个 worker,也是随机一个 worker_id,进入到 start 函数,如果这个随机出来的 worker_id 不等于 ngx.worker.id() 的值的话,此时全局就没有 sync 定时器了。

fankeke commented 6 years ago

额,好像是的 。。。 思路有点问题。

tokers commented 6 years ago

@fankeke 不过 os.time 的问题,可能是会有精度不够的问题,可以在这个基础上结合一个随机数,哈哈

fankeke commented 6 years ago

嗯,我提个PR可以

fankeke commented 6 years ago

@tokers 当前的做法还是有些问题: 场景如下:如果有两个worker,2个sync,sync1和sync2 nginx启动时,其中sync1和sync2都被worker1持有, 然后worker2突然挂掉,然后重启执行init_worker阶段,然后继续创建sync1和sync2. (因为此时的lock_key和lock_time_key都已经不同了,而且没有其他worker进行抢锁,必然会创建所有的sync)

这个时候就会发生问题, 是不是这样?

tokers commented 6 years ago

@fankeke 是的。 目前 resty-sync 的做法能够完全避免掉这些问题。

fankeke commented 6 years ago

@tokers 目前的resty-sync如何避免我上面的场景的?

tokers commented 6 years ago

@fankeke 你可以仔细看下 _M.start 这个函数,我通过在共享内存里设置一个特殊的标记,即用 LOCK_TIMER_KEY 作为 key,存放成功启动 sync timer 的 worker 的 id,过期时间为这个 sync 实例所设置的 interval + 10,且每次定时器在处理回调的时候,都会更新下这个 key ,以避免过期。

如果某个 worker 进入到这个函数,先第一次抢锁,抢到锁的,首先会判断这个 LOCK_TIMER_KEY 对应的值是不是存在,如果不存在,则由这个 worker 抢占到 timer,同时共享内存里的 LOCK_TIMER_KEY 存放它的 id。

如果这个 LOCK_TIMER_KEY 对应的值已经是存在的,需要判断其值和当前 worker 自己的 id 是否一致,不一致则立刻返回,一致则接着往下走,抢占到 timer。

这样子,在有 worker 崩溃的时候,如果不是 timer owner 崩溃,则由于 LOCK_TIMER_KEY 对应的值和它本身的 ID 不同,是不会创建出 timer 来的,相反,如果是之前的 timer owner 奔溃,由于 worker id 只能是 [0, n - 1],新起来的 worker 必然和刚刚奔溃的 worker id 一致,此时它便可以重新创建出 timer 来,保证 resty.sync 工作正常。

fankeke commented 6 years ago

@tokers crash后的worker创建的 LOCK_TIMER_KEY 在变化,并不是不变的,这个你考虑了吗?

这是我起了4个worker,创建一个sync时的shdict的数据,我kill了三次worker后的数据: sync_timer_1513914468:1 sync_timer_1513914438:0 sync_timer_1513915510:2 _time_ex1:1513916022
_data_ex1:16 _version_ex1:version 10

此时3个worker都持有了这个sync的timer。

tokers commented 6 years ago

@fankeke 恩,这个 new 操作你要放到 init 阶段,可以放在 init 阶段再试试