[Redis] DB는 무조건 RDB만 있는 줄 알았던 나의 Redis 입문기
처음 개발을 배울 때, 나에게 데이터베이스란 곧 관계형 데이터베이스였다.
테이블을 만들고, 관계를 맺고, SQL로 데이터를 끄집어내는 것만 있는 줄 알았다.
일반적으로 컴퓨터를 쓸 때도 무언가를 다운로드 받으면 당연히 하드디스크에 저장을 했었으니,
데이터베이스도 마찬가지일 거라도 생각을 했었다.
그러다가 한 프로젝트에서 메모리에 데이터를 저장한다는 데이터베이스를 처음 들어봤었다.
내가 아는 하드웨어 지식으로써는 PC가 꺼지면 메모리도 날아가는걸로 알고 있었는데,
거기에 저장한다고 하니 당연히 의구심이 들 수 밖에 없었다.
하지만 개념을 알고 실제로 사용해보니 그건 단점이 아니라 목적이었다.
영구히 보관할 데이터가 아니라, 찰나의 순간에 엄청난 속도로 주고받아야 하는 데이터들을 위한 용도였기 때문이다.
이 개념을 실제로 프로젝트에 이식했던 첫 경험은 OAuth 로그인 구현이었다.
카카오나 구글 같은 외부 로그인을 구현하다 보면 이런 흐름을 마주하게 된다.
우리 사이트에서 로그인 버튼을 누르는 순간, 사용자는 카카오의 페이지로 떠나버린다.
거기서 인증을 마치고 다시 내 사이트로 리다이렉트 되어 돌아올 때,
이 사용자가 아까 그 사용자가 맞는지 확인하는 과정이 필요했다.
이 짧은 외출 시간 동안 세션 정보나 임시 상태 값을 유지해야 했는데,
이걸 굳이 무거운 MySQL에 저장하고 삭제하기에는 너무 비효율적이었다.
- RDB를 썼다면: INSERT하고, 돌아오면 SELECT하고, 확인 끝나면 다시 DELETE… 불필요한 디스크 I/O 낭비.
- Redis를 쓰니: 메모리 위에서 빠르게 세션 정보를 저장하고, 사용자가 돌아오면 꺼내 확인한 뒤, 일정 시간이 지나면 알아서 사라지게 설정하면 끝이었다.
이때 이후로는 다른 DB들도 많고 상황에 따라, 목적에 따라 맞는걸 사용하면 된다는걸 알았다.
여기서는 그때 얻은 개념들을 정리해보고자 한다.
Redis란?
Key, Value 구조로 이루어진 비정형 데이터를 관리하기 위한 NoSQL이며 In-Memory 데이터베이스
Redis의 특징
- 기본적으로 데이터를 메모리에 저장하여 처리하기 때문에 읽기 및 쓰기 작업 속도가 디스크 기반 데이터베이스보다 빠르다.
- 별도의 쿼리문이 필요하지 않기 때문에 간편하게 사용이 가능하다.
- String,JSON,Set,List 등 다양한 데이터 구조를 지원한다.
- 싱글 스레드로 동작하기 때문에 한 번에 여러 요청을 수행할 수 없고, 시간이 오래 걸리는 요청을 수행하면 장애가 발생할 수 있다.
- Master-Slave(Replica), Sentinel, Cluster을 활용한 HA구성을 하여 서버 이중화 가능
Memory VS Disk
하드웨어의 측면에서 보면 구조적으로 접근 속도에서 퍼포먼스 차이가 날 수 밖에 없다.
Redis 시스템 구성 방법
- Master-Slave(Replica) 구성
Master는 1개 이상의 Slave를 가질 수 있다.
- Slave는 Master의 데이터를 실시간으로 전달받아 저장한다.
- Master가 다운 시 Slave가 이를 대신할 수 있다.(수동으로 변경해야 함)
- Master-Slave(Replica) + Sentinel 구성
Sentinel을 각 서버에 한 대씩 설치하여 3대 이상의 홀수를 유지한다.
- 센티널은 각 서버를 감시하고 있다가 마스터가 다운되면 과반수가 투표된 슬레이브를 마스터로 승격시킨다.(홀수를 맞춰야 하는 이유)
- 다운되었던 마스터가 다시 시작되면 센티널이 슬레이브로 전환시킨다.
- 레디스는 마스터가 모든 데이터를 가지고있기 때문에 슬레이브는 마스터의 복제본을 유지하고 있을 뿐이며, 데이터를 분산하지 않는다.
- 따라서 장애가 발생하는 동안의 내용들을 완벽히 보장할 수는 없다.
- 센티널은 데이터를 처리하지 않고 감시 역할만 한다.
- 고가용성과 자동 Failover를 지원한다.
- Master-Slave(Replica) + Cluster 구성
서버1의 마스터가 다운되어도 다른 서버의 슬레이브가 서버1의 마스터를 대체한다.
- 여러 노드에 데이터를 분산하여 저장한다(Sharding 방식)
- 고성능, 고가용성, Failover, 확장성이 좋다(스케일 아웃)
- 큰 트래픽이 없다면 오버스펙이 될 수도 있기 때문에, 비용 체크가 필요하다
간단한 Java 환경에서의 Redis 구현
1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
@Controller
@RequestMapping("/redis")
public class cashing {
// redis ip
@Value("${redis.host}")
private String redisHost;
// redis port
@Value("${redis.port}")
private int redisPort;
// redis db name
@Value("${redis.masterName}")
private String redisMasterName;
// redis db password
@Value("${redis.password}")
private String redisPassword;
private static JedisPoolConfig jedisPoolConfig = null;
private static JedisPool pool = null;
// redis 연결한다
private void connect() {
if(jedisPoolConfig == null){
jedisPoolConfig = new JedisPoolConfig();
}
if(pool == null){
pool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 3000, redisPassword, 5);
}
}
// db에 데이터를 저장한다
@RequestMapping(value = "set", method = {RequestMethod.POST})
public void set(@RequestBody String request){
connect();
Jedis jedis = pool.getResource();
JSONObject jsonobj = new JSONObject(request);
String redisKey = jsonobj.getString("key");
JSONObject json = new JSONObject();
json.put("redirect_uri", jsonobj.get("redirect_uri"));
json.put("id", jsonobj.get("id"));
json.put("pw", jsonobj.get("pw"));
json.put("name", jsonobj.get("name"));
jedis.set(redisKey, String.valueOf(json));
if (jedis != null) {
jedis.close();
}
}
// db에서 데이터를 가져온다
@RequestMapping(value = "get", method = {RequestMethod.GET})
public void get(HttpServletRequest request){
connect();
Jedis jedis = pool.getResource();
System.out.println(jedis.get(request.getParameter("key")));
if (jedis != null) {
jedis.close();
}
}
// db에서 데이터를 삭제한다
@RequestMapping(value = "del", method = {RequestMethod.DELETE})
public void del(HttpServletRequest request){
connect();
Jedis jedis = pool.getResource();
System.out.println(jedis.del(request.getParameter("key")));
if (jedis != null) {
jedis.close();
}
}
// 만료 시간을 설정하여 해당 시간이 지나면 데이터가 자동으로 삭제된다
@RequestMapping(value = "expire", method = {RequestMethod.POST})
public void expire(@RequestBody String request){
connect();
Jedis jedis = pool.getResource();
JSONObject jsonobj = new JSONObject(request);
String redisKey = jsonobj.getString("key");
JSONObject json = new JSONObject();
json.put("redirect_uri", jsonobj.get("redirect_uri"));
json.put("id", jsonobj.get("id"));
json.put("pw", jsonobj.get("pw"));
json.put("name", jsonobj.get("name"));
jedis.set(redisKey, String.valueOf(json));
jedis.expire(redisKey, 10);
if (jedis != null) {
jedis.close();
}
}
}
상세 코드는 깃허브에 있다.
다음 목표
현재까지 개발하면서 경험한 것들, 개인적으로 공부하면서 알게 된 것들을 정리하였다.
앞으로도 새롭게 배우고 싶거나, 습득한 지식이 있으면 지속적으로 업데이트 할 예정이다.




