앞선 포스팅에서 WebFlux의 코드, DB 등 동기, 블럭킹으로 작성되면 WebFlux의 장점을 잃고 MVC와 다를게 없는 성능을 보여준다고 말했었습니다.
그래서 이번에는 redis-reactive를 통해서 WebFlux에서 redis를 사용하는 간단한 실습을 진행할려고합니다.
실습 코드는 깃허브 주소에서 확인하실 수 있습니다.
1. redis-reactive, embedded redis 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'
implementation 'it.ozimov:embedded-redis:0.7.2'
...
}
2. redis의 문자열의 key value를 사용하도록 Bean 등록
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class BasicRedisConfig {
@Primary
@Bean
ReactiveRedisOperations<String, String> redisOperations(ReactiveRedisConnectionFactory factory) {
RedisSerializer<String> serializer = new StringRedisSerializer();
RedisSerializationContext<String, String> serializationContext = RedisSerializationContext
.<String, String>newSerializationContext()
.key(serializer)
.value(serializer)
.hashKey(serializer)
.hashValue(serializer)
.build();
return new ReactiveRedisTemplate<>(factory, serializationContext);
}
}
3. test 및 로컬 test를 위한 embedded redis 설정
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import redis.embedded.RedisServer;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Configuration
public class EmbeddedRedisConfig {
@Value("${spring.redis.port}")
private int redisPort;
private RedisServer redisServer;
@PostConstruct
public void redisServer() {
redisServer = new RedisServer(redisPort);
redisServer.start();
}
@PreDestroy
public void stopRedis() {
redisServer.stop();
}
}
4. handler 작성
- loadData(): 10만개의 데이터 로드 메서드
- findReactorList(): 비동기, 논블로킹 방식의 메서드
- findNormalList(): 동기, 블로킹 방식의 메서드
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Component
@RequiredArgsConstructor
public class BasicHandler {
private final ReactiveRedisConnectionFactory factory;
private final ReactiveRedisOperations<String, String> reactiveRedisOperations;
private final RedisTemplate<String , String> stringStringRedisTemplate;
private static final AtomicInteger count = new AtomicInteger(0);
public void loadData() {
List<String> data = new ArrayList<>();
IntStream.range(0, 100000).forEach(i -> data.add(UUID.randomUUID().toString()));
Flux<String> stringFlux = Flux.fromIterable(data);
factory.getReactiveConnection()
.serverCommands()
.flushAll()
.thenMany(stringFlux.flatMap(uid -> reactiveRedisOperations.opsForValue()
.set(String.valueOf(count.getAndAdd(1)), uid)))
.subscribe();
}
public Flux<String> findReactorList() {
return reactiveRedisOperations
.keys("*")
.flatMap(key -> reactiveRedisOperations.opsForValue().get(key));
}
public Flux<String> findNormalList() {
return Flux.fromIterable(Objects.requireNonNull(stringStringRedisTemplate.keys("*"))
.stream()
.map(key -> stringStringRedisTemplate.opsForValue().get(key))
.collect(Collectors.toList()));
}
}
5. router 등록
- 등록시 bean이름이 다른 router와 겹치지 않게 조심해야함. 동일 이름의 bean 2개 만들려고하면서 실패하게 됨
import com.webflux.study.handler.BasicHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
@RequiredArgsConstructor
public class BasicRouter {
private final BasicHandler basicHandler;
@Bean
public RouterFunction<ServerResponse> basicRoute() {
return RouterFunctions.route()
.GET("/reactive-list", serverRequest -> ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM)
.body(basicHandler.findReactorList(), String.class))
.GET("/normal-list", serverRequest -> ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM)
.body(basicHandler.findNormalList(), String.class))
.GET("/load", serverRequest -> { basicHandler.loadData(); return ServerResponse.ok()
.body(BodyInserters.fromValue("Load Data Completed")); })
.build();
}
}
이후 /load -> normal-list -> reactive-list 순으로 이동하면 WebFlux에 대해서 보다 이해 가능할 것으로 생각이 듭니다.
'Spring Webflux' 카테고리의 다른 글
Spring Webflux 간단 개념 정리 및 실습 (2) | 2021.06.24 |
---|