Redis is an open-source in-memory datastore used as a database, cache, and even a message broker.
레디스는 DB, 캐시, 메세지 브로커로 쓰일 수 있는 인메모리 데이터 저장소임
-NoSQL DB
-Key-Value DB
Key-Value DB는 항상 고유한 단순 문자열(Key)과 임의의 큰 데이터 필드(Value)를 포함함
Value에 대한 포인터와 함께 고유한 Key를 저장하기 위해 해시 테이블을 구현한다
Value은 정수, JSON, 배열 etc...
Key는 해당 값을 참조하는 데 사용됨
오직 Key를 이용해서만 질의를 할 수 있다~
refresh token 같이 단시간 다량의 엑세스를 필요로 하는 도메인에 적용해보면 좋다
사전에 도커가 설치되어 있어야 한다
docker run --name some-redis -d -p 6379:6379 redis redis-server --requirepass 1234
컨테이너 이름은 some-redis 이고
베이스 OS의 6379 포트로 접근하면 접근할 수 있고
redis-server 비밀번호는 1234로 설정했다
실제 서비스한다면 패스워드와 더불어 포트도 변경해주는 게 좋음.. 구글링해본 결과 클라우드 환경에 레디스가 설치된 케이스에 대부분 해킹 시도가 있다고 한다
docker ps
항목 중 특히 PORTS 부분이 아래와 같이 설정한대로 잘 맵핑되었나 확인되어야 한다
0.0.0.0:6379->6379/tcp
레디스가 잘 설치된 걸 확인
docker exec -it some-redis redis-cli
위와 같이 레디스 내부 데이터를 살펴보려고 하면
(error) NOAUTH Authentication required.
가 발생함
이때 아까 설정해주었던 password로 접속 가능함
AUTH 1234
bulid.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.7.7'
application.yml
spring:
redis:
port: 6379
host: localhost
password: 1234
timeout: 3000
이중 timeout
은 password가 설정된 경우 레디스 DB가 액티브 상태가 아니면
서버가 에러 코드를 보내야 하는데, 이 시간이 굉장히 오래걸렸기 때문에 설정해둠
대부분 redis는 메인 DB로 사용하지 않으니까 timeout도 함께 설정해두는 것을 추천한다
RedisConfig.java
SpringBoot 2.0이상이라면, 아래 코드이면 된다
@Configuration
@EnableCaching
public class RedisConfig {
}
@EnableCaching
은 캐싱을 사용하겠다는 의미임
SpringBoot 2.0이하라면,
해당 파일 내부에 RedisConnectionFactory
정의해서 직접 빈 주입해줘야함
@EnableRedisRepositories
어노테이션도 추가로 붙여줘야함
@Configuration
@EnableCaching
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
configuration.setPassword(password);
return new LettuceConnectionFactory(configuration);
}
}
RefreshToken.java
@RedisHash
: Redis 도메인을 선언함@Id
: Id는 해당 도메인의 기본 Key가 된다@Indexed
: Id 외에 다른 변수를 Key로 설정하고 싶을 때 사용한다@Getter
@Builder
@RedisHash("RefreshToken")
public class RefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Indexed
private String username;
private String tokenValue;
private String updateAt;
public static RefreshToken of(String username, String tokenValue){
return RefreshToken.builder()
.username(username)
.tokenValue(tokenValue)
.updateAt(LocalDateTime.now().toString())
.build();
}
}
Redis는 SQL과 달리 DATE or TIMESTAMP 자료형이 없어 @TimeStamp나 @CreatedDate 등등 사용이 불가했다
그래서 생성 시점 시간을 직접 String으로 변환해서 담아줌
RefreshTokenRepository.java
@Indexed
나 @Id
변수를 이용해서 쿼리 작성해야함public interface RefreshTokenRepository extends CrudRepository<RefreshToken, Long> {
Optional<RefreshToken> findByUsername(String username);
}
UserService.java
@Service
public class UserService {
private final UserRepository userRepository;
private final RefreshTokenRepository refreshTokenRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtUtil jwtUtil;
public TokenDto loginUser(SigninForm form) {
User user = userRepository.findByUsername(form.getUsername())
.orElseThrow(()->new IllegalArgumentException("유저가 존재하지 않음"));
if(passwordEncoder.matches(form.getPassword(), user.getPassword())){
TokenDto dto = jwtUtil.generateToken(user.getUsername());
refreshTokenRepository.save(RefreshToken.of(user.getUsername(),dto.getRefreshToken()));
return dto;
}else{
throw new IllegalArgumentException("패스워드가 다름");
}
...
}
Redis Cache를 활용하는 부분
@Cacheable
: 캐싱을 활용할것임을 명시한다
-key
: 레포지토리에서 사용했던 key 변수
-cacheNames
: 캐싱의 대상이 되는 value, 즉 도메인
@Service
public class UserService {
...
@Cacheable(key = "#username", cacheNames = "RefreshToken")
public String readToken(String username) {
RefreshToken token = refreshTokenRepository.findByUsername(username)
.orElseThrow(()->new IllegalArgumentException("해당 토큰이 존재하지 않음"));
return token.getTokenValue();
}
...
}
기타 Redis와 크게 상관은 없지만 참고용,,
UserController.java
@RestController
public class UserController {
...
@PostMapping("/auth/signin")
public ResponseEntity loginUser(@RequestBody SigninForm form){
return ResponseEntity.ok(userService.loginUser(form));
}
@GetMapping("/auth/read")
public ResponseEntity readToken(@RequestParam String username){
return ResponseEntity.ok(userService.readToken(username));
}
...
}
SigninForm.java
@Getter
@Setter
public class SigninForm {
private String username;
private String password;
}
localhost:8080/auth/signin
redis-cli
접속
keys *
hgetall Token:8502679460442746444
localhost:8080/auth/read?username=haden
-첫번째 request
-두번째 request
Response 부분에 Time을 보면 반복적으로 read를 요청했을 때,
326ms > 25 ms로 시간이 확 줄어듦
캐싱도 잘 되고 있음을 확인!