๋ณธ ๋ด์ฉ์ ํ๋น๋ฏธ๋์ด์์ ์ถ๊ฐํ ์ฑ (๊ฐ๋ฐ์ ๊ธฐ์ ๋ฉด์ ๋ ธํธ)์ ์ผ๋ถ ๋ด์ฉ์ ๋ณด๊ฐํ๊ธฐ ์ํด์ ๋ง๋ Redis ํ์ฉ ์์ ์ ๋๋ค.
์ฑ ์ ๋ถ๋์ ์ฝ๋๋ ์ด๋ก ๋ฑ์ ๋ด์ฉ์ ๋ค ๋ด์๋ด์ง ๋ชปํ์๊ธฐ์ ๋ณธ ๋ฌธ์๋ก ์ถ๊ฐ์ ์ธ ์ค๋ช ์ ์งํํฉ๋๋ค.
๋ง์ฝ ๋ด์ฉ์ ๋ฌธ์ ๊ฐ ์๊ฑฐ๋ ์ค/ํ์๊ฐ ์์ ๊ฒฝ์ฐ [email protected]์ผ๋ก ๋ฉ์ผ ๋ถํ๋๋ฆฝ๋๋ค.
- Instagram - https://www.instagram.com/codevillains
- email - [email protected]
- Yes24 - https://www.yes24.com/Product/Goods/125554439
- KyoboBooks - https://product.kyobobook.co.kr/detail/S000212738756
- 2024.03.25, ํ๋น๋ฏธ๋์ด, ์ด๋จํฌ(codevillain) ์
์ ์ ๋ฐ ์ฃผ๋์ด ๊ฐ๋ฐ์ง๊ตฐ์ ์ทจ์ ๋ฐ ์ด์ง์ ์ํ ๊ฐ์ด๋
์๋ฅ ์์ฑ ๋ฐฉ๋ฒ๋ถํฐ, ์ ๋งํ ํ์ฌ๋ฅผ ์ฐพ๋ ๋ฐฉ๋ฒ, ์ฝ๋ฉ ํ ์คํธ์ ๊ธฐ์ ๋ฉด์ ์ ์ค๋นํ๊ธฐ ์ํด ์์์ผ ํ ๊ฐ๋ ๋ค์ ์ด๋ค ๋ฐฉ์์ผ๋ก ์ ๊ทผํด์ผ ํ๋์ง ์ค๋ช ํ์ต๋๋ค.
ํนํ ๋ฉด์ ์์ ๋ฉด์ ๊ด์ด ๋์ง๋ ์ง๋ฌธ์ ์๋์ ํด๋น ์ง๋ฌธ์ ์ฌ๋ฐ๋ฅด๊ฒ ๋ต๋ณํ๊ธฐ ์ํ ์ค์ง์ ์ธ ๋์ฒ๋ฐฉ๋ฒ์ ๋ํด์ ๊ธฐ์ ํ์์ต๋๋ค. ์์ธ๋ฌ ๊ธฐ์ ๋ฉด์ ์ ๋์ด ์ธ์ฑ๋ฉด์ ์์ ๊ฐ์ ธ์ผํ ๋ง์๊ฐ์ง๊ณผ ๋ฆฌ๋์ญ ์์น, ์ ๋ต์ด ์๋ ์ง๋ฌธ์ ๋์ฒํ๊ธฐ ์ํ ์ฌ๋ก๋ค์ ์๊ฐํ์์ต๋๋ค.
์ทจ์ ๋ฐ ์ด์ง์ ์ํฉ์์ ๋ณธ์ธ์ด ์ค๋นํ๊ณ ์๋ ๋ฐฉํฅ์ด ๋ง๋์ง ์ ๊ฒํ๊ณ ์ปค๋ฆฌ์ด ํจ์ค๋ฅผ ์ด๋ป๊ฒ ์ค๊ณํด์ผ ํ๋์ง ์ค์ฒ ๊ฐ๋ฅํ ๋ฐฉ๋ฒ์ ์ ์ํจ์ผ๋ก์ ๋์์ ์ฃผ๊ณ ์ ํ์์ต๋๋ค.
- ์๋์ด๋ฉด์ ๊ด์ด ์๋ ค์ฃผ๋ ๊ฐ๋ฐ์ ์ทจ์ ๊ณผ ์ด์ง, ํ๋ฐฉ์ ํด๊ฒฐํ๊ธฐ ์ด๋ก ํธ
- ์๋์ด๋ฉด์ ๊ด์ด ์๋ ค์ฃผ๋ ๊ฐ๋ฐ์ ์ทจ์ ๊ณผ ์ด์ง, ํ๋ฐฉ์ ํด๊ฒฐํ๊ธฐ ์ค์ ํธ
Redis๋ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์ฅ์๋ก, ๋งค์ฐ ๋น ๋ฅธ ์๋๋ก ๋ฐ์ดํฐ๋ฅผ ์กฐํ/์ ์ฅํ ์ ์๋ค.
key, value ์ ์ฅ์๋ก ์ ๋ช ํ์ง๋ง ๋จ์ํ key-value ์ ์ฅ์๋ฅผ ๋์ด ๋ค์ํ ๋ฐ์ดํฐ ๊ตฌ์กฐ(๋ฌธ์์ด, ๋ฆฌ์คํธ, ํด์, ์ ๋ฑ)๋ฅผ ์ง์ํ๋ฉฐ, ์ฃผ๋ก ์บ์ฑ ์์คํ ์ผ๋ก ์ฌ์ฉ๋์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ๋ฅผ ์ค์ด๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ์ ํฅ์์์นธ๋ค.
Redis๋ ์ฑ๊ธ ์ค๋ ๋ ์ด๋ฒคํธ ๋ฃจํ ๋ฐฉ์์ผ๋ก ๋์ํ๋ฉฐ, ํด๋ผ์ด์ธํธ๊ฐ ์คํํ ๋ช ๋ น์ด๋ค์ ์ด๋ฒคํธ ํ์ ์ ์ฌํ๊ณ ํ๋์ฉ ์ฒ๋ฆฌํ๋ค. ์ด ๋ฐฉ์์ ์ด๋น ์๋ง ๊ฑด์ ์ฐ์ฐ์ ์ฒ๋ฆฌํ ์ ์๋ ๋์ ์ฑ๋ฅ์ ์ ๊ณตํ๋ค. ๋ฐ์ดํฐ ์์์ฑ์ AOF(Append Only File)์ RDB(Redis Database) ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ์ ๊ณต๋๋ค. ์ผ๋ฐ์ ์ผ๋ก RDB ๋ฐฉ์์ผ๋ก ์ผ๋ณ ๋ฐฑ์ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ณ , ๊ทธ ์ดํ๋ถํฐ๋ AOF ํํ๋ก ๋น์ผ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐฑ์ ํ๋ ํํ๋ก ์์์ฑ์ ์ ์งํ๋ค.
๋ํ, Redis๋ ๋ณต์ ๋ฐ ํด๋ฌ์คํฐ๋ง ๊ธฐ๋ฅ์ ํตํด ๊ณ ๊ฐ์ฉ์ฑ๊ณผ ํ์ฅ์ฑ์ ์ ๊ณตํ๋ฉฐ, Pub/Sub ๋ฉ์์ง ์์คํ ๊ณผ ํธ๋์ญ์ ๊ธฐ๋ฅ๋ ์ง์ํ๋ค. ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ํด ๋ค์ํ ์ ์ฑ (์: LRU, LFU)์ ์ ๊ณตํ์ฌ ์ ํ๋ ๋ฉ๋ชจ๋ฆฌ ํ๊ฒฝ์์๋ ํจ์จ์ ์ผ๋ก ๋์ํ ์ ์๋ค
์ผ๋ฐ์ ์ธ Replication ์ธํธ๋ก ๊ตฌ์ฑํด๋ ๋ฌด๋ฐฉํ์ง๋ง ์ผํฐ๋ฌ(Sentinel) ์ด๋ ํด๋ฌ์คํฐ๋ฅผ ๊ตฌ์ถํจ์ผ๋ก์, ๊ณ ๊ฐ์ฉ์ฑ (HA, High Availability)์ Failover๋ฅผ ๊ตฌ์ฑํ ์ ์๋ค.
์ผํฐ๋ฌ์ ๊ฒฝ์ฐ ์ผํฐ๋ฌ ์๋ฒ๋ก ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐํ๋ฉฐ, ์ผํฐ๋ฌ์ด ๋ง์คํฐ ์๋ฒ๋ค์๊ฒ ๋ช ๋ น์ด๋ฅผ ์ง์ํ๋ค. ์ด ๊ณผ์ ์์ ๋ชจ๋ํฐ๋ง์ด ๋์ด์ผ ํ๋ฏ๋ก 3๋ ์ด์์ ํ์๋ก ๋ง์คํฐ์ ๋ ํ๋ฆฌ์นด๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๋ฉฐ, ์ฅ์ ๋ฐ์์ ์ผํฐ๋ฌ์ด ์๋ฒ๋ค์ ๋์ ์ ์ฐจ๋ฅผ ์งํํ์ฌ (Quorum, ์ ์กฑ์) ๋ง์คํฐ ์๋ฒ๋ฅผ ์ ์ธํ๊ณ ์ฅ์ ๋ณต๊ตฌ ์ ์ฐจ๋ฅผ ์งํํ๋ค.
ํด๋ฌ์คํฐ์ ๊ฒฝ์ฐ ํด๋ฌ์คํฐ์ ํฌํจ๋ ๋ ธ๋๋ค์ด ์๋ก ํต์ ํ๋ฉด์ HA๋ฅผ ์ ์งํ๋ฉฐ, ์ค๋ฉ ๋ฑ์ ๋ถ์ฐ ์ ์ฅ์ด ๊ฐ๋ฅํ๋ค. (Hash ์๊ณ ๋ฆฌ์ฆ)
์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์ ํด๋ผ์ด์ธํธ๋ ๋ ๋์ค ํด๋ฌ์คํฐ์์ ๋ ธ๋ ์ค ํ๋๋ผ๋ ์ฐ๊ฒฐ๋๋ฉด ํด๋ฌ์คํฐ ์ ์ฒด ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๋ค. ๋ฐ๋ผ์ ์ด์์ค ์ฆ์ค์ ํ๋๋ผ๋ ์คํ๋ง ์ค์ ์ ๋ณ๊ฒฝํ ํ์๊ฐ ์๋ค.
$ brew install redis
$ brew services start redis or redis-server (foreground ์คํ)
$ brew services stop redis or foreground ์คํ ํ๋ฉด์์ Ctrl + c
$ brew services restart redis
> SET Hello world // ์ ์ฅ
> GET Hello // ์กฐํ
NX ์ต์ : ์ง์ ํ ํค๊ฐ ์์ ๋์๋ง ์๋ก์ด ํค๋ฅผ ์์ฑ (SET Hello newWorld NX)
XX ์ต์ : ํค๊ฐ ์์ ๋์๋ง ์๋ก์ด ๊ฐ์ผ๋ก ๋ฎ์ด์ (SET Hello newWorld XX)
์๋ฃ๊ตฌ์กฐ์ด์ง๋ง ์ซ์ ํํ๋ ์ ์ฅ ๊ฐ๋ฅํ๊ณ INCR์ด๋ INCRBY ๋ฅผ ์ด์ฉํ์ฌ 1์ฉ ์ฆ๊ฐ์ํค๊ฑฐ๋ ์ ๋ ฅํ ๊ฐ๋งํผ ์ฆ๊ฐ์ํฌ์ ์๋ค.
> SET counter 100
> incr counter
(integer) 101
> incrby counter 50
(integer) 151
MGET์ผ๋ก ์ฌ๋ฌ ํค๋ฅผ ์กฐ์ํ ์ ์๋ค.
> MSET a 10 b 20 c 30
OK
> MGET a b c
1) "10"
2) "20"
3) "30"
List ํ์ (๋ฐฐ์ด)์ ๊ฒฝ์ฐ LPUSH(head)๋ฅผ ์ด์ฉํด List์ ์ผ์ชฝ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ํ๊ณ , RPUSH(tail)์ ํตํด List์ ์ค๋ฅธ์ชฝ์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ํ๋ค.
> LPUSH mylist e
(integer) 1
> RPUSH mylist b
(integer) 2
> LRANGE mylist 0 -1
1) "e"
2) "b"
LRANGE๋ฅผ ํตํด ์์๊ณผ ๋ ์ธ๋ฑ์ค๋ฅผ ์กฐํํ ์ ์๋ค.
LPOP์ ์ด์ฉํ์ฌ ๊ธฐ๋ณธ์ญ์ (๋งจ ์)๋ฅผ ํ๊ฑฐ๋ ์ง์ ๋ ์ ๋งํผ ์ญ์ ํ ์ ์๋ค.
LTRIM์ ๊ฒฝ์ฐ ์ง์ ๋ ๋ ๋ฒ์๋ง ๋จ๊ธฐ๊ณ ๋๋จธ์ง๋ ์ญ์ ํ๋ค.
LPOP mylist
"A"
> LRANGE mylist 0 -1
1) "B"
2) "C"
3) "A"
4) "D"
5) "e"
6) "b"
> LPOP mylist 6
1) "B"
2) "C"
3) "A"
4) "D"
5) "e"
6) "b"
> LRANGE mylist 0 -1
(empty array)
> LPUSH mylist D A C B A
(integer) 5
> LTRIM mylist 0 1
OK
> LRANGE mylist 0 -1
1) "A"
2) "B"
LINSERT๋ฅผ ํตํด ์ํ๋ ๋ฐ์ดํฐ ์(before) ํน์ ๋ค(after)์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
> LINSERT mylist BEFORE B C
(integer) 3
> LRANGE mylist 0 -1
1) "A"
2) "C"
3) "B"
LSET์ ํตํด ์ง์ ๋ ์ธ๋ฑ์ค์ ์ ๊ท๋ก ์ ๋ ฅํ์ฌ ๋ฎ์ด์ธ ์ ์๋ค
> LSET mylist 1 D
> LRANGE mylist 0 -1
1) "A"
2) "D"
3) "B"
LINDEX ๋ฅผ ํตํด ์ํ๋ ์ธ๋ฑ์ค์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์ ์๋ค.
> LINDEX mylist 2
"B"
Key, Value์์ Value์ ๊ตฌ์กฐ ์ญ์ ํ๋์ ๊ฐ์ผ๋ก ์ด๋ฃจ์ด์ง ์์ดํ ์งํฉ์ด๋ค.
HSET ์ปค๋งจ๋๋ฅผ ์ด์ฉํด์ ์์ดํ ๋ค์ ์ ์ฅํ ์ ์์ผ๋ฉฐ, HGET์ ํตํด ์กฐํํ ์ ์๋ค.
HMGET์ผ๋ก ์ฌ๋ฌ ํ๋๋ฅผ ์กฐํํ ์ ์๊ณ , HGETALL์ ํตํด hash์ ๋ชจ๋ ํ๋๋ฅผ ๋ฐํ๋ฐ์ ์ ์๋ค.
> HSET Product1 name "samsung"
(integer) 1
> HSET Product1 device "gal 22"
(integer) 1
> HSET Product1 version "2021"
(integer) 1
> HSET Product2 name "apple" device "iphone 11 pro" version "2022"
> HGET Product1 name
"samsung"
> HMGET Product2 name device
1) "apple"
2) "iphone 11 pro"
> HGETALL Product1
1) "name"
2) "samsung"
3) "device"
4) "gal 22"
5) "version"
6) "2021"
์ ๋ ฌ๋์ง ์๊ณ ์ค๋ณต ์ ์ฅ์ด ๋์ง ์๋ ๋ฌธ์์ด์ด๋ค.
SADD๋ก Set์ ์ ์ฅํ ์ ์์ผ๋ฉฐ SMEMBERS๋ก Set ์๋ฃ๋ฅผ ๋ณผ ์ ์๋ค.
SREM์ผ๋ก ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ์ ์๊ณ , SPOP์ผ๋ก ๋ด๋ถ ์์ดํ ์ ๋ฐํํ๊ณ ์ญ์ ํ ์ ์๋ค.
ํฉ์งํฉ์ SUNION, ๊ต์งํฉ์ SINTER, ์ฐจ์งํฉ์ SDIFF๋ก ์ํํ ์ ์๋ค.
> SADD myset a
(integer) 1
> SADD myset a a a b b b c c c
(integer) 2
> SMEMBERS myset
1) "a"
2) "b"
3) "c"
> SREM myset b
(integer) 1
> SMEMBERS myset
1) "a"
2) "c"
> SPOP myset
"a"
> SMEMBERS myset
1) "c"
> SADD set1 a b c d e
(integer) 5
> SADD set2 d e f g h
(integer) 5
> SINTER set1 set2
1) "d"
2) "e"
> SDIFF set1 set2
1) "a"
2) "b"
3) "c"
> SDIFF set2 set1
1) "f"
2) "g"
3) "h"
์ค์ฝ์ด๊ฐ์ ๋ฐ๋ผ ์ ๋ ฌ๋๋ ๋ฌธ์์ด. ๋ชจ๋ ์์ดํ ์ ์ค์ฝ์ด-๊ฐ ์์ผ๋ก ์ ์ฅ๋๋ฉฐ ์ ์ฅ์ ์ค์ฝ์ด๋ก ์ ๋ ฌ ์ฒ๋ฆฌ๋๋ค.
> ZADD score:1 100 user:B
(integer) 1
> ZADD score:1 150 user:A 150 user:C 200 user:F 300 user:E
(integer) 4
> ZRANGE score:1 1 3
1) "user:A"
2) "user:C"
3) "user:F"
> ZRANGE score:1 100 150 BYSCORE
1) "user:B"
2) "user:A"
3) "user:C"
๊ทธ๋ฐ์ Bitmap์ด๋ Hyperloglog, Geospatial(์๊ฒฝ๋), Stream(๋ฉ์์ง๋ธ๋ก์ปค) ๋ฑ์ด ์๋ค.
> EXISTS key-name
> KEYS [pattern] | * // ํจํด ์กฐํ ํน์ ๋ชจ๋ ํค ์กฐํ
- KEYS ์ํ ๋น์ฉ์ ๋๊ธฐ ๋๋ฌธ์ ์์ญ๋ง๊ฐ์ ํค ์ ๋ณด๋ฅผ ๋ฐํํ๋ ๊ฒ์ health check ์๋ต์ด ์์ด ํ์ผ์ค๋ฒ๊ฐ ๋ ์ ์๋ค. (๋ค๋ฅธ ์ปค๋งจ๋๋ ์ฌ์ฉ๋ถ๊ฐ)
- h?llo โ hello, hallo ๋งค์นญ
- h*llo โ hllo, heeello ๋งค์นญ
- h[ae]llo โ hello, hallo ๋งค์นญ, hillo๋ ๋งค์นญ ๋ถ๊ฐ
- h[^e]llo โ hallo, hbllo ๋งค์นญ, hello๋ ๋งค์นญ ๋ถ๊ฐ
- h[a-b]llo โ hallo, hbllo ๋งค์นญ
> RENAME key newKey
> RENAMENX key newKey
> FLUSHALL [sync | async]
or
> DEL key [key-name]
or
> UNLINK key [key-name]
DEL ์ ๊ฒฝ์ฐ ๋๊ธฐ์ ์ผ๋ก ์ํํ๊ธฐ ๋๋ฌธ์ ๋ ๋์ค ์ธ์คํด์ค์ ์ํฅ์ ์ฃผ๊ณ , ์ด ๋๋ฌธ์ ๊ฐ๊ธ์ DEL ๋ณด๋ค๋ UNLINK๋ก ์ฒ๋ฆฌํด์ผ ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ํค๋ฅผ ์ญ์ ํ์ฌ ์ฑ๋ฅ์ ์ด์๊ฐ ์๊ฒ ๋๋ค.
ํค ๋ง๋ฃ ์ง์
> EXPIRE key secontd [NX | XX | GT | LT]
> EXPIRETIME key // ํค ์ญ์ ์๊ฐ ๋ฐํ, ๋ง๋ฃ์๊ฐ ์์ ๊ฒฝ์ฐ -1, ํค ์์ ๊ฒฝ์ฐ -2 ๋ฐํ
TTL์ ๊ฒฝ์ฐ ํค์ ๋ง๋ฃ์๊ฐ์ ๋ฐํํ๋ค.
> TTL key // ๋ช ์ด ๋ค์ ๋ง๋ฃ๋๋์ง ๋ฐํ, ๋ง๋ฃ์๊ฐ ์์ ๊ฒฝ์ฐ -1, ํค ์์ ๊ฒฝ์ฐ -2 ๋ฐํ
์์น ์ ๋ณด๋ฅผ ๊ฒฝ๋์ ์๋ ์์ผ๋ก ์ ์ฅํ๋ฉฐ, ๋ด๋ถ์ ์ผ๋ก sorted set ๊ตฌ์กฐ๋ก ์ ์ฅํ๋ค.
> GETADD user 20.123123123 12.2133255345
ํน์ ์๋น์ ๋ ์คํ ๋์ด๋ผ๋ ํค์ ๊ฒฝ๋, ์๋, ์๋น ์ด๋ฆ์์ผ๋ก ์ ์ฅํ ๊ฒฝ์ฐ ์๋์ ๊ฐ๋ค.
> GEOADD restaurant 30.0123123 34.123123354 pangyo // ์ ์ฅ
> GEOPOS restaurant pangyo // ์ ์ฅ๋ ๋ฐ์ดํฐ
1) 1) "30.01231223344802856"
2) "34.12312216686382982"
> GEOSEARCH restaurant FROMLONLAT 30.0123123 34.123123354 byradius 1km // FROMLONLAT ์ผ๋ก ๊ฒฝ๋, ์๋ ์ง์ ํ byradius ๋ก ๋ฐ๊ฒฝ 1km ์กฐํ
> GEOSEARCH key FROMMEMBER member bybox 4 2 km // ๋์ผํ ์๊ฒฝ๋ ๊ฐ ๊ธฐ์ค์ผ๋ก witth ์ height์ ๋ํญํ๋ ๋ฐ์ดํฐ ๊ฒ์ (์ง์ฌ๊ฐํ)
๊ทธ๋ฐ์ ๋ ๋์ค๋ฅผ ์บ์๋ก ์ฌ์ฉํ๊ฑฐ๋ ์ธ์ ์คํ ์ด๋ก ์ฌ์ฉํ๊ฑฐ๋ ๋ฉ์์ง ๋ธ๋ก์ปค ๋ฑ์ผ๋ก ํ์ฉํ ์ ์๋ค.
Redis ์๋ฒ์ ์ ๋ฐ์ ์ธ ์ํ๋ฅผ ํ์ธํ ์ ์๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ช ๋ น์ด์ด๋ค.
> INFO
์ด ๋ช ๋ น์ด๋ ์๋ฒ, ํด๋ผ์ด์ธํธ, ๋ฉ๋ชจ๋ฆฌ, ์์์ฑ ๋ฑ ๋ค์ํ ์น์ ์ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ค.
์ค์๊ฐ์ผ๋ก Redis ์๋ฒ์์ ์ฒ๋ฆฌ๋๋ ๋ช ๋ น์ด๋ฅผ ๋ณผ ์ ์๋ค.
> MONITOR
์ฃผ์: ํ๋ก๋์ ํ๊ฒฝ์์๋ ์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์ผ๋ฏ๋ก ์ฃผ์ํด์ ์ฌ์ฉํด์ผ ํ๋ค.
์ค์ ๋ ์คํ ์๊ฐ ์๊ณ๊ฐ์ ์ด๊ณผํ ๋ช ๋ น์ด๋ค์ ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์๋ค.
> SLOWLOG GET [number]
- Redis-cli: ๊ธฐ๋ณธ ์ ๊ณต๋๋ CLI ๋๊ตฌ๋ก, ๋ค์ํ ๋ชจ๋ํฐ๋ง ๋ช ๋ น์ด๋ฅผ ์ง์
- Redis-stat: Redis ์๋ฒ์ ์ค์๊ฐ ํต๊ณ๋ฅผ ๋ณด์ฌ์ฃผ๋ CLI ๋๊ตฌ
- Redis Commander: ์น ๊ธฐ๋ฐ์ Redis ๊ด๋ฆฌ ๋๊ตฌ๋ก, ๋ฐ์ดํฐ ์กฐํ ๋ฐ ์์ ์ด ๊ฐ๋ฅ
- Prometheus + Grafana: ๋ฉํธ๋ฆญ ์์ง ๋ฐ ์๊ฐํ๋ฅผ ์ํ ๊ฐ๋ ฅํ ์กฐํฉ
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋
- ์ฐ๊ฒฐ๋ ํด๋ผ์ด์ธํธ ์
- ์ด๋น ์ฒ๋ฆฌ๋๋ ๋ช ๋ น์ด ์
- ํค์คํ์ด์ค ํํธ/๋ฏธ์ค ๋น์จ
- ๋คํธ์ํฌ ๋์ญํญ ์ฌ์ฉ๋
์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ์ธ์ ์ ๋ณด๋ฅผ Redis์ ์ ์ฅํ์ฌ ๋น ๋ฅธ ์ ๊ทผ๊ณผ ๋ถ์ฐ ํ๊ฒฝ์์์ ์ธ์ ๊ณต์ ๋ฅผ ๊ตฌํํ ์ ์๋ค.
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
// ์ฌ์ฉ ์
@RestController
public class SessionController {
@GetMapping("/set-session")
public String setSession(HttpSession session) {
session.setAttribute("username", "John Doe");
return "Session set";
}
@GetMapping("/get-session")
public String getSession(HttpSession session) {
return (String) session.getAttribute("username");
}
}
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ API ์๋ต์ Redis์ ์บ์ฑํ์ฌ ์๋ต ์๊ฐ์ ํฌ๊ฒ ์ค์ผ ์ ์๋ค.
@Cacheable(value = "userCache", key = "#userId")
public User getUserById(String userId) {
// ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ก์ง
}
๊ฒ์์ด๋ ๋ฆฌ๋๋ณด๋์์ Sorted Set์ ์ฌ์ฉํ์ฌ ์ค์๊ฐ ๋ญํน์ ๊ตฌํํ ์ ์๋ค.
> ZADD leaderboard 1000 "user:1"
> ZADD leaderboard 2000 "user:2"
> ZREVRANGE leaderboard 0 9 WITHSCORES // ์์ 10๋ช
์กฐํ
List ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋จํ ๋ฉ์์ง ํ๋ฅผ ๊ตฌํํ ์ ์๋ค.
> LPUSH tasks "task:1"
> RPOP tasks // ์์
์ฒ๋ฆฌ
HyperLogLog๋ฅผ ์ฌ์ฉํ์ฌ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์นด๋๋๋ฆฌํฐ๋ฅผ ์ถ์ ํ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ์น์ฌ์ดํธ์ ์ผ์ผ ์ ๋ฐฉ๋ฌธ์ ์๋ฅผ ์ถ์ ํ ์ ์๋ค.
> PFADD daily_visitors "user:1" "user:2" "user:3"
> PFCOUNT daily_visitors
API ์์ฒญ์ ์๋๋ฅผ ์ ํํ๋ ๋ฐ Redis๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
@Service
public class RateLimiter {
private final StringRedisTemplate redisTemplate;
public RateLimiter(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean allowRequest(String userId, int maxRequests, int timeWindowSeconds) {
String key = "rate_limit:" + userId;
long currentTime = System.currentTimeMillis() / 1000;
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForZSet().add(key, String.valueOf(currentTime), currentTime);
operations.opsForZSet().removeRangeByScore(key, 0, currentTime - timeWindowSeconds);
operations.opsForZSet().size(key);
return operations.exec();
}
});
// ํธ๋์ญ์
๊ฒฐ๊ณผ ๊ฒ์ฆ
if (txResults == null || txResults.isEmpty()) {
throw new RuntimeException("Redis transaction failed");
}
// ๋ง์ง๋ง ๊ฒฐ๊ณผ(size ์ฐ์ฐ)๊ฐ Long ํ์
์ธ์ง ํ์ธ
if (!(txResults.get(txResults.size() - 1) instanceof Long)) {
throw new RuntimeException("Unexpected result type from Redis transaction");
}
Long requestCount = (Long) txResults.get(txResults.size() - 1);
// ๋ง๋ฃ ์๊ฐ ์ค์ (ํธ๋์ญ์
์ธ๋ถ์์ ์ํ)
redisTemplate.expire(key, timeWindowSeconds, TimeUnit.SECONDS);
return requestCount <= maxRequests;
}
}
// ์ฌ์ฉ ์
@RestController
public class ApiController {
private final RateLimiter rateLimiter;
public ApiController(RateLimiter rateLimiter) {
this.rateLimiter = rateLimiter;
}
@GetMapping("/api/resource")
public ResponseEntity<String> getResource(@RequestHeader("User-Id") String userId) {
try {
if (!rateLimiter.allowRequest(userId, 10, 60)) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("Rate limit exceeded");
}
// ์ค์ ๋ฆฌ์์ค ์ฒ๋ฆฌ ๋ก์ง
return ResponseEntity.ok("Resource data");
} catch (Exception e) {
// ๋ก๊น
์ถ๊ฐ
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred");
}
}
}
- github.com์์ ๋ณธ์ธ ๊ณ์ ์ ๋น github project repository ์์ฑ
- ๋ก์ปฌ clone ํ Springboot gradle ํ๋ก์ ํธ ์์ฑ (Spring-Redis)
- root(Spring-Redis)์์ new > module...์ ํ
- Java project ์ ํ ํ ํ๋ก์ ํธ ๋ค์ ์ ๋ ฅ, Gradle, Groovy ์ ํ ํ Create ํด๋ฆญ
- root์ src ํด๋๋ ํ์ ์์ผ๋ฏ๋ก ์ญ์
- ์ด์ ๊ฐ์ ํํ๋ก ์ฌ๋ฌ ํ๋ก์ ํธ ํ์์ ๋ฉํฐ ๋ชจ๋ ํํ๋ก ์์ฑํ ์ ์์ผ๋ฉฐ, ์ฐ๊ด๊ด๊ณ๋ฅผ ๋ฐ๋ก ๋งบ์ง ์๊ณ ๊ฐ๋ณ ํ๋ก์ ํธ๋ก ์ฌ์ฉํจ
redis-sample : Spring startup์ ๊ฐ๋จํ ๋ฉ์์ง ์ถ๋ ฅ ์์
redis-crud : redis๋ฅผ ์ด์ฉํ CRUD ํ์ฉ ์์
redis-pub/sub : message publish/subscribe ์์
root์ build.gradle์ ์๋์ ๊ฐ๋ค. ํ์ ํ๋ก์ ํธ๋ค์ root์ ์ค์ ์ ์์กด์ฑ์ ๊ฐ์ง๋ง root์๋ ์์ค๊ฐ ์๊ณ gradle ๋ํ๋์๋ง ๋งบ๋๋ก ๋ฉํฐ ํ๋ก์ ํธ๋ฅผ ๊ตฌ์ฑํ๋ค. (๋ค๋ฅธ ๋ชจ๋๊ด ์์กด ๊ด๊ณ ์์)
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.1'
id 'io.spring.dependency-management' version '1.1.5'
}
group = 'com.villainscode'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
// ํ์ ํ๋ก์ ํธ ๋ชจ๋์ ๊ณตํต ํ๋ฌ๊ทธ์ธ ๋ฐ ์์กด์ฑ ์ ์
subprojects {
apply plugin: 'java-library'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'java'
compileJava.options.encoding = 'UTF-8'
sourceCompatibility = '17'
task wrapper(type: Wrapper) {
gradleVersion = '8.1.1'
}
repositories {
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
tasks.named('test') {
useJUnitPlatform()
}
}
root์ settings.gradle์ ์๋์ ๊ฐ๋ค. ํ์ ํ๋ก์ ํธ ์์ฑ ํ gradle refresh ํ๋ฉด ์๋์ผ๋ก ์ถ๊ฐ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
rootProject.name = 'Spring-Redis'
include 'redis-sample'
include 'redis-crud'
include 'redis-pub'
include 'redis-sub'
๊ฐ ํ์ ํ๋ก์ ํธ๋ ํด๋น ํ๋ก์ ํธ์์ ํ์ํ ๋ชจ๋์ dependencies๋ค์ ๊ฐ build.gradle ์ ์ถ๊ฐํด์ค ํ ์ฌ์ฉํ๋ฉด ๋๋ค.
๊ธฐ๋ณธ ์์ (redis-sample)๋ ํน๋ณํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ํ์ง ์๋ค. ํ์ง๋ง redis-crud๋ pub/sub ๊ด๋ จ ๋ชจ๋๋ค์ spring-boot-starter-web ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ํ๋ค. ๊ฐ ๋ชจ๋ ํ์์ build.gradle ํ์ผ์ ์ฐธ์กฐํ๊ธฐ ๋ฐ๋๋ค.
๋ sample์ด๋ crud๋ ๊ธฐ๋ณธ ํฌํธ์ธ 8080์ผ๋ก ์คํ๋์ง๋ง pub/sub์ ๋ฐ๋ก ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์์ผ ํ๋ฏ๋ก properties์์ server.port๋ฅผ ๊ฐ๊ฐ 8081, 8082๋ก ์ค์ ํ๋ค.
๊ฐ๊ฐ์ ๋ชจ๋๋ค์ ์คํํ ๋ค API๋ฅผ ํธ์ถํ๋ .http ์ํ์ ์๋์ ๊ฐ๋ค. (.http ์ฌ์ฉ๋ฒ์ ๋ํด์๋ intellij .http
๋ก ๊ตฌ๊ธ๋ง ํ๊ธฐ ๋ฐ๋๋ค )
### redis-sample Send message to testChannel
POST http://localhost:8080/message/testChannel
Content-Type: application/json
{
"message": "Hello, this is a test message."
}
### redis-crud
GET http://localhost:8080/api/v1/keys
### redis-crud
GET http://localhost:8080/api/v1/getAll
### redis-pub Publish OrderQueue
POST http://localhost:8081/publish
Content-Type: application/json
{
"id": "order123",
"userId": "user456",
"productName": "Laptop",
"price": 1200,
"qty": 2
}