Skip to content

Commit ad239fb

Browse files
chapakookjhpark
andauthored
Redis 모듈 및 infra-compose 추가 (#6)
* README.md 에 :modules:redis 추가 * master 전용 connectionFactory, redisTemplate 추가 * redis testUtils 추가 (TestContainers, CleanUp util) * :apps:commerce-api 에 :modules:redis 의존성 추가 * redis 모듈 : application.yml -> redis.yml * /modules/redis 모듈 추가 (master - readonly replica 기반) * infra-compose.yml 에 redis master/readonly 연결정보 추가 --------- Co-authored-by: jhpark <jhpark@ioisoft.com>
1 parent b1e618c commit ad239fb

12 files changed

Lines changed: 260 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ Root
2828
├── apps ( spring-applications )
2929
│ └── 📦 commerce-api
3030
├── modules ( reusable-configurations )
31-
│ └── 📦 jpa
31+
│ ├── 📦 jpa
32+
│ └── 📦 redis
3233
└── supports ( add-ons )
3334
├── 📦 monitoring
3435
└── 📦 logging

apps/commerce-api/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
dependencies {
22
// add-ons
33
implementation(project(":modules:jpa"))
4+
implementation(project(":modules:redis"))
45
implementation(project(":supports:jackson"))
56
implementation(project(":supports:logging"))
67
implementation(project(":supports:monitoring"))
@@ -17,4 +18,5 @@ dependencies {
1718

1819
// test-fixtures
1920
testImplementation(testFixtures(project(":modules:jpa")))
21+
testImplementation(testFixtures(project(":modules:redis")))
2022
}

apps/commerce-api/src/main/resources/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ spring:
2020
config:
2121
import:
2222
- jpa.yml
23+
- redis.yml
2324
- logging.yml
2425
- monitoring.yml
2526

docker/infra-compose.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,55 @@ services:
1414
volumes:
1515
- mysql-8-data:/var/lib/mysql
1616

17+
redis-master:
18+
image: redis:7.0
19+
container_name: redis-master
20+
ports:
21+
- "6379:6379"
22+
volumes:
23+
- redis_master_data:/data
24+
command:
25+
[
26+
"redis-server", # redis 서버 실행 명령어
27+
"--appendonly", "yes", # AOF (AppendOnlyFile) 영속성 기능 켜기
28+
"--save", "",
29+
"--latency-monitor-threshold", "100", # 특정 command 가 지정 시간(ms) 이상 걸리면 monitor 기록
30+
]
31+
healthcheck:
32+
test: ["CMD", "redis-cli", "-p", "6379", "PING"]
33+
interval: 5s
34+
timeout: 2s
35+
retries: 10
36+
37+
redis-readonly:
38+
image: redis:7.0
39+
container_name: redis-readonly
40+
depends_on:
41+
redis-master:
42+
condition: service_healthy
43+
ports:
44+
- "6380:6379"
45+
volumes:
46+
- redis_readonly_data:/data
47+
command:
48+
[
49+
"redis-server",
50+
"--appendonly", "yes",
51+
"--appendfsync", "everysec",
52+
"--replicaof", "redis-master", "6379", # replica 모드로 실행 + 서비스 명, 서비스 포트
53+
"--replica-read-only", "yes", # 읽기 전용으로 설정
54+
"--latency-monitor-threshold", "100",
55+
]
56+
healthcheck:
57+
test: ["CMD", "redis-cli", "-p", "6380", "PING"]
58+
interval: 5s
59+
timeout: 2s
60+
retries: 10
61+
1762
volumes:
1863
mysql-8-data:
64+
redis_master_data:
65+
redis_readonly_data:
1966

2067
networks:
2168
default:

modules/redis/build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
plugins {
2+
`java-library`
3+
`java-test-fixtures`
4+
}
5+
6+
dependencies {
7+
api("org.springframework.boot:spring-boot-starter-data-redis")
8+
9+
testFixturesImplementation("com.redis:testcontainers-redis")
10+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.loopers.config.redis;
2+
3+
4+
import io.lettuce.core.ReadFrom;
5+
import org.springframework.beans.factory.annotation.Qualifier;
6+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.context.annotation.Primary;
10+
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
11+
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
12+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
13+
import org.springframework.data.redis.core.RedisTemplate;
14+
import org.springframework.data.redis.serializer.StringRedisSerializer;
15+
16+
import java.util.List;
17+
import java.util.function.Consumer;
18+
19+
@Configuration
20+
@EnableConfigurationProperties(RedisProperties.class)
21+
public class RedisConfig{
22+
private static final String CONNECTION_MASTER = "redisConnectionMaster";
23+
public static final String REDIS_TEMPLATE_MASTER = "redisTemplateMaster";
24+
25+
private final RedisProperties redisProperties;
26+
27+
public RedisConfig(RedisProperties redisProperties){
28+
this.redisProperties = redisProperties;
29+
}
30+
31+
@Primary
32+
@Bean
33+
public LettuceConnectionFactory defaultRedisConnectionFactory() {
34+
int database = redisProperties.database();
35+
RedisNodeInfo master = redisProperties.master();
36+
List<RedisNodeInfo> replicas = redisProperties.replicas();
37+
return lettuceConnectionFactory(
38+
database, master, replicas,
39+
b -> b.readFrom(ReadFrom.REPLICA_PREFERRED)
40+
);
41+
}
42+
43+
@Qualifier(CONNECTION_MASTER)
44+
@Bean
45+
public LettuceConnectionFactory masterRedisConnectionFactory() {
46+
int database = redisProperties.database();
47+
RedisNodeInfo master = redisProperties.master();
48+
List<RedisNodeInfo> replicas = redisProperties.replicas();
49+
return lettuceConnectionFactory(
50+
database, master, replicas,
51+
b -> b.readFrom(ReadFrom.MASTER)
52+
);
53+
}
54+
55+
@Primary
56+
@Bean
57+
public RedisTemplate<String, String> defaultRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
58+
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
59+
return defaultRedisTemplate(redisTemplate, lettuceConnectionFactory);
60+
}
61+
62+
@Qualifier(REDIS_TEMPLATE_MASTER)
63+
@Bean
64+
public RedisTemplate<String, String> masterRedisTemplate(
65+
@Qualifier(CONNECTION_MASTER) LettuceConnectionFactory lettuceConnectionFactory
66+
) {
67+
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
68+
return defaultRedisTemplate(redisTemplate, lettuceConnectionFactory);
69+
}
70+
71+
72+
private LettuceConnectionFactory lettuceConnectionFactory(
73+
int database,
74+
RedisNodeInfo master,
75+
List<RedisNodeInfo> replicas,
76+
Consumer<LettuceClientConfiguration.LettuceClientConfigurationBuilder> customizer
77+
){
78+
LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = LettuceClientConfiguration.builder();
79+
if(customizer != null) customizer.accept(builder);
80+
LettuceClientConfiguration clientConfig = builder.build();
81+
RedisStaticMasterReplicaConfiguration masterReplicaConfig = new RedisStaticMasterReplicaConfiguration(master.host(), master.port());
82+
masterReplicaConfig.setDatabase(database);
83+
for(RedisNodeInfo r : replicas){
84+
masterReplicaConfig.addNode(r.host(), r.port());
85+
}
86+
return new LettuceConnectionFactory(masterReplicaConfig, clientConfig);
87+
}
88+
89+
private <K,V> RedisTemplate<K,V> defaultRedisTemplate(
90+
RedisTemplate<K,V> template,
91+
LettuceConnectionFactory connectionFactory
92+
){
93+
StringRedisSerializer s = new StringRedisSerializer();
94+
template.setKeySerializer(s);
95+
template.setValueSerializer(s);
96+
template.setHashKeySerializer(s);
97+
template.setHashValueSerializer(s);
98+
template.setConnectionFactory(connectionFactory);
99+
return template;
100+
}
101+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.loopers.config.redis;
2+
3+
public record RedisNodeInfo(
4+
String host,
5+
int port
6+
) { }
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.loopers.config.redis;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
5+
import java.util.List;
6+
7+
@ConfigurationProperties(value = "datasource.redis")
8+
public record RedisProperties(
9+
int database,
10+
RedisNodeInfo master,
11+
List<RedisNodeInfo> replicas
12+
) { }
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
spring:
2+
data:
3+
redis:
4+
repositories:
5+
enabled: false
6+
7+
datasource:
8+
redis:
9+
database: 0
10+
master:
11+
host: ${REDIS_MASTER_HOST}
12+
port: ${REDIS_MASTER_PORT}
13+
replicas:
14+
- host: ${REDIS_REPLICA_1_HOST}
15+
port: ${REDIS_REPLICA_1_PORT}
16+
17+
---
18+
spring.config.activate.on-profile: local, test
19+
20+
datasource:
21+
redis:
22+
master:
23+
host: localhost
24+
port: 6379
25+
replicas:
26+
- host: localhost
27+
port: 6380
28+
29+
---
30+
spring.config.activate.on-profile: dev
31+
32+
---
33+
spring.config.activate.on-profile: qa
34+
35+
---
36+
spring.config.activate.on-profile: prd
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.loopers.testcontainers;
2+
3+
import com.redis.testcontainers.RedisContainer;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.testcontainers.utility.DockerImageName;
6+
7+
@Configuration
8+
public class RedisTestContainersConfig {
9+
private static final RedisContainer redisContainer = new RedisContainer(DockerImageName.parse("redis:latest"));
10+
11+
static {
12+
redisContainer.start();
13+
}
14+
15+
public RedisTestContainersConfig() {
16+
System.setProperty("datasource.redis.database", "0");
17+
System.setProperty("datasource.redis.master.host", redisContainer.getHost());
18+
System.setProperty("datasource.redis.host.port", String.valueOf(redisContainer.getFirstMappedPort()));
19+
System.setProperty("datasource.redis.replicas[0].host", redisContainer.getHost());
20+
System.setProperty("datasource.redis.replicas[0].port", String.valueOf(redisContainer.getFirstMappedPort()));
21+
}
22+
}

0 commit comments

Comments
 (0)