캐시란 무엇입니까?
먼저 캐시의 정의를 알아봐야 할 것 같습니다.
캐쉬런 데이터나 값이 미리 복사되어 저장되는 임시 위치입니다.
.
그렇다면 캐시를 사용하는 이유는 무엇입니까?
캐시를 사용하는 이유는 무엇입니까?
동일한 정보를 반복적으로 읽거나 데이터에 액세스하는 데 시간이 오래 걸리는 경우사용을 고려할 수 있습니다.
가져오는 데이터를 문자 그대로 복사하고 캐시할 수 있으며 호출될 때마다 캐시에 저장된 데이터를 빠르게 가져올 수 있습니다.
데이터에 접근하는데 시간이 오래 걸리더라도 미리 데이터를 복사하여 캐시에 저장하고, 호출 시 캐시에 저장된 데이터를 가져와서 사용한다.
이렇게 하면 I/O가 줄어들고 프로젝트 성능이 향상됩니다.
그러나 캐시를 사용할 때 고려해야 할 몇 가지 사항이 있습니다.
프로젝트는 캐시 유무에 관계없이 동일한 예상 동작을 가져야 합니다.
이제 코드 예제를 살펴보겠습니다.
이번에 고려한 시나리오는 온라인 서점에서 책 정보를 검색할 때 같은 책을 여러 번 검색하는 상황을 만들 것입니다.
예상되는 결과는 처음 요청된 책만 리포지토리에 액세스하여 데이터를 검색하고 후속 요청은 사용을 위해 캐시에서 데이터를 검색한다는 것입니다.
필요한 기본 코드 작성
# 책.자바
@NoArgsConstructor
@AllArgsConstructor(staticName = "of") // .of()로 Book 객체를 만들 수 있도록 해줍니다.
@Data
public class Book {
private String title;
private String author;
private String publishedDate; // 0000-00-00 형태의 문자열로 입력받음
}
BookRepository에서 제목, 저자 및 출판일을 받아 저장합니다.
# 책 저장소.자바
@NoArgsConstructor
@Repository
public class BookRepository {
private Map<String, Book> bookShelf = new HashMap<>();
public void enrollBookInfo(String title, String author, String publishedDate) {
bookShelf.put(title, Book.of(title, author, publishedDate));
}
public Book getBook(String title) {
System.out.println(title + "을 찾기 위해서 데이터베이스에 접근....");
if (bookShelf.keySet().contains(title)) {
return bookShelf.get(title);
}
throw new RuntimeException("찾는 도서는 존재하지 않습니다.
");
}
}
등록책정보()도서를 등록할 수 있는 방법입니다.
getBook()에 찾고 있는 책이 없으면 RuntimeException을 생성하도록 설계했습니다.
캐시는 Repository에 따로 들어오지 않기 때문에 Service를 통해서라면 리포지토리에 접근할 때 “제목을 찾기 위해 데이터베이스에 접근하는 중…”이라는 메시지가 표시됩니다.
나는 그것을했다.
# 예약서비스.자바
@RequiredArgsConstructor
@Service
public class BookService {
private final BookRepository bookRepository;
@PostConstruct
public void init() {
// 도서 4개를 등록합니다.
bookRepository.enrollBookInfo("자바는 정석이다", "김정석", "2022-12-12");
bookRepository.enrollBookInfo("파이썬은 정석이다", "이정석", "2012-06-13");
bookRepository.enrollBookInfo("자바스크립트는 정석이다", "박정석", "2020-02-14");
bookRepository.enrollBookInfo("C는 정석이다", "정정석", "2015-01-11");
}
public void printBookInfo(String title) {
try {
Book book = bookRepository.getBook(title);
System.out.println("도서명: " + book.getTitle() + " 저자: " + book.getAuthor() + " 출간일: " + book.getPublishedDate());
} catch (RuntimeException exception) {
System.out.println(exception.getMessage());
}
}
}
@PostConstruct 주석을 사용하여 BookService의 생성자가 실행된 직후에 4권의 책을 등록합니다.
이렇게 등록하면 BookRepository에 책이 등록됩니다.
그리고 조회에 사용될 때 printBookInfo()를 사용하여 인쇄할 수 있도록 했습니다.
이때 Repository에서 발생하는 RuntimeException에 대한 예외를 처리한다.
# 메인애플리케이션.자바
@RequiredArgsConstructor
@SpringBootApplication
public class MainApplication {
private final BookService bookService;
public static void main(String() args) {
SpringApplication.run(MainApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class) // <-- 모든 빈이 다 등록이 끝났을 때의 이벤트를 말함
public void run() {
bookService.printBookInfo("자바는 정석이다");
bookService.printBookInfo("자바는 정석이다");
bookService.printBookInfo("자바는 정석이다");
bookService.printBookInfo("자바는 정석이다");
}
}
DI는 BookService를 수신하고 BookRepository에서 “Java is the standard” 책을 검색합니다.
연속 4회 작업을 쿼리하는 작업입니다.
출력 결과를 확인해 봅시다.
출력을 보면 “Java를 찾기 위해 데이터베이스에 액세스하는 것이 표준입니다…”라는 메시지가 4번 나타납니다.
즉, “Java is the norm”이라는 책을 4번 검색하기 위해 BookRepository에 4번 액세스했음을 의미합니다.
이제 캐시를 사용하여 처음으로만 액세스할 수 있도록 BookRepository를 구성해 보겠습니다.
캐시가 성공적으로 작동한 경우 예상되는 결과는 다음과 같습니다.
Java는 표준을 찾기 위해 데이터베이스에 액세스합니다….
도서명 : Java is the Standard 저자 : 김정석 출간일 : 2022-12-12
도서명 : Java is the Standard 저자 : 김정석 출간일 : 2022-12-12
도서명 : Java is the Standard 저자 : 김정석 출간일 : 2022-12-12
도서명 : Java is the Standard 저자 : 김정석 출간일 : 2022-12-12
레디스 설치
레디스 홈페이지운영체제별로 설치하는 방법이 있습니다.
이 부분은 다루지 않겠습니다.
저는 맥 M1을 사용하고 있어서 홈브류로 간단하게 설치했습니다.
Redis 실행(터미널)
# Redis 서버 실행
>> redis-server –loglevel 상세 정보
이를 입력하면 로컬에서 Redis 서버를 성공적으로 연 것입니다.
–loglevel verbose는 디버그와 유사한 자세한 로그 기록을 출력합니다.
예를 들어 서버 접속 이력과 캐시 등록 이력이 모두 표시됩니다.
원하는 경우 특정 로그 수준만 출력하도록 설정할 수 있습니다.
# Redis 클라이언트와 연결
열린 서버에 클라이언트로 접속해 봅시다.
>> redis-cli
Redis 서버에 클라이언트로 접속하면 Redis 서버 측에 히스토리가 남아있는 것을 확인할 수 있습니다.
# Redis에 등록된 캐시(키) 데이터 확인
>> 키 *
이 명령은 등록된 모든 키를 인쇄할 수 있습니다.
결과는 (빈 배열)이어야 합니다.
아직 캐시를 사용하도록 설정하지 않았습니다.
이제 프로젝트로 돌아가서 Redis를 사용하도록 설정하겠습니다.
Redis 종속성 추가
# build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
해당 Redis 종속성을 종속성에 추가합니다.
아 참고로 저는 Spring Boot 2.5.2 버전과 Java 11 버전을 사용하고 있습니다.
Spring Boot 버전이 매우 낮다면 해당 버전이 Redis를 지원하는지 확인해야 합니다.
대부분 2.5.2 이상을 사용하고 있는 것으로 판단됩니다.
어느 부분에 캐시를 찾아 적용
캐시를 적용해야 하는 부분은 Repository에 접근하여 책 정보를 가져오는 곳에 캐시를 적용해야 합니다.
찾아보면 BookRepository의 getBook() 메서드가 그 역할을 담당하고 있습니다.
그런 다음 BookRepository의 getBook() 메서드에 캐시를 적용해 보겠습니다.
# BookRepository.java의 getBook()
@Cacheable("book")
public Book getBook(String title) {
System.out.println(title + "을 찾기 위해서 데이터베이스에 접근....");
if (bookShelf.keySet().contains(title)) {
return bookShelf.get(title);
}
throw new RuntimeException("찾는 도서는 존재하지 않습니다.
");
}
@캐시 가능() 주석에 의해 적용된 캐시. 캐시에서 사용할 키 이름은 “book”으로 설정됩니다.
# @EnableCaching을 MainApplication.java에 적용
@EnableCaching
@RequiredArgsConstructor
@SpringBootApplication
public class MainApplication {
private final BookService bookService;
public static void main(String() args) {
SpringApplication.run(MainApplication.class, args);
}
@EventListener(ApplicationReadyEvent.class) // <-- 모든 빈이 다 등록이 끝났을 때의 이벤트를 말함
public void run() {
bookService.printBookInfo("자바는 정석이다");
bookService.printBookInfo("자바는 정석이다");
bookService.printBookInfo("자바는 정석이다");
bookService.printBookInfo("자바는 정석이다");
}
}
캐시를 사용하기 위해 첨부해야 하는 주석입니다.
Configuration bean에 붙일 수도 있지만 저는 캐시 관련 Config 설정이 없기 때문에 MainApplication에 붙여넣었습니다.
이제 프로그램을 다시 실행해 봅시다.
실행되지만 직렬화할 수 없다고 합니다.
Redis에 캐시 데이터를 저장할 때 우리가 만든 Book 클래스는 Redis의 기본 Serialize 메서드를 사용하여 직렬화되어 저장됩니다.
이것은 의미 즉, 캐시 데이터로 저장할 Book 클래스는 직렬화 가능한 클래스여야 합니다.
하다.
코드를 수정합시다.
# 책.자바
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
@Data
public class Book implements Serializable {
private String title;
private String author;
private String publishedDate;
}
Serializable 인터페이스를 구현하고 직렬화할 수 있는 클래스로 만들었습니다.
이제 MainApplication을 다시 시작하겠습니다.
우리가 원하는 결과를 얻었습니다!
데이터를 얻기 위해 데이터베이스에 처음 접속했을 때만, 그 다음에는 데이터베이스가 아닌 캐시에서 데이터를 가져와서 출력했습니다.
이제 Redis 클라이언트로 이동하여 성공적으로 등록되었는지 확인하겠습니다.
키가 성공적으로 등록되었습니다.
책이 핵심이고 (::)으로 구분하고 뒤따르는 부분은 “Java is standard”라는 문자열을 인코딩하는 문자열 형태인 것 같습니다.
처음부터 영어로 설정했으면 좋았을 것 같은데 이 부분은 추후 리팩토링에서 추가적으로 다루도록 하겠습니다.
Redis를 사용하여 프로젝트 캐싱을 완료했습니다.
하지만 더 많은 코드를 리팩터링해 보겠습니다.
추가 리팩토링 작업
첫 번째 저장할 캐시 데이터의 양을 설정해 보겠습니다.
이는 성능과도 관련될 수 있기 때문에 중요한 기능입니다.
캐시도 너무 많이 저장되고 관리하지 않으면 서버에 부하가 걸립니다.
두번째 직렬화 방법 변경안돼. 지금은 Redis의 기본 설정을 사용하고 있지만 우리에게 더 친숙한 JackSon 라이브러리를 사용하여 직렬화 방법을 변경해 보겠습니다.
그러면 출력이 더 좋아 보일 것입니다.
캐시 데이터 보존 기간 설정
Redis에 대한 구성을 사용자 지정하려면 @Configuration 주석이 있는 구성 클래스를 생성해야 합니다.
# CustomRedisConfig.java
@EnableCaching
@Configuration
public class CustomRedisConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(5)); // <-- 5초 뒤에 캐시 데이터가 삭제됩니다.
}
}
MainApplication에 등록한 @EnableCaching 주석을 가져옵니다.
이제 @Configuration이 존재하므로 여기에 붙여넣을 수 있습니다.
RedisCacheConfiguration을 사용자 지정 Redis 구성을 위한 빈으로 등록하다.
우리가 설정하고자 하는 TTL(Time To Live, 데이터 유효 기간)을 5초로 설정한 후 인스턴스를 Spring Bean으로 등록할 수 있도록 반환합니다.
이제 MainApplication을 다시 실행하고 “Java is standard”가 5초 후에 사라지는지 확인합니다.
그 전에 해야 할 일이 있습니다.
이전 테스트 중에 등록된 캐시 데이터를 지워야 합니다.
.
>> 플러쉬
해당 명령을 사용하여 모든 캐시 데이터를 지웁니다.
그런 다음 * 키로 삭제가 성공했는지 확인합니다.
그런 다음 MainApplciation을 다시 한 번 실행해 보겠습니다.
처음으로 keys *를 실행하면 위와 같이 데이터가 등록된 것을 확인할 수 있고, 5초 후에 다시 key *를 실행하면 데이터가 사라진 것을 확인할 수 있다.
여전히 내 말을 믿지 못한다면 Redis 서버에서 로그를 확인할 수도 있습니다.
직렬화 방법 변경
이 부분도 Configuration에서 RedisCacheConfiguration의 설정을 변경하여 bean에 등록할 수 있습니다.
아까 사용했던 bean에 추가로 적용해보자.
# CustomRedisConfig.java
import org.springframework.data.redis.serializer.RedisSerializationContext;
@EnableCaching
@Configuration
public class CustomRedisConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(5))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}
이와 같이 . 연산자가 너무 많아 가독성이 떨어지는 경우 정적 가져오기를 사용하여 다음과 같이 정리할 수 있습니다.
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
@EnableCaching
@Configuration
public class CustomRedisConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(100))
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}
이제 Redis의 Value 직렬화 방법을 Jackson2로 변경했습니다.
MainApplication을 다시 실행해 봅시다.
캐시 TTL을 설정하면 Redis에 캐시 데이터가 남지 않으나, 있다면 모두 삭제하고 재시작하시기 바랍니다.
Serialize가 적용되었는지 확인하기 위해 TTL을 100초로 변경해 보겠습니다.
5초만에 등록하자마자 거의 바로 삭제되기 때문에 확인이 어렵습니다.
음… 연재되지 않았습니다.
여기에 한 가지 문제가 있었습니다.
구성에서 직렬화 방법을 변경했습니다.
따라서 이전에 Book 클래스에 연결된 Serializable 인터페이스의 구현을 삭제해야 합니다.
# 책.자바
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
@Data
public class Book {
private String title;
private String author;
private String publishedDate;
}
추가적으로 JackSon을 사용할 때 @NoArgsConstructor를 사용하여 빈 생성자를 생성해야 합니다.
정상적으로 작동합니다.
이제 MainApplication을 다시 시작하겠습니다.
한글은 이렇게 인코딩되어 표시되는데, 제목, 저자, 출판일 등의 객체가 잭슨을 이용하여 JSON 형식으로 직렬화되어 있는 것을 볼 수 있습니다.
추가: Redis에서 한국어 인코딩 방법 변경
Redis에서 한글을 보려면 다음 명령으로 클라이언트를 실행하십시오.
>> redis-cli –raw
결과를 보면 “Java is standard”라는 문자열이 잘 나오는 것을 볼 수 있다.
JSON 타입으로 직렬화된 데이터도 잘 확인됩니다.
좋아요~