Skip to content

Latest commit

ย 

History

History
662 lines (513 loc) ยท 20.1 KB

readme.md

File metadata and controls

662 lines (513 loc) ยท 20.1 KB

Spring Redis Quick Guide

๋ณธ ๋‚ด์šฉ์€ ํ•œ๋น›๋ฏธ๋””์–ด์—์„œ ์ถœ๊ฐ„ํ•œ ์ฑ…(๊ฐœ๋ฐœ์ž ๊ธฐ์ˆ  ๋ฉด์ ‘ ๋…ธํŠธ)์˜ ์ผ๋ถ€ ๋‚ด์šฉ์„ ๋ณด๊ฐ•ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋งŒ๋“  Redis ํ™œ์šฉ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.
์ฑ…์˜ ๋ถ„๋Ÿ‰์ƒ ์ฝ”๋“œ๋‚˜ ์ด๋ก ๋“ฑ์˜ ๋‚ด์šฉ์„ ๋‹ค ๋‹ด์•„๋‚ด์ง€ ๋ชปํ•˜์˜€๊ธฐ์— ๋ณธ ๋ฌธ์„œ๋กœ ์ถ”๊ฐ€์ ์ธ ์„ค๋ช…์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ ๋‚ด์šฉ์— ๋ฌธ์ œ๊ฐ€ ์žˆ๊ฑฐ๋‚˜ ์˜ค/ํƒˆ์ž๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ [email protected]์œผ๋กœ ๋ฉ”์ผ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์ฑ… ์†Œ๊ฐœ

[์—ฐ๋ด‰์˜ ์•ž์ž๋ฆฌ๋ฅผ ๋ฐ”๊พธ๋Š”] ๊ฐœ๋ฐœ์ž ๊ธฐ์ˆ ๋ฉด์ ‘ ๋…ธํŠธ

  • 2024.03.25, ํ•œ๋น›๋ฏธ๋””์–ด, ์ด๋‚จํฌ(codevillain) ์ €

์‹ ์ž… ๋ฐ ์ฃผ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ง๊ตฐ์˜ ์ทจ์—… ๋ฐ ์ด์ง์„ ์œ„ํ•œ ๊ฐ€์ด๋“œ

์„œ๋ฅ˜ ์ž‘์„ฑ ๋ฐฉ๋ฒ•๋ถ€ํ„ฐ, ์œ ๋งํ•œ ํšŒ์‚ฌ๋ฅผ ์ฐพ๋Š” ๋ฐฉ๋ฒ•, ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ์™€ ๊ธฐ์ˆ  ๋ฉด์ ‘์„ ์ค€๋น„ํ•˜๊ธฐ ์œ„ํ•ด ์•Œ์•„์•ผ ํ•  ๊ฐœ๋…๋“ค์„ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผํ•ด์•ผ ํ•˜๋Š”์ง€ ์„ค๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ ๋ฉด์ ‘์—์„œ ๋ฉด์ ‘๊ด€์ด ๋˜์ง€๋Š” ์งˆ๋ฌธ์˜ ์˜๋„์™€ ํ•ด๋‹น ์งˆ๋ฌธ์— ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋‹ต๋ณ€ํ•˜๊ธฐ ์œ„ํ•œ ์‹ค์งˆ์ ์ธ ๋Œ€์ฒ˜๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ๊ธฐ์ˆ ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์•„์šธ๋Ÿฌ ๊ธฐ์ˆ ๋ฉด์ ‘์„ ๋„˜์–ด ์ธ์„ฑ๋ฉด์ ‘์—์„œ ๊ฐ€์ ธ์•ผํ•  ๋งˆ์Œ๊ฐ€์ง๊ณผ ๋ฆฌ๋”์‹ญ ์›์น™, ์ •๋‹ต์ด ์—†๋Š” ์งˆ๋ฌธ์— ๋Œ€์ฒ˜ํ•˜๊ธฐ ์œ„ํ•œ ์‚ฌ๋ก€๋“ค์„ ์†Œ๊ฐœํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ทจ์—… ๋ฐ ์ด์ง์˜ ์ƒํ™ฉ์—์„œ ๋ณธ์ธ์ด ์ค€๋น„ํ•˜๊ณ  ์žˆ๋Š” ๋ฐฉํ–ฅ์ด ๋งž๋Š”์ง€ ์ ๊ฒ€ํ•˜๊ณ  ์ปค๋ฆฌ์–ด ํŒจ์Šค๋ฅผ ์–ด๋–ป๊ฒŒ ์„ค๊ณ„ํ•ด์•ผ ํ•˜๋Š”์ง€ ์‹ค์ฒœ ๊ฐ€๋Šฅํ•œ ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•จ์œผ๋กœ์„œ ๋„์›€์„ ์ฃผ๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Redis

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 ์•Œ๊ณ ๋ฆฌ์ฆ˜)

์Šคํ”„๋ง ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ผ์ด์–ธํŠธ๋Š” ๋ ˆ๋””์Šค ํด๋Ÿฌ์Šคํ„ฐ์˜์˜ ๋…ธ๋“œ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์—ฐ๊ฒฐ๋˜๋ฉด ํด๋Ÿฌ์Šคํ„ฐ ์ „์ฒด ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์šด์˜์ค‘ ์ฆ์„ค์„ ํ•˜๋”๋ผ๋„ ์Šคํ”„๋ง ์„ค์ •์„ ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

์„ค์น˜(MacOS, brew ์„ค์น˜ ํ™˜๊ฒฝ ๊ธฐ์ค€)

$ 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)

String

์ž๋ฃŒ๊ตฌ์กฐ์ด์ง€๋งŒ ์ˆซ์ž ํ˜•ํƒœ๋„ ์ €์žฅ ๊ฐ€๋Šฅํ•˜๊ณ  INCR์ด๋‚˜ INCRBY ๋ฅผ ์ด์šฉํ•˜์—ฌ 1์”ฉ ์ฆ๊ฐ€์‹œํ‚ค๊ฑฐ๋‚˜ ์ž…๋ ฅํ•œ ๊ฐ’๋งŒํผ ์ฆ๊ฐ€์‹œํ‚ฌ์ˆ˜ ์žˆ๋‹ค.

> SET counter 100
> incr counter
(integer) 101
> incrby counter 50
(integer) 151

MSET

MGET์œผ๋กœ ์—ฌ๋Ÿฌ ํ‚ค๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

> MSET a 10 b 20 c 30
OK
> MGET a b c
1) "10"
2) "20"
3) "30"

LIST ํƒ€์ž…์˜ ์กฐ์ž‘

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"

HASH

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"

SET

์ •๋ ฌ๋˜์ง€ ์•Š๊ณ  ์ค‘๋ณต ์ €์žฅ์ด ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ž์—ด์ด๋‹ค.

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"

Sorted Set

์Šค์ฝ”์–ด๊ฐ’์— ๋”ฐ๋ผ ์ •๋ ฌ๋˜๋Š” ๋ฌธ์ž์—ด. ๋ชจ๋“  ์•„์ดํ…œ์€ ์Šค์ฝ”์–ด-๊ฐ’ ์Œ์œผ๋กœ ์ €์žฅ๋˜๋ฉฐ ์ €์žฅ์‹œ ์Šค์ฝ”์–ด๋กœ ์ •๋ ฌ ์ฒ˜๋ฆฌ๋œ๋‹ค.

> 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(๋ฉ”์‹œ์ง€๋ธŒ๋กœ์ปค) ๋“ฑ์ด ์žˆ๋‹ค.

Key ๊ด€๋ จ ์ปค๋งจ๋“œ

ํ‚ค ์กฐํšŒ

 > EXISTS key-name

์ „์ฒด ํ‚ค ์กฐํšŒ

> KEYS [pattern] | * // ํŒจํ„ด ์กฐํšŒ ํ˜น์€ ๋ชจ๋“  ํ‚ค ์กฐํšŒ 
- KEYS ์ˆ˜ํ–‰ ๋น„์šฉ์€ ๋†’๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜์‹ญ๋งŒ๊ฐœ์˜ ํ‚ค ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ health check ์‘๋‹ต์ด ์—†์–ด ํŽ˜์ผ์˜ค๋ฒ„๊ฐ€ ๋‚  ์ˆ˜ ์žˆ๋‹ค. (๋‹ค๋ฅธ ์ปค๋งจ๋“œ๋„ ์‚ฌ์šฉ๋ถˆ๊ฐ€) 

Pattern ์Šคํƒ€์ผ

  • 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 ์˜ ๊ฒฝ์šฐ ๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ ˆ๋””์Šค ์ธ์Šคํ„ด์Šค์— ์˜ํ–ฅ์„ ์ฃผ๊ณ , ์ด ๋•Œ๋ฌธ์— ๊ฐ€๊ธ‰์  DEL ๋ณด๋‹ค๋Š” UNLINK๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ํ‚ค๋ฅผ ์‚ญ์ œํ•˜์—ฌ ์„ฑ๋Šฅ์ƒ ์ด์Šˆ๊ฐ€ ์—†๊ฒŒ ๋œ๋‹ค.

ํ‚ค ๋งŒ๋ฃŒ ์ง€์ •

> EXPIRE key secontd [NX | XX | GT | LT]
> EXPIRETIME key // ํ‚ค ์‚ญ์ œ ์‹œ๊ฐ„ ๋ฐ˜ํ™˜, ๋งŒ๋ฃŒ์‹œ๊ฐ„ ์—†์„ ๊ฒฝ์šฐ -1, ํ‚ค ์—†์„ ๊ฒฝ์šฐ -2 ๋ฐ˜ํ™˜ 

TTL์˜ ๊ฒฝ์šฐ ํ‚ค์˜ ๋งŒ๋ฃŒ์‹œ๊ฐ„์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

> TTL key // ๋ช‡ ์ดˆ ๋’ค์— ๋งŒ๋ฃŒ๋˜๋Š”์ง€ ๋ฐ˜ํ™˜, ๋งŒ๋ฃŒ์‹œ๊ฐ„ ์—†์„ ๊ฒฝ์šฐ -1, ํ‚ค ์—†์„ ๊ฒฝ์šฐ -2 ๋ฐ˜ํ™˜ 

Geospatial Index (์œ„์น˜ ๋ฐ์ดํ„ฐ)

GEO SET

์œ„์น˜ ์ •๋ณด๋ฅผ ๊ฒฝ๋„์™€ ์œ„๋„ ์Œ์œผ๋กœ ์ €์žฅํ•˜๋ฉฐ, ๋‚ด๋ถ€์ ์œผ๋กœ 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 ์„œ๋ฒ„์˜ ์ „๋ฐ˜์ ์ธ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ช…๋ น์–ด์ด๋‹ค.

> INFO

์ด ๋ช…๋ น์–ด๋Š” ์„œ๋ฒ„, ํด๋ผ์ด์–ธํŠธ, ๋ฉ”๋ชจ๋ฆฌ, ์˜์†์„ฑ ๋“ฑ ๋‹ค์–‘ํ•œ ์„น์…˜์˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

MONITOR

์‹ค์‹œ๊ฐ„์œผ๋กœ Redis ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌ๋˜๋Š” ๋ช…๋ น์–ด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

> MONITOR

์ฃผ์˜: ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

SLOWLOG

์„ค์ •๋œ ์‹คํ–‰ ์‹œ๊ฐ„ ์ž„๊ณ„๊ฐ’์„ ์ดˆ๊ณผํ•œ ๋ช…๋ น์–ด๋“ค์˜ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

> SLOWLOG GET [number]

์™ธ๋ถ€ ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ

  • Redis-cli: ๊ธฐ๋ณธ ์ œ๊ณต๋˜๋Š” CLI ๋„๊ตฌ๋กœ, ๋‹ค์–‘ํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ช…๋ น์–ด๋ฅผ ์ง€์›
  • Redis-stat: Redis ์„œ๋ฒ„์˜ ์‹ค์‹œ๊ฐ„ ํ†ต๊ณ„๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” CLI ๋„๊ตฌ
  • Redis Commander: ์›น ๊ธฐ๋ฐ˜์˜ Redis ๊ด€๋ฆฌ ๋„๊ตฌ๋กœ, ๋ฐ์ดํ„ฐ ์กฐํšŒ ๋ฐ ์ˆ˜์ •์ด ๊ฐ€๋Šฅ
  • Prometheus + Grafana: ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ๋ฐ ์‹œ๊ฐํ™”๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ์กฐํ•ฉ

์ฃผ์š” ๋ชจ๋‹ˆํ„ฐ๋ง ์ง€ํ‘œ

  • ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰
  • ์—ฐ๊ฒฐ๋œ ํด๋ผ์ด์–ธํŠธ ์ˆ˜
  • ์ดˆ๋‹น ์ฒ˜๋ฆฌ๋˜๋Š” ๋ช…๋ น์–ด ์ˆ˜
  • ํ‚ค์ŠคํŽ˜์ด์Šค ํžˆํŠธ/๋ฏธ์Šค ๋น„์œจ
  • ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ ์‚ฌ์šฉ๋Ÿ‰

Redis ์‹ค์ œ ์‚ฌ์šฉ ์‚ฌ๋ก€

์„ธ์…˜ ์ €์žฅ์†Œ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ์ž ์„ธ์…˜ ์ •๋ณด๋ฅผ 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

์†๋„ ์ œํ•œ(Rate Limiting)

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");
        }
    }
}

Redis Quick Guide ํ”„๋กœ์ ํŠธ ์„ค์ •

  1. github.com์—์„œ ๋ณธ์ธ ๊ณ„์ •์˜ ๋นˆ github project repository ์ƒ์„ฑ
  2. ๋กœ์ปฌ clone ํ›„ Springboot gradle ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ (Spring-Redis)
  3. root(Spring-Redis)์—์„œ new > module...์„ ํƒ
  4. Java project ์„ ํƒ ํ›„ ํ”„๋กœ์ ํŠธ ๋„ค์ž„ ์ž…๋ ฅ, Gradle, Groovy ์„ ํƒ ํ›„ Create ํด๋ฆญ
  5. root์˜ src ํด๋”๋Š” ํ•„์š” ์—†์œผ๋ฏ€๋กœ ์‚ญ์ œ
  6. ์ด์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ ํ•˜์œ„์— ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ํ˜•ํƒœ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋”ฐ๋กœ ๋งบ์ง€ ์•Š๊ณ  ๊ฐœ๋ณ„ ํ”„๋กœ์ ํŠธ๋กœ ์‚ฌ์šฉํ•จ

project์ƒ์„ฑ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

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
}