본문 바로가기
업무 기록/API

프로젝트 요약 정리 SpringBoot 와 Redis 연결 및 업데이트

by code2772 2023. 10. 25.

[ 목차 ]

    728x90
    반응형

    들어가며

    회사에서 URL 호출 방식의 API로 짧은 시간에 다량의 내용을 보내는 부분이 있다.
    기존 웹서버는 jsp로 구현된 내용을 이번에 Spring Boot와 Node Expressjs 를 이용하여 DB와 Redis에 새로운 인증 API 키를 갱신하는 기능을 개발하게 되었다.

    초기 Redis 값을 가저와 해당 필요한 부분만 Update 를 하는 방식을 생각하였지만 여러 문제가 있어 Json 형태의 Redis를 읽어와 해당 새로운 키 값을 만들고 가저온 정보에서 해당 필요 내용을 수정해 새로운 key, value를 만들기로 하니 여러 시행착오 끝에 문제를 해결할 수 있었다.

     

    Redis Config

    여기서는 Redis Sentinel 및 Lettuce를 사용하여 Redis 데이터베이스의 고가용성을 확보하고 Java 애플리케이션과 Redis 사이의 효율적인 통신을 지원한다. Redis Sentinel은 장애를 탐지하고 자동 스위치를 수행하여 Redis 클러스터를 무중단으로 유지하는 역할을 하고 있다. Lettuce는 Java 애플리케이션과 Redis 간 통신을 담당하는 클라이언트 라이브러리로, 비동기 I/O 및 스레드 안전성을 강조하며 높은 성능을 제공하고 있다.

    @Configuration
    public class RedisCacheConfig {
    @Value("${spring.redis.cache.database}")
    int redisDatabase;
    
    @Value("${spring.redis.sentinel.password}")
    String redisPassword;
    
    @Value("${spring.redis.sentinel.master}")
    String sentinelMaster;
    
    @Value("${spring.redis.sentinel.nodes}")
    String sentinelNodes;

    먼저 application.properties 에 미리 선언한 코드를 @Vlaue 값으로 받아온다.

     

     @Bean
        @Qualifier("connectionFactory")
    public RedisConnectionFactory connectionFactory() {
         RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration().master(sentinelMaster);
         for (String node : sentinelNodes.split(",")) {
         String split[] = node.split(":");
         sentinelConfig.sentinel(split[0].trim(), Integer.parseInt(split[1]));
         }
         sentinelConfig.setDatabase(redisDatabase);
         sentinelConfig.setPassword(redisPassword);
            final ClientOptions clientOptions =
                    ClientOptions.builder()
                    .disconnectedBehavior(DisconnectedBehavior.REJECT_COMMANDS)
                    .build();
       
            LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
             .clientOptions(clientOptions)
             .commandTimeout(Duration.ofSeconds(5))
                    .shutdownTimeout(Duration.ofMillis(300))
                    .build();
            LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(sentinelConfig, clientConfig);
         return lettuceConnectionFactory;
        }

    connectionFactory 메소드는 Redis Sentinel 을 구성하고 연결하는 부분이다.

     

        @Bean
        public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    
            Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
            serializer.setObjectMapper(objectMapper);
    
            return serializer;
        }

    Redis와 Spring Boot 애플리케이션 간 데이터 교환을 위한 JSON 직렬화 및 역직렬화 메커니즘을 구성하였다. 이를 통해 Redis에 저장된 데이터를 Java 객체로 변환하고 Java 객체를 Redis에 저장할 수 있게 되었다.

     

    Jackson2JsonRedisSerializer: Jackson2JsonRedisSerializer는 JSON 형식의 데이터를 Redis와 상호 작용하기 위한 직렬화 및 역직렬화 작업을 수행하는 데 사용되는 Redis Serializer이다. Redis에 저장된 데이터를 Java 객체로 변환하거나 Java 객체를 JSON 형식의 문자열로 직렬화하여 Redis에 저장할 때 사용된다.

     

    ObjectMapper: ObjectMapper는 Jackson 라이브러리의 핵심 클래스로, Java 객체와 JSON 데이터 간의 변환을 담당한다. 이 설정에서는 ObjectMapper를 생성하고 객체의 모든 속성(필드)에 대한 가시성을 설정하여 모든 필드를 직렬화 대상으로 만드는데 즉, objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY) 코드는 클래스의 모든 필드를 직렬화한다.

     

    serializer.setObjectMapper(objectMapper): 위에서 생성한 ObjectMapper를 Jackson2JsonRedisSerializer에 설정한다. 이렇게 하면 Redis에 저장되는 데이터가 JSON 형식으로 직렬화되며, 역직렬화할 때도 JSON 데이터를 다시 Java 객체로 변환한다

     

    Redis Service

    Redis 서버와의 연결 확인 및 API 키 업데이트를 관리하는 것이다. 이를 통해 애플리케이션은 Redis 데이터를 안전하게 업데이트하고 서버 연결 상태를 모니터링한다.

      @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
        public void checkRedisConnection() {
            try (RedisConnection connection = redisConnectionFactory.getConnection()) {
                String pingResult = new String(connection.ping());
                if ("PONG".equals(pingResult)) {
                    log.info("서버 연결 양호");
                } else {
                    log.info("연결 실패");
                }
            } catch (Exception e) {
                log.error("오류 발생", e);
            }
        }

    try (RedisConnection connection = redisConnectionFactory.getConnection()): Redis 서버와의 연결을 시도한다.

    try-with-resources 구문을 사용하여 연결을 자동으로 닫아준다.

    String pingResult = new String(connection.ping()): Redis 서버에 PING 명령을 보내어 응답을 받는데 "PONG"이라는 응답을 받으면 서버 연결이 정상이라고 판단한다.

     

        private static final Logger log = LoggerFactory.getLogger(RedisService.class);
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        public void updateApiKeyInRedis(String currentApiKey, String newApiKey) {
            checkRedisConnection();
            try {
                String currentRedisKey = "song:apikey:" + currentApiKey;
                String newRedisKey = "song:apikey:" + newApiKey;
    
                log.info("currentApiKey: {}", currentApiKey);
                log.info("newApiKey: {}", newApiKey);
                log.info("currentRedisKey: {}", currentRedisKey);
                log.info("newRedisKey: {}", newRedisKey);
    
                Object existingData = redisTemplate.opsForValue().get(currentRedisKey);
                log.info("existingData: {}", existingData);
    
                if (existingData != null) {
                    ObjectMapper objectMapper = new ObjectMapper();
                    objectMapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false);
    
                    Map<String, Object> userData;
    
                    if (existingData instanceof String) {
                        try {
                            // 기존 데이터를 JSON 형식으로 변환
                            userData = objectMapper.readValue((String) existingData, new TypeReference<Map<String, Object>>() {});
                        } catch (IOException e) {
                            log.error("JSON 파싱 오류", e);
                            return; // JSON 변환에 실패하면 업데이트를 중단합니다.
                        }
                    } else if (existingData instanceof Map) {
                        // 이미 JSON 형식인 경우도 처리
                        userData = (Map<String, Object>) existingData;
                    } else {
                        log.error("기존 데이터 형식이 JSON이 아닙니다");
                        return; // 다른 데이터 형식인 경우 업데이트를 중단합니다.
                    }
    
                    userData.put("apiKey", newApiKey);
    
                    String newApiData = objectMapper.writeValueAsString(userData);
    
                    // 기존 키에서 데이터를 복사하여 새로운 키로 저장
                    redisTemplate.opsForValue().set(newRedisKey, newApiData);
    
                    log.info("API 키 업데이트 및 복사 완료: currentApiKey={}, newApiKey={}", currentApiKey, newApiKey);
                } else {
                    log.error("기존 데이터를 찾을 수 없습니다. currentRedisKey = {}", currentRedisKey);
                }
            } catch (Exception e) {
                log.error("API 키 업데이트 중 오류 발생", e);
            }
        }
    }

     

    updateApiKeyInRedis(String currentApiKey, String newApiKey): Redis에 저장된 API 키를 업데이트하는 메서드이다.

     

    실행순서

    String currentRedisKey = "song:apikey:" + currentApiKey; 및 String newRedisKey = "song:apikey:" + newApiKey;으로 현재 및 새로운 API 키의 Redis 키를 생성한다.

    Object existingData = redisTemplate.opsForValue().get(currentRedisKey);를 사용하여 현재 키에 저장된 데이터를 가져옴

    데이터의 유무를 확인하고, 데이터가 존재하는 경우 JSON 데이터로 변환한다. 데이터가 JSON 형식이 아니거나 변환에 실패하면 업데이트를 중단하고.

    현재 API 키를 새 API 키로 업데이트하고 업데이트된 데이터를 새 키에 저장합니다. 각 단계에서 작업 내용을 로그를 남긴다.

    반응형