GH-3690: RedisLockRegistry improvement, using pub-sub for lock to wit…#3691
GH-3690: RedisLockRegistry improvement, using pub-sub for lock to wit…#3691Meteorkor wants to merge 3 commits into
Conversation
…for lock to without spinlock
artembilan
left a comment
There was a problem hiding this comment.
Pulling this locally for a closer look...
Thanks
artembilan
left a comment
There was a problem hiding this comment.
I didn't do a deep investigation and comparison, but it doesn't look like we get too much difference in what is there just with a plain sleep_and_repeat and this distributed subscription from all the instances. I see there is a SpinBarrier.waitFor() in the RedisMessageListenerContainer. Probably subscription by itself is not so hard, but still: we got a lot of them from every single client... Plus in the end we are blocked on the LockSupport.parkNanos() for the Future.get(). I don't mind how complex the solution is, but worry if it is optimal enough to justify the maintenance burden in the future...
Would you mind to share more details what is your expectation from this fix and how it supposed to work? (See my comments on lines anyway)
I respect your work and appreciate the effort though.
Thank you!
| if (!RedisLockRegistry.this.redisMessageListenerContainer.isRunning()) { | ||
| RedisLockRegistry.this.redisMessageListenerContainer.start(); | ||
| } | ||
| final Future future = RedisLockRegistry.this.unlockNotifyMessageListener.unlockWait(this.lockKey); |
There was a problem hiding this comment.
I wonder what is the difference between that Thread.sleep(); until expire and this Future.get().
Feels like not too much difference in the final logic.
Only the problem that Thread.sleep(); is a local function while that listener requires an additional subscription into Redis. Therefore it is more resource consuming.
What do I miss?
And what happened with UNLINK: https://redis.io/commands/UNLINK ?
I understand that we may move to it altogether since Redis 4.0.0 is old enough to worry about more older versions in support, but still that would be slightly breaking change in the current 5.5.x.
Although we may just fully treat your change as a new feature and move it to the next major version.
Just need to understand what a benefit we would gain from your solution?
There was a problem hiding this comment.
I think the difference between Thread.sleep() and future.get() is
I think there is a difference between purpose and frequency.
If the purpose of Thread.sleep() was to simply repeat
future.get() doesn't need to check repeatedly until unlock() is done.
However, since ttl exists, I set the time.
it must be able to wake up without unlocking
Usually we expect to use the default value (1min), so we don't need to retry in the meantime.
And if you perform unLock() faster than 100ms in another vm, you can acquire the lock faster than 100ms.
second,
I think the use for unlink should be considered.
That part is not reflected yet.
(If we use this solution, I will fix it.)
There was a problem hiding this comment.
you can acquire the lock faster than 100ms
That’s really convincing 😅. Only need to figure out what to do with dangling , not in demand unlock events you store in the cache…
probably time to look closer into that Redisson solution with pub-sub.
I’m not convinced yet that it is worth to manage one more optional dependency just for this lock solution… although they may have other useful features to consider in the future 😉
| } | ||
| } | ||
|
|
||
| private final class UnLockNotifyFuture extends FutureTask<Boolean> { |
There was a problem hiding this comment.
See org.springframework.util.concurrent.SettableListenableFuture - we don't need a dedicated custom type since one already exists.
| } | ||
|
|
||
| public Future unlockWait(String lockKey) { | ||
| return this.notifyMap.computeIfAbsent(lockKey, key -> new UnLockNotifyFuture()); |
There was a problem hiding this comment.
I believe there is a flaw in this solution: we may never be interested in many, many locks in the Redis, but only one.
So, just after requesting our unavailable lock we start listener container and subscribe to the global topic.
When ever other clients release their locks, they are going to appear in this local store and will not be removed.
Remember you have fixed recently a memory leak in this registry: #3655 ?
Another concern that all the subscribers are going to get the same just released lock notification....
|
Looks like you took some inspiration from the Redisson: https://github.com/redisson/redisson/blob/master/redisson/src/main/java/org/redisson/RedissonLock.java. I wonder if we still need to keep our own solution when we just can use their solution and avoid copy/pasting and time consuming for the task which is out of scope of this Messaging framework... |
|
I looked into redisson while looking for how to improve spinlock, but I applied it briefly because I think adding a dependency to the project has a big impact. If you don't apply a PR solution, it's ok to apply their solution. And if you think the maintenance cost is going to be high, it's okay to keep the existing code. It's just a suggestion to make a little bit more improvement. |
* change remove timing for 'notifyMap' * use unlink * remove DUMMY_CALLABLE
artembilan
left a comment
There was a problem hiding this comment.
I think now it looks good.
Pulling locally for final review and code clean up...
Thank you!
|
Merged as 3c32daa. thank you again for a great contribution; looking forward for more! |
|
@artembilan Thank you for code clean up However, there seems to be one change in the code. It seems that the deleteUnlock code should be outside the catch. If It seems there are various ways.
|
|
You are right! Have just pushed the fix: 2d75289 Thank you again! 😄 |
GH-3690
Replace spinLock with pub-sub
use
redisMessageListenerContaineras-is
if lock accquire fail, will repeat retry to accuire lock per 100ms
to-be
if lock accquire fail, waitLock regist(unlock msg subscribe)
and retry to accuire lock, because timing issue(if other vm unlock, before waitLock regist)
and wait unlock