Redis란?
- Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 DBMS.
- 인메모리 방식의 데이터 저장소로, 일반적인 DB에 비해 속도가 빠르다.
- String, Set, Sorted Set, Hash, List와 같이 다양한 데이터 타입을 지원한다.
- Single Thread 구조이기 때문에 처리 시간이 긴 요청이 들어올 경우 해당 요청을 처리할 때 까지 다른 요청도 응답을 받을 수 없다.
- Master Redis 서버의 데이터를 Slave Redis 서버에 복제할 수 있다.
로컬에서 Redis는 Docker환경에 구축하기로 하였다.
docker run --name redis_server -it -d -p 6379:6379 redis
실행후
docker logs -f redis_server
로그확인
Redis가 정상적으로 실행됨을 확인 할 수 있다.
다음은 Redis와 연동하기 위한 SpringBoot 설정이다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Build.gradle에 라이브러리를 추가한다.
spring:
cache:
type: redis
redis:
host: 127.0.0.1
port: 6379
redis 접속정보와 이번 예제에선 redis에 데이터가 저장된것을 확인하고 해당 데이터를 캐시로 사용할것이기 때문에 캐시타입을 레디스로 application.yml에 명시한다.
이제 스프링부트에 redisconfig파일에 redis와 연동할 빈을 등록한다.
@Configuration
@RequiredArgsConstructor
@EnableCaching
public class RedisConfig {
private final RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(redisProperties.getHost(),redisProperties.getPort());
}
@Bean
public CacheManager cacheManager() {
RedisCacheManagerBuilder builder =
RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory());
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
.disableCachingNullValues()
.entryTtl(Duration.ofMinutes(30L));
builder.cacheDefaults(configuration);
return builder.build();
}
@Bean
public RedisTemplate<String,Object> redisTemplate(){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer()); // Key: String
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); // Value: 직렬화에 사용할 Object 사용하기
return redisTemplate;
}
}
Redis 구현체로는 Jedis와 Lettuce가 있는데, Lettuce가 성능이 더 좋아 많이 사용된다고 한다.
@EnableCaching 어노테이션을 통해 캐싱 기능 사용 등록하고 레디스캐시매니저를 등록한다.
예제이후 자료구조형태로 레디스에 데이터가 저장되는지 테스트하기위해 레디스템플릿역시 bean으로 등록해둔다.
테스트를 하기위해 컨트롤러와 서비스에 예제소스를 구성한다.
@GetMapping("/member/{memberId}")
public ResponseEntity<?> getMemberInfo(@PathVariable("memberId") Long memberId) {
return ResponseEntity.ok(redisMemberService.getMemberInfo(memberId));
}
@PostMapping("/join")
public ResponseEntity<?> joinMember(@RequestBody Map<String, String> memberInfo) {
Member member = new Member();
member.setName(memberInfo.get("name"));
redisMemberService.join(member);
return ResponseEntity.ok("가입 완료");
}
@PutMapping("/update")
public ResponseEntity<?> updateMember(@RequestBody Map<String, String> memberInfo) {
System.out.println(memberInfo);
Member member = new Member();
member.setId(Long.parseLong(memberInfo.get("id")));
member.setName(memberInfo.get("name") + "update");
redisMemberService.update(member);
return ResponseEntity.ok("수정 완료");
}
@DeleteMapping("/member/{memberId}")
public ResponseEntity<?> deleteMember(@PathVariable("memberId") Long memberId) {
redisMemberService.removeMember(memberId);
return ResponseEntity.ok("삭제 완료");
}
간단한 CRUD작업이다.
@Service
@RequiredArgsConstructor
@Transactional
public class RedisMemberService {
private final RedisMemberRepository redisMemberRepository;
public void join(Member member){
redisMemberRepository.save(member);
}
@CachePut(value = "Member", key = "#member.id", cacheManager = "cacheManager")
public Member update(Member member){
redisMemberRepository.save(member);
return redisMemberRepository.findById(member.getId()).get();
}
@Cacheable(value = "Member", key = "#memberId", cacheManager = "cacheManager",unless = "#result == null")
public Member getMemberInfo(Long memberId){
return redisMemberRepository.findById(memberId).get();
}
@CacheEvict(value = "Member", key = "#memberId", cacheManager = "cacheManager")
public void removeMember(Long memberId){
redisMemberRepository.delete(redisMemberRepository.findById(memberId).get());
}
}
서비스 레이어의 경우 레디스캐시매니저를 이용해 캐싱한다.
@Entity
@Getter
@Setter
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
}
간단한 Member Entitiy를 만들어 data jpa로 테스트하도록한다.
먼저 간단히 회원가입을 하고
Get메서드로 해당 사용자의 정보를 불러온다.
해당 SELECT 쿼리가 db로 날아간걸 콘솔로 확인 할 수 있다.
여기서 재차 사용자정보를 조회하게되면
쿼리가 날아가지 않는다 이유는 레디스에 캐싱이 적용되었기 때문이다 (서비스 레이어에서)
그렇기 때문에 처음 조회한 이후에 레디스에 해당 사용자의 키로 데이터가 저장되어야한다.
Redlis-cli로 확인해보자
docker exec -it redis_server redis-cli
명령어로 접속한다.
keys * 명령어로 저장된 키들을 확인 할 수 있다.
방금 조회한 사용자의id로 Member라는 캐시이름으로 데이터가 저장되었다 해당 데이터의 상세정보는 get 명령어로 확인 할 수 있다.
사용자의 정보가 정상적으로 redis에 등록되었다.
사용자정보를 업데이트 할 경우
db뿐만아니라 redis에도 해당 업데이트가 적용된다.
삭제 또한 db뿐만아니라 redis에서도 삭제된다.
이번엔 객체를 redis에 저장하는게 아닌 자료구조를 저장하는 테스트를 구성해보았다.
@GetMapping("/templateTest")
public void templateTest(){
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.set("testKey","testValue");
}
@GetMapping("/templateListTest")
public void templateListTest(){
ListOperations<String, Object> stringObjectListOperations = redisTemplate.opsForList();
stringObjectListOperations.rightPush("listTest","h");
stringObjectListOperations.rightPush("listTest","e");
stringObjectListOperations.rightPush("listTest","l");
stringObjectListOperations.rightPush("listTest","l");
stringObjectListOperations.rightPush("listTest","o");
stringObjectListOperations.rightPushAll("listTest"," ","w","o","r","l","d");
String character_1 = (String) stringObjectListOperations.index("listTest", 1);
System.out.println("character_1 = " + character_1);
Long size = stringObjectListOperations.size("listTest");
System.out.println("size = " + size);
List<Object> ResultRange = stringObjectListOperations.range("listTest", 0, 10);
System.out.println("ResultRange = " + Arrays.toString(ResultRange.toArray()));
}
@GetMapping("/templateHashTest")
public void templateHashTest(){
String key = "testHash";
HashOperations<String, Object, Object> stringObjectObjectHashOperations = redisTemplate.opsForHash();
stringObjectObjectHashOperations.put(key, "Hello", "helloVal");
stringObjectObjectHashOperations.put(key, "Hello2", "helloVal2");
stringObjectObjectHashOperations.put(key, "Hello3", "helloVal3");
Object hello = stringObjectObjectHashOperations.get(key, "Hello");
System.out.println("hello = " + hello);
Map<Object, Object> entries = stringObjectObjectHashOperations.entries(key);
System.out.println("entries = " + entries.get("Hello2"));
Long size = stringObjectObjectHashOperations.size(key);
System.out.println("size = " + size);
}
일반적인 키밸류,리스트,맵 형태가 모두 잘 저장되고 불러올수 있다.
'Spring' 카테고리의 다른 글
@Conditional과 @AutoConfiguration (0) | 2023.11.06 |
---|---|
SpringBoot AutoConfiguration (0) | 2023.11.06 |
SpringBoot와 웹서버 (0) | 2023.10.31 |
인터셉터 설정 (0) | 2023.10.05 |
전역예외처리 (1) | 2023.10.05 |