[Redis] ๋ถ์ฐ ๋ฝ (Distributed Lock)
๋ฝ์ ๋์์ฑ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ์์คํ ์์ ๊ฐ์ฅ ์ค์ํ ๊ฒ ์ค ํ๋๋ค.
๋ณดํต์ ๊ด๊ณํ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฝ ๊ธฐ๋ฅ์ ํ์ฉํด์ ๋ฝ ์ฒ๋ฆฌ๋ฅผ ํ๊ณค ํ๋๋ฐ, ๊ทธ๊ฒ๋ง์ผ๋ก๋ ํ์ฅ์ฑ์ด๋ ์ฑ๋ฅ ๋ฉด์์ ๋ถ์กฑํ ๋๊ฐ ์๋ค.
์ ํ๋ฆฌ์ผ์ด์
์ฝ๋๋ฅผ ์ด์ฉํ Lock ์ฒ๋ฆฌ๋ ์ฑ๊ธ ์ฑ์์๋ ๊ฐ๋ฅํ์ง, ์ค์ผ์ผ์์๋ ๋ถ์ฐ ํ๊ฒฝ์์๋ ์ ํจํ์ง ์๋ค.
๊ทธ๋ด๋๋ Lock์ ์ง์ ๊ตฌํํด์ ์ฌ์ฉํ๊ณค ํ๋๋ฐ, ๋ณดํต Redis๋ฅผ ์ค์ ๋ฉ๋ชจ๋ฆฌ๋ก ๋๊ณ Lock ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํด์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
ํ ์คํธ ํ๊ฒฝ ๊ตฌ์ฑ
์ฐ์ redis๋ฅผ docker๋ก ๊ฐ๋จํ๊ฒ ๋์ ๋ค.
docker run -p 12345:6379 redis
๊ทธ๋ฆฌ๊ณ ์๋๋ Rust๋ก ์์ฑํ ์์ ์ฝ๋๋ค.
๊ฐ๋
์ค๋ช
์ ์ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๊ผญ ์ฝ๋ ์ ์ฒด๋ฅผ ๋ค ์ดํดํ ํ์๋ ์๋ค.
use futures::future::join_all;
use redis::Commands;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send>> {
let redis_client = redis::Client::open("redis://127.0.0.1:12345/").unwrap();
let mut redis_connection = redis_client.get_connection().unwrap();
let _: () = redis_connection.set("counter", 0).unwrap();
// 10000๋ฒ ๋ฐ๋ณตํฉ๋๋ค.
let _ = join_all((0..10000).into_iter().map(|_| {
let redis_client = redis_client.clone();
return tokio::spawn(async move {
let mut redis_connection = redis_client.get_connection().unwrap();
let old: i32 = redis_connection.get("counter").unwrap();
let new = old + 1;
println!("new: {}", new);
let _: () = redis_connection.set("counter", new).unwrap();
});
}))
.await;
let counter: i32 = redis_connection.get("counter").unwrap();
println!("counter: {}", counter);
Ok(())
}
์ฝ๋์ ๋ก์ง ์์ฒด๋ ๋งค์ฐ ๋จ์ํ๋ค.
redis์์ "counter"๋ผ๋ ํค์ ๋ฉํฐ์ค๋ ๋๋ก 10000๋ฒ ๋์์ ๋ฐ๋ณตํ๋ฉด์ ์ฆ๊ฐ๋ฅผ ์ํค๊ณ ์๋ค.
์๋ ๊ธฐ๋ํ๋๋ก๋ผ๋ฉด 10000๋ฒ ๋ฐ๋ณตํ์ผ๋ 10000์ด ๋์ด์ผ ํ์ง๋ง,
๊ฒฐ๊ณผ๋ ๊ทธ๋ ์ง ์๋ค.
get์ผ๋ก ๊ฐ์ ๊ฐ์ ธ์ค๊ณ , set์ผ๋ก ๋ค์ ์ค์ ํ๋ ๋ฏธ๋ฌํ ๊ฐ๊ทน์์ ๋์์ฑ ์ถฉ๋์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๋ฅผํ
๋ฉด counter๊ฐ 2์ธ ์ํ์์ ๋์์ 2๊ฐ์ ์ค๋ ๋๊ฐ "2" ๊ฐ์ ๋ฐ์์ค๋ฉด, 2->3, 3->4๋ก ๊ฐ์ด ๋์ด๋๋ ๊ฒ์ด ์๋๋ผ 2->3, 2->3 ์ ๊ฐ์ด ์ค๋ณตํ ๋น์ ํด๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๋ฌํ ์ํฉ์ ๋ง๊ธฐ ์ํด ์ฌ์ฉํ๋ ๊ฒ์ด Lock์ด๋ค.
์ฌ์ค ๊ทธ๋ฅ ๋์์ ์ ๊ทผ์ด ๋ถ๊ฐ๋ฅํ๋๋ก ๋ง์๋ฒ๋ฆฌ๋ ๊ฒ์ด๋ค.
setnx ๊ธฐ๋ฐ์ Spin Lock
Spin Lock์ Lock์ด ๋๊ธฐ์ค์ผ๋ ๋บ๊ธ๋บ๊ธ ๋๋ค๊ณ ํด์ ๋ถ๋ฅด๋ ๋ช ์นญ์ธ๋ฐ, ๊ตฌํ์ ์ด๋ ต์ง ์๋ค.
๋งค ํ๋ก์ธ์ค๋ง๋ค redis์ setnx ๋ช ๋ น์ผ๋ก lock ๊ฐ์ ์ ์ฅํ๊ณ , ์ด๋ฏธ lock์ด ์๋ค๋ฉด ๋๊ธฐํ๊ณ , ํ๋ก์ธ์ค๊ฐ ๋๋๋ฉด lock ๊ฐ์ ์ ๊ฑฐํ๊ฒ ํ๋ฉด ๋๋ค.
setnx๋ ๊ธฐ์กด์ ๊ฐ์ด ์กด์ฌํ๋ฉด 0์ ๋ฆฌํดํ๋ atomicํ ๋ช ๋ น์ด๋ผ, 1์ ๋ฐํํ ๋๊น์ง ๋ฌดํ๋ฃจํ๋ฅผ ๋๋ ค์ ๋๊ธฐ๋ฅผ ํ๊ฒ ํ๋ฉด ๋๋ค. ์ด๋ ๊ฒ ๋ง์ด๋ค.

๊ทธ๋ฆฌ๊ณ ๋๊ฐ๋๋ del๋ก ์ ๊ฑฐํด์ฃผ๋ฉด ๋๋ค.

์ด๋ฌ๋ฉด ์์ฃผ ๊ฐ๋จํ Spin Lock์ ์์ฑ์ด๋ค.

์. ์ด์ ๊ธฐ๋ํ๋๋ก ๋์ํ ๊ฒ์ด๋ค.

์ ๋์ํ๋ ๊ฒ ๊ฐ๋ค.
ํ์ง๋ง ์ฌ๊ธฐ์๋ ๋จ์ ์ด ์ข ์๋๋ฐ...
์ ํ๋ฆฌ์ผ์ด์
๋จ์์ ๋ฌดํ๋ฃจํ ๋๋ฉฐ ๊ธฐ๋ค๋ฆฌ๋๊ฑด ๊ทธ๋ ๋ค ์น๊ณ , setnx๋ฅผ ์์ฅ์ฐฝ ๋ ๋ ค๋๊ธฐ ๋๋ฌธ์ Redis์ ๊ฐํด์ง๋ ๋ถ๋ด์ด ์์ฒญ๋๋ค๋ ๊ฒ์ด๋ค.
์ด๊ฑธ ํด๊ฒฐํ๋ ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ๋จ์์ ๋ฌดํ๋ฃจํ๋ฅผ sleep๋ ๊ฑธ์ด๊ฐ๋ฉด์ ์ฒ์ฒํ ํ๋ ๋ฐฉ๋ฒ์ด ์๊ธด ํ๋ฐ, best practice๋ผ๊ณ ๋งํ๊ธฐ๋ ์ด๋ ต๋ค.
Redis pub/sub ๊ธฐ๋ฐ์ Lock ๊ตฌํ
Redis๋ ๋คํ์ค๋ฝ๊ฒ๋ pub/sub์ด๋ผ๋ ์ด๋ฒคํธ ๊ธฐ๋ฐ์ ์์คํ ์ ์ ๊ณตํ๋ค.
๊ทธ๋ฌ๋๊น, ์ด๊ฑธ๋ก lock์ ํ๋๋ง๋ค ์๋ฆผ์ ๋ณด๋ด์, ๊ทธ๋ ๋ค์ setnx๋ฅผ ์๋ํ๋๋ก ํ๋ฉด, ๋ฌด์์ ์์์ง๋ setnx์ ํ์๋ฅผ ์ค์ฌ์ Redis์ ๋ถ๋ด์ ์ค์ด๋ ๊ฒ์ด๋ค.
Lock ํด์ ๋ฅผ ํ ๋ Lock์ด ํ๋ ธ๋ค๊ณ ์๋ ค์ฃผ๊ณ

Lock ์ง์ ์ ํ ๋๋ ๋ฃจํ๋ฅผ ๋๋ ค๊ณ ํ ๋๋ง๋ค ์ pub/sub์ ๊ธฐ๋ฐ์ผ๋ก ๋ธ๋ญ์ ๊ฑธ์ด์ฃผ๋ฉด ๋๋ค.
์ด๋ฌ๋ฉด lock์ด ํด์ ๋ ๋๋ง setnx๋ก lock ์ง์
์ ์๋ํ ๊ฒ์ด๋ค.
๋์์ ์ด์ ๊ณผ ๊ฑฐ์ ๋์ผํ๊ณ ,
์ผ๋ฐ SpinLock์ Redis CPU๊ฐ ์ด์ ๋๋ก ๋จน๋๊ฒ

๊ฑฐ์ ์ ๋ฐ ์ ๋๋ก ์ค์ด๋ค์๋ค.
์ด ๋ก์ง์ ๊ผญ ๋ค ์ง์ ๊ตฌํํ ํ์๋ ์๊ณ , ๊ด์ฐฎ์ Redis ํด๋ผ์ด์ธํธ ๊ตฌํ์ฒด๊ฐ ์๋ค๋ฉด ๊ทธ๊ฑธ ์ฐ๋ฉด ๋๋ค.
๊ทธ๋ฐ๋ฐ ์ฌ๊ธฐ์๋ ๋ฌธ์ ๊ฐ ์๊ธด ์์ผ๋.. Redis๋ง์ ๋ถ์ฐํ๋ ๋ฉํฐ๋ ธ๋ Redis ํด๋ฌ์คํฐ์์๋ ๊ธฐ๋ํ๋๋ก ๋์ํ์ง ์์ ์ ์๋ค๋ ๊ฒ์ด๋ค. ๋ ธ๋๋ผ๋ฆฌ ๊ฐ์ด ๋ค๋ฅผ ์ ์์ผ๋๊น!
Redlock
Redlock์ ๊ฐ์ฅ ๊ถ์ฅ๋๋ ํํ์ Lock ์๊ณ ๋ฆฌ์ฆ์ด๋ค.
pub/sub ๊ธฐ๋ฐ์ Lock์ ๋ฉํฐ๋
ธ๋ Redis ํ๊ฒฝ๊น์ง ๊ณ ๋ คํ ๊ฒ์ด๋ค.
์์ธํ ๊ฒ์ ๊ณต์๋ฌธ์๋ฅผ ์ฐธ์กฐํ๋ฉด ์ข๋ค.
https://redis.io/docs/manual/patterns/distributed-locks/
๋ฉํฐ๋ ธ๋์์์ ์ถฉ๋์ ํผํ๊ธฐ ์ํด ์ด๋ฌํ ๋ก์ง์ ๋ฐ๋ฅธ๋ค.
-
ํ์ฌ ์๊ฐ์ ๋ฐ๋ฆฌ์ด ๋จ์๋ก ๊ฐ์ ธ์จ๋ค.
-
N๊ฐ์ ๋ ธ๋์์ ๋ชจ๋ ๋์ผํ ํค ์ด๋ฆ๊ณผ ๋ฌด์์ ๊ฐ์ผ๋ก ์์ฐจ์ ์ผ๋ก Lock์ ํ๋ํ๋ ค๊ณ ์๋ํ๋ค.
์ฌ๊ธฐ์ ๊ฐ ๋ ธ๋์ ์ ๊ธ์ ์ค์ ํ ๋ ํด๋ผ์ด์ธํธ๋ ์ด Lock ์๋ ํด์ ์๊ฐ๊ณผ ๋น๊ตํ์ฌ ์์ ํ์์์์ ์ฌ์ฉํ์ฌ Lock์ ํ๋ํ๋ค.
์๋ฅผ ๋ค์ด, ์๋ ํด์ ์๊ฐ์ด 10์ด์ธ ๊ฒฝ์ฐ ํ์์์์ ๋๋ต 5-50 ๋ฐ๋ฆฌ์ด ๋ฒ์์ผ ์ ์๋ค. ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ํด๋ผ์ด์ธํธ๋ ๋ค์ด๋ Redis ๋ ธ๋์ ํต์ ์ ์๋ํ๋ ๋์ ์ค๋ ์๊ฐ ๋์ ๋ธ๋ก๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ค.
๋ ธ๋๊ฐ ์ฌ์ฉ ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ ๊ฐ๋ฅํ ๋นจ๋ฆฌ ๋ค์ ๋ ธ๋์ ํต์ ์ ์๋ํด์ผ ํ๋ค. -
ํด๋ผ์ด์ธํธ๋ Lock์ ํ๋ํ๊ธฐ ์ํด ๋จ๊ณ 1์์ ์ป์ ํ์์คํฌํ๋ฅผ ํ์ฌ ์๊ฐ์์ ๋นผ์ด ๊ฒฝ๊ณผ๋ ์๊ฐ์ ๊ณ์ฐํ๋ค.
ํด๋ผ์ด์ธํธ๊ฐ ๋๋ค์์ ๋ ธ๋(์ ์ด๋ 3๊ฐ)์์ Lock์ ํ๋ํ๊ณ , Lock์ ํ๋ํ๋ ๋ฐ ๊ฑธ๋ฆฐ ์ด ์๊ฐ์ด Lock์ validity time๋ณด๋ค ์์ ๊ฒฝ์ฐ์๋ง Lock์ด ํ๋๋ ๊ฒ์ผ๋ก ๊ฐ์ฃผ๋๋ค. -
Lock์ด ํ๋๋ ๊ฒฝ์ฐ, validity time์ ๋จ๊ณ 3์์ ๊ณ์ฐ๋ ๊ฒฝ๊ณผ ์๊ฐ์ ์ด๊ธฐ validity time์์ ๋บ ๊ฐ์ผ๋ก ๊ฐ์ฃผ๋๋ค.
-
๋ง์ฝ ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ค ์ด์ ๋ก ์ธํด Lock์ ์ป์ง ๋ชปํ๋ค๋ฉด (N/2+1 ๊ฐ์ ์ธ์คํด์ค๋ฅผ ์ ๊ธ ์ ์์๊ฑฐ๋ ์ ํจ ์๊ฐ์ด ์์์ธ ๊ฒฝ์ฐ), ํด๋ผ์ด์ธํธ๋ Lock์ ์ป์ง ๋ชปํ ๊ฒ์ผ๋ก ์ฌ๊ฒผ๋ ๋ชจ๋ ๋ ธ๋๋ฅผ unlockํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์๋ ๋ช๊ฐ์ง ๊ตฌํ์ฒด๋ค์ด ์กด์ฌํ๋ค.
๋ณด๋ฉด ์ข ์ด์ํ๊ฒ๋ ๋ง๋ค. ์คํ ๊ฐ์ ๋ณด๊ณ ๊ด์ฐฎ์ผ๋ฉด ๊ณจ๋ผ์ฐ์..
๋น์ฅ ๋ด๊ฐ ์จ๋ณธ Rust ๊ตฌํ์ฒด๋ ์ ๋๋ก ๋ ๋ฌผ๊ฑด์ด ์๋์๊ณ , Go๋ ๊ทธ๋ฌ๋ค.
Node.js ๊ตฌํ์ฒด๋ ์ ์์ง...?
์๋ฌดํผ ์ ๊ธฐ์ Redisson์ด ์ด ๋ฐฉ๋ฉด์์ ์ข ์ ์ฐ์ด๋ ๊ฒ ๊ฐ์๋ค.
์ฐธ์กฐ
https://redis.io/docs/manual/patterns/distributed-locks/
https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html
https://hudi.blog/distributed-lock-with-redis/
http://redisgate.kr/redis/command/setnx.php