[CowAPI] 20. SpringWebFlux + Redis

준돌·2022년 7월 5일
0

오늘의 Cow

목록 보기
25/45

1. 문제

  • "Ai"와 "Ai 정보들을 이용할 대시보드"를 SSE를 이용하여 실시간으로 처리하려고 합니다.

  • publish, subscribe 패턴을 사용하여 대시보드를 publish 하려고 합니다.

  • 동기식으로 처리한다면 subscribe 중 인 다른 클라이언트로 인해 지연이 발생합니다.

  • 비동기로 처리하려고 합니다.

  • 하지만, MySql DB에 저장을 하고 사용한다면 다음과 같은 에러를 발생합니다.

Possibly blocking call in non-blocking context could lead to thread starvation

2. 원인

  • 비동기 처리 context 에서 blocking을 사용하면 thread의 starvation이 발생합니다.
  • thread가 수행가능한 상황임에도 매우 오랜시간 대기할 수 있어 발생하는 문제입니다.

3. 해결방법

  • @Async 어노테이션을 사용합니다.
  • 저는 AI와 대시보드는 잦은 입출력이 있으므로 Redis를 사용하기로 합니다.

4. 코드

// 1. RedisConfig

@Configuration
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String host; // (1)

    @Value("${spring.redis.port}")
    private int port; // (2)

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port); // (3)
    }

    @Bean
    public RedisTemplate<?, ?> redisTemplate() {  // (4)
        RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}
  • (1, 2). redis DB의 host, port 입니다.
  • (3). Factory 패턴을 사용하여 connection을 생성합니다.
  • (4). 리플렉션을 사용하여 유연성을 높입니다.

// 2. Entity

@RedisHash(value = "bashboard", timeToLive = -1L)
public class Dashboard implements Serializable {
    @Id
    private String id;

    private Long totalUser;
    private Long todayUser;
    private Timestamp updatedAt;
}
  • Serializable : 직렬화를 implements 합니다.
  • value : Redis 의 keyspace 값으로 사용됩니다.
  • timeToLive : 만료시간을 seconds 단위로 설정할 수 있습니다. 기본값은 만료시간이 없는 -1L 입니다.
  • @Id : Redis Key 값이 되며 null 로 세팅하면 랜덤값이 설정됩니다.

// 3. Repository, Service

@Service
public class DashboardRedisService {
    @Autowired
    private RedisTemplate<String, Dashboard> dashboardRedisRepository; // (1)
    
    public Dashboard getDashboard(Dashboard dashboard) { // (2)
    	ValueOperations<String, Dashboard> valueOperations = dashboardRedisRepository.opsForValue();
        return valueOperations.get(dashboard.getId());
    }
}
  • (1). RedisConfig에서 사용한 리플렉션 덕분에 유연성이 증가합니다.
  • (2). Dashboard를 저장합니다.

// 4. DashboardService
@Service
public class DashboardService {
	private final DashboardRedisService dashboardRedisService;
    private static final Long refreshTime = 2L;
    
    public Flux<ServerSentEvent<DashboardResponseDto>> publish() { // (1)
    	...
    
    	Dashboard dashboard = dashboardRedisService.getDashboard(); // (2)
        
        ...
        
        return Flux.interval(Duration.ofSeconds(refreshTime))
                	.map(sequence -> ServerSentEvent.<DashboardResponseDto> builder()
                    	.id("/dashboard")
                        .event("periodic-event")
                        .data(dashboardResponseDto)
                        .retry(Duration.ofSeconds(refreshTime))
                        .build());
    }
}
  • (1). 대시보드를 Springboot WebFlux를 사용하여 비동기로 publish합니다.
  • (2). Redis를 사용하여 대시보드를 get 합니다.
profile
눈 내리는 겨울이 좋아!

0개의 댓글