AWS ElasticCache Redis 를 생성해놓고 어디에 쓸지 째려보기만한지 어언 한달...
드디어 결단의 시간이 왔다.
Redis를 사용해서 사이트 일일방문자수를 확인하는데 사용하려고 한다.
왜 Redis를 사용하냐면, 실시간으로 접속하는 모든 방문자를 캐치하여 즉시 DB에 저장하는 것은 DB Connection과 I/O를 많이 발생시켜 비효율적으로 보였기 때문이다.
따라서, Redis라는 캐시를 두어 실시간으로 접속하는 방문자를 캐시에 저장해두고 일정시간마다 DB에 저장하여 DB I/O를 줄여보고자 한다.
로직은 다음과 같이 작성하려고 한다.
1. 특정 경로로 온 요청에 대해 방문자 Interceptor 작동.
2. Redis에 ip_date 를 key값으로 존재여부 확인 후 저장.
3. Scheduler를 통해 일정시간마다 Redis에 쌓여있는 방문자 데이터를 DB에 저장한 후 Redis clear.
간단명료하다.
자, 이제 시작해보자. ㄱㅈㅇ~
환경설정
먼저, 의존성 추가를 통해 redis 라이브러리를 사용할 수 있도록 하자.
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.7.7'
그리고 본인이 설치한 Redis Server 접속정보를 작성하자.
application.yml
spring:
redis:
port: 6379
host: your_ip
이제 RedisTemplate를 Bean에 등록하여 Spring환경에서 주입받아 사용할 수 있도록 셋팅하자.
RedisConfig.java
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
Interceptor 생성
이제 방문자들의 데이터를 Redis에 저장할 수 있는 Interceptor를 작성하자.
SingleVisitInterceptor.java
@Component
@RequiredArgsConstructor
public class SingleVisitInterceptor implements HandlerInterceptor {
private final RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String userIp = request.getRemoteAddr();
String userAgent = request.getHeader("User-Agent");
String today = LocalDate.now().toString();
String key = userIp + "_" + today;
ValueOperations valueOperations = redisTemplate.opsForValue();
if (!valueOperations.getOperations().hasKey(key)) {
valueOperations.set(key, userAgent);
}
return true;
}
}
*주의사항
여기서 주의해야할 사항이 있는데, 본인처럼 Nginx나 기타 Proxy를 사용할 경우 RemoteAddr이 127.0.0.1로 받아오게 된다. 아래와 같은 방어코드를 통해 Ip를 추출할 수 있다.
public String getIp(HttpServletRequest request){
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
본인은 Spring Security를 사용하기 때문에 아래의 Config에 Interceptor를 추가해줬다.
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig implements WebMvcConfigurer {
private final SingleVisitInterceptor singleVisitInterceptor;
...
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(singleVisitInterceptor)
.addPathPatterns("/mylocation/**");
}
}
Redis-Cli를 통해 데이터가 제대로 저장되는지 확인까지 완료.
redis-cli # cli가 없을 경우 설치 선행필요
keys *
Scheduler를 통해 Redis와 DB 반영
이제 Redis에 쌓인 방문자 데이터를 일정 시간마다 DB에 저장하여 동기화하는 작업을 하면 마무리가 된다.
방문자를 관리할 수 있는 Entity를 생성하여 50분마다 DB에 저장해보자.
VisitorEntity.java
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "tb_visitor")
public class VisitorEntity {
@Id
@GeneratedValue
private Long id; // 기본키
private String userIp;
private String userAgent;
private LocalDate date;
}
VisitorRepository.java
public interface VisitorRepository extends JpaRepository<VisitorEntity, Long> {
boolean existsByUserIpAndDate(String userIp, LocalDate date);
}
VisitorScheduler.java
@Component
@Slf4j
@RequiredArgsConstructor
public class VisitorScheduler {
private final RedisTemplate<String, String> redisTemplate;
private final VisitorRepository visitorRepository;
@Scheduled(initialDelay = 3000000, fixedDelay = 3000000)
public void updateVisitorData() {
Set<String> keys = redisTemplate.keys("*_*");
for (String key : keys) {
String[] parts = key.split("_");
String userIp = parts[0];
LocalDate date = LocalDate.parse(parts[1]);
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
String userAgent = valueOperations.get(key);
if(!visitorRepository.existsByUserIpAndDate(userIp, date)){
VisitorEntity visitor = VisitorEntity.builder()
.userAgent(userAgent)
.userIp(userIp)
.date(date)
.build();
visitorRepository.save(visitor);
}
redisTemplate.delete(key);
}
}
}
위와 같은 작업을 통해 50분마다 DB에 Redis의 방문자 데이터가 반영이 되고 Redis는 Clear가 된다.
이제 일일 테이블에 쌓인 데이터를 통해 일일방문자를 체크할 수 있게 되었다.
이번 작업을 통해 Redis를 아주 겉표면만 찍먹해볼 수 있는 좋은 기회였다.
점점 딥하게 사용하며 조금씩 지식도 쌓아나가보자.
끗!
'server > 🟥Redis' 카테고리의 다른 글
AWS[Elasti Cache] ElastiCache for Redis란? 노드/샤드/클러스터 (0) | 2023.01.14 |
---|---|
AWS[Elasti Cache] - Redis 생성 및 Ec2에서 접속 (0) | 2023.01.12 |
댓글