ballqs 님의 블로그
[Spring] Redis Cluster localhost에 구현하여 적용 본문
Redis Cluster 란?
데이터의 고가용성과 분산 처리를 위해 Redis가 제공하는 기능이다.
이를 통해 여러 노드에 데이터를 분산 저장하고, 장애 발생 시 자동으로 복구하거나 재구성할 수 있는 클러스터 환경을 제공한다.
Redis Cluster 특징
데이터 샤딩 (Sharding)
Redis Cluster는 데이터를 여러 노드에 분산해서 저정하는 방식이다. 전체 키 공간을 16,384개의 슬롯(Slot)으로 나누고, 각 노드가 이 슬롯 중 일부를 담당한다. 키는 해시 함수에 의해 슬롯 중 하나에 매핑되고, 해당 슬롯을 가진 노드에 저장된다. 이를 통해 여러 노드에 데이터를 분산할 수 있다.
고가용성 (High Availability)
Redis Cluster는 자동으로 장애 조치를 할 수 있다. 노드가 장애를 일으키면, 클러스터 내에 있는 다른 복제 노드(replica)가 자동으로 승격되며 마스터 노드로 전환된다. 이를 통해 서비스 중단 없이 데이터를 사용할 수 있게 한다.
복제 (Replication)
Redis Cluster는 각 마스터 노드마다 하나 이상의 슬레이브 노드(복제본 노드)를 가질 수 있다. 이 복제본 노드는 마스터 노드가 장애가 발생했을 때 자동으로 마스터로 전환될 준비를 합니다. 복제는 데이터 일관성을 높이고 데이터 손실을 방지하는 데 도움이 됩니다.
노드 간 통신 (Gossip Protocol)
Redis Cluster의 노드들은 서로 상태 정보를 교환하기 위해 Gossip 프로토콜을 사용한다. 이를 통해 클러스터 내에서 노드의 상태를 확인하고, 장애가 발생한 노드를 감지하거나 클러스터를 재구성할 수 있다.
파티셔닝과 확장성 (Partitioning & Scalability)
Redis Cluster는 데이터를 여러 노드에 분산시켜 처리하므로, 노드 수를 늘리면 저장 용량과 처리 능력을 확장할 수 있다. 이를 통해 고성능을 요구하는 대규모 시스템에서도 효율적으로 운영할 수 있다.
클라이언트 측 리다이렉션
Redis Cluster는 데이터를 분산 처리하기 때문에, 클라이언트가 요청을 잘못된 노드에 보낼 수 있다. 이때 클라이언트는 MOVED라는 리다이렉션 메시지를 받아서 해당 데이터를 저장하고 있는 올바른 노드로 요청을 다시 보낼 수 있다.
Localhost에서 진행하기
※Window의 경우 우선적으로 WSL가 깔려 있지 않으면 따라하기 어렵다. 또한 redis는 깔려 있다는 전제하에 작성한다.
참조사이트 : 링크
port 별 폴더 생성
mkdir -p {7000..7005}
redis.conf 파일을 port별로 작성
# Redis 인스턴스가 사용할 포트를 설정합니다.
port [port]
# 클러스터 모드를 활성화합니다.
cluster-enabled yes
# 클러스터 노드 정보를 저장할 파일을 지정합니다.
cluster-config-file nodes-[port].conf
# 노드의 타임아웃 시간을 설정합니다.
cluster-node-timeout 5000
# 데이터의 영속성을 위해 AOF(Append Only File) 모드를 활성화합니다.
appendonly yes
redis.conf 실행
redis-server ./[port]/redis.conf
실행 화면
6개의 port에 있는 모든 redis.conf을 실행시켜 준다.
Cluster 생성
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003
127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
이 명령어는 6개의 Redis 노드를 클러스터로 생성하며, 각 마스터 노드에 대해 1개의 슬레이브 노드를 설정합니다. 이 명령어를 실행하면 Redis Cluster가 자동으로 구성됩니다.
Cluster Nodes 확인
redis-cli -p [port] cluster nodes
Nodes 죽여보기
pkill -f "redis-server.*[port]"
7000 포트
7003 포트 슬레이브 -> 마스터 승격
결과 확인
7000 포트를 죽이니까 7003 가 슬레이브에서 마스터로 승격한 것을 확인할수있다.
Spring boot 적용
application.yml 설정
spring:
data:
redis:
cluster:
nodes: 127.0.0.1:7000 , 127.0.0.1:7001 , 127.0.0.1:7002 , 127.0.0.1:7003 , 127.0.0.1:7004 , 127.0.0.1:7005
RedisConfig.java 작성
@Slf4j
@EnableRedisRepositories
@RequiredArgsConstructor
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.cluster.nodes}")
private List<String> nodes;
@Bean
public RedisMessageListenerContainer redisMessageListener(
RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
List<RedisNode> redisNodes = nodes.stream()
.map(node -> {
String[] parts = node.split(":");
return new RedisNode(parts[0], Integer.parseInt(parts[1]));
}).toList();
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
redisClusterConfiguration.setClusterNodes(redisNodes);
return new LettuceConnectionFactory(redisClusterConfiguration);
}
@Bean
public RedissonClient redissonClient() {
final Config config = new Config();
ClusterServersConfig csc = config.useClusterServers()
.setScanInterval(2000)
.setConnectTimeout(100)
.setTimeout(3000)
.setRetryAttempts(3)
.setRetryInterval(1500);
nodes.forEach(node -> csc.addNodeAddress("redis://" + node));
return Redisson.create(config);
}
}
RedisController.java 작성
@RestController
@RequiredArgsConstructor
@RequestMapping("/redis")
@Slf4j
public class RedisController {
private final RedisService redisService;
@PostMapping
public void create(@RequestBody CreateDto createDto) {
redisService.create(createDto);
}
@GetMapping("/{id}")
public void select(@PathVariable String id) {
redisService.select(id);
}
}
RedisService.java 작성
@Slf4j(topic = "StockRedisService")
@RequiredArgsConstructor
@Service
public class RedisService {
private final RedisTemplate<String , Object> redisTemplate;
private final String KEY_NAME = "stock";
public void create(CreateDto createDto) {
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
String key = KEY_NAME + ":" + createDto.getId();
hashOperations.put(key, "id", createDto.getId());
hashOperations.put(key, "name", createDto.getName());
hashOperations.put(key, "cnt", createDto.getCnt());
hashOperations.getOperations().expire(key, 600, TimeUnit.SECONDS);
}
public void select(String id) {
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
String key = KEY_NAME + ":" + id;
Map<String, Object> entries = hashOperations.entries(key);
if (!entries.isEmpty()) {
log.info("Selected stock id: {}", entries.get("id"));
log.info("Selected stock name: {}", entries.get("name"));
log.info("Selected stock cnt: {}", entries.get("cnt"));
}
}
}
insert 결과물
Select 결과물
'코딩 공부 > Spring' 카테고리의 다른 글
[Spring] Spring Batch란? feat.DB 분리 작업 (0) | 2024.11.10 |
---|---|
[Spring] 소나클라우드(SonaCloud)란? (0) | 2024.10.22 |
[Spring] Redis 동시성 문제 Redisson으로 해결! (0) | 2024.10.08 |
[Spring] Redis pub/sub란? (0) | 2024.10.07 |
[Spring] @Async 란? (0) | 2024.10.04 |