// netstat 없으면 net-tools 설치
sudo apt install net-tools
// 6379 포트 쓰는지 확인
netstat -nlpt | grep 6379
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN -
tcp6 0 0 ::1:6379 :::* LISTEN -
4. redis에 접속 후 테스트
// 접속
redis-cli
// 테스트
127.0.0.1:6379> set test1 testvalue
OK
get test1
127.0.0.1:6379> get test1
"testvalue"
| Docker 로 Redis 이미지 생성하기
1. Ubuntu에 Docker 설치
sudo docker apt install docker.io
2. Docker 로그인 하고 Docker 서비스가 실행되는지 확인하기
// 로그인
sudo docker login -u <아이디>
Password : <패스워드 작성 후 엔터>
// 실행확인
service docker status
3. 기존의 Redis-server 끄고, Docker에서 Redis image 생성하기
// 일단 redis-server 끄고
sudo systemctl stop redis-server
// 서비스 꺼진거 확인하고
service --status-all
// docker에서 image 생성하면서 실행
sudo docker run -it --name <이미지명> -p 6379:6379 -d redis
4. 컨테이너 확인하기
sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b037231966f6 redis "docker-entrypoint.s…" 23 minutes ago Created zero-mall-product-redis
- Look aside Cache, Write Back - Memcahced, Redis API
1. Cache의 배경과 목적
- 파레토의 법칙에 따르면, 80%의 결과는 20%의 원인에 의해 발생한다.
- 다시말해, 사람들이 자주 쓰는 데이터는 정해져 있고 이를 캐싱해서 저장하면 DB에 접근할 필요가 없어진다.
- 서비스를 런칭하고 사용자가 늘어나면 그만큼 DB 작업량도 늘어나기 마련이라 한다.
- 이럴 때 캐시를 사용한다면, 성능을 개선할 수 있다.
2. Cache의 사용 방식
- 캐시는 조회 또는 쓰기를 할 때에 사용될 수 있다.
Look Aside Cache (Lazy Loading)
캐시를 한 번 접근하여 데이터의 존재 유무에 따라 바로 반환 또는 DB (or API) 호출
Write Back
쓰기(Insert) 작업이 많을 때 쿼리를 모아서 배치(Batch)처리
(1) Look aside Cache
- 한 번 조회한 데이터를 캐시에 저장 후, 동일한 데이터를 조회할 때,
DB에 direct로 접근하지 않고, Look aside(주의를 돌려) 캐시의 내용을 확인한다.
- Process
- 사용자가 웹서버에 요청을 보낸다.
- 웹서버는 Cache를 먼저 조회한다.
- Cache에 데이터가 있다면, 사용자에게 해당 데이터를 반환(Cache Hit)하고,
Cache에 데이터가 없다면, DB에서 데이터를 조회한 후 해당 데이터를 반환(Cache Miss)한다.
(2) Write Back
- 캐시 안에 일시적으로 저장한 후 한 번에 모아 배치 처리를 하는 것을 말한다.
- 하지만 Write Back을 사용하면 캐시 안의 데이터가 중간에 유실될 수 있기 때문에 사용에 주의가 필요하다.
(3) 그 외에도 Write Through 등등이 있다.
| Redis
REDIS : Remote (외부에 있는) Dictionary (Key-Value형태) Server (서버)
1. Redis란?
한줄 요약
특징
Redis
Key-value 형태의 값을 저장할 수 있는 In-Memory 저장소
- Key-Value 형식의 NoSql - In-memory - 다양한 데이터 타입 제공 - 주로 캐시로 사용 - 싱글 쓰레드 - Atomic
- Redis는 인메모리 기반의 data structure store로,db/cache/message broker, streaming engine으로 사용될 수 있다. * 많은 개발자들은 Redis를 Store가 아닌Cache라고 분류한다. * Redis는 지속성을 보장하기 위해 데이터를 In-Memory 곧, Disk에 저장할 수 있다.
* 단, In-memory지만 영구적으로 저장할 수 있기도 하다.
- Redis는 NoSQL로 분류되며, Key-Value 형태로 데이터를 저장한다. - Redis는 문자열, 해쉬, 리스트, Set, sorted Set 형태의 다양한 자료구조를 지원한다. - Redis는 기본적으로 Single-Threaded하며, 자료구조는 Atomic 한 성질(=원자성, All or nothing)을 가지고 있어
race condition이 일어나는 것일 예방할 수 있다는 장점을 가지고 있다.
2. Redis의 다양한 모드
종류
서버
특징
Single
단일서버
HA미지원
Sentinel
여러서버
Master-slave* 방식, HA지원*
Cluster
* HA(High Availability) : 서버 하나가 죽더라도 나머지 서버들이 서비스를 계속 운영할 수 있도록 하는 전략
* Master-Slave
- Master : 한 서버에는 하나의 Master노드가 있다.
-Slave : Master노드에 대한 복사본
- Cluster 모드에서는, 여러 서버가 있다고 전제할 때, 각 서버의 Master의 복제본은 다른 서버의 Slave에 저장한다.
이렇게 할 경우, 서버가 다 죽고 하나만 남는다고 하더라도 운영에 지장이 생기지 않게된다.
1번서버
2번서버
3번서버
Master
Master1
Master2
Master3
Slave
Master2 복제본
Master1 복제본
Master1 복제본
Slave
Master3 복제본
Master3 복제본
Master2 복제본
| Redis 설치하기
** 참고사항 : embeded-redis를 사용할 경우, 아래와 같이 별도로 redis를 설치하지 않고도, 스프링 build.gradle에 라이브러리를 추가하는 것만으로도 redis를 사용할 수 있다.
(3) @SpringBootApplication 클래스에 @EnableCaching을 추가
@SpringBootApplication
@EnableScheduling
@EnableCaching
public class BaedanguemApplication {
public static void main(String[] args) {
SpringApplication.run(BaedanguemApplication.class, args);
}
}
2. RedisConnectionFactory 및 CacheManager Bean 생성
(1) RedisConnectionFactory 빈 생성
- 사용하는 모드에 따라, Configuration인스턴스를 생성한 후, host, port, password 등을 setting한다.
- 이후 LettuceConnectionFactory에 해당 configuration을 넣어 반환하면 빈 생성이 완료된다.
@Configuration
@RequiredArgsConstructor
public class CacheConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
// Single 모드
RedisStandaloneConfiguration conf = new RedisStandaloneConfiguration();
// Cluster 모드
// RedisClusterConfiguration conf = new RedisClusterConfiguration();
conf.setHostName(host);
conf.setPort(port);
return new LettuceConnectionFactory(conf);
}
}
* C:\Program Files\Redis 의 redis.windows-service.conf 파일에서 port, required password 등을 설정할 수 있다.
(2) CacheManager 빈 생성
- RedisConnectionFactory를 매개변수로 담은 메소드를 생성한다. (반환 값은 CacheManager)
- RedisCacheConfiguration.defaultCacheConfig()에 key, value에 대한 Serialization 방식을 설정한다.
!! Redis에 data 또는 Object를 저장하기 위해 직렬화(Serialization)를 통해 data, object를 Byte 단위로 변경한다.
* Redis는 Java 시스템 외부의 캐시 서버이기 때문에, 외부에 데이터를 저장하기 위해서는 직렬화가 필요하다.
* 마찬가지로 Redis에서 데이터를 가져와 data, object로 변환할 때에 역직렬화를 사용한다.
- RedisCacheManager.RedisCacheManagerBuilder를 통해
매개변수의 RedisConnectionFactory와, 바로 위에서 작성한 RedisCacheConfiguration을 담아 반환한다.
@Configuration
@RequiredArgsConstructor
public class CacheConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory){
RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(conf)
.build();
}
@Bean
public RedisConnectionFactory redisConnectionFactory(){
// Single 모드
RedisStandaloneConfiguration conf = new RedisStandaloneConfiguration();
// Cluster 모드
// RedisClusterConfiguration conf = new RedisClusterConfiguration();
conf.setHostName(host);
conf.setPort(port);
return new LettuceConnectionFactory(conf);
}
}
@Service
class XXXService{
// 사전 고려사항 :
// (1) 자주 요청되는 데이터인가? 그렇다면 캐싱
// (2) 변경이 잦은 데이터인가? 그렇다면 캐싱x
// redis server의 key-value와 다른 개념
@Cacheable(key = "#companyName", value = "finance")
public ScrapedResult getDividendByCompanyName(String companyName) {
// 메소드 내용
}
}
ㄴ value 값을 일일히 입력하지 않고, 상수화하여 사용할 수도 있다.
package com.example.baedanguem.model.constants;
public class CacheKey {
public static final String KEY_FINANCE = "finance";
}
(2) Http 요청을 보낸 후 결과 확인하기
- 수업에서는 Serialization 관련하여 InvalidDefinitionException 오류가 발생했다.
- 원인 : LocalDateTime 타입의 데이터를 캐시에 저장하기 위해 직렬화 하는 과정에서 타입 미스매치 발생
- 3.에서 직/역직렬화를 StringRedisSerializer가 담당하고 있었기 때문에 LocalDateTime은 직렬화할 수 없다.
- 해결 방법 : 아래 확인
(3) 웹서버<->캐시 data 직렬화, 역직렬화 과정에서 타입 미스매치 시
- 수업에서는 Json 타입의 데이터를 송수신 하기 때문에 @JsonSerialize와 @JsonDeserialize를 통해
직렬화/역직렬화 방식을 지정해주었다.
@Data
@Builder
public class Dividend {
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime date;
private String dividend;
}
>> 이 상태에서, 프로그램을 실행한 후, 컨트롤러를 통해 최초로 데이터를 조회할 경우, 정상적으로 결과값을 받을 수 있다.
>> 그리고 redis-cli.exe에서 keys * 를 입력해 캐싱이 잘 되었는지 확인하면 결과는 아래처럼 나타난다.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.baedanguem.model.Company` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"{"@class":"com.example.baedanguem.model.ScrapedResult","company":{"@class":"com.example.baedanguem.model.Company","ticker":"MMM","name":"3M Company"},"dividends":["java.util.ArrayList",[{"@class":"com.example.baedanguem.model.Dividend","date":[2016,11,16,0,0],"dividend":"1.11"},{"@class":"com.example.baedanguem.model.Dividend","date":[2017,2,15,0,0],"dividend":"1.175"},{"@class":"com.example.baedanguem.model.Dividend","date":[2017,5,17,0,0],"dividend":"1.175"},{"@class":"com.example.baedanguem.m"[truncated 1906 bytes]; line: 1, column: 115] (through reference chain: com.example.baedanguem.model.ScrapedResult["company"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.12.5.jar:2.12.5]
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1764) ~[jackson-databind-2.12.5.jar:2.12.5]
(4) 추가적인 내용
- 스프링 프로그램을 종료하더라도, 캐시 서버에는 데이터가 남아있다.
- @Cacheable 메소드 안에 log를 남길 경우, 처음 DB에서 데이터를 조회할 때 콘솔에 로그가 남지만,
캐시에서 데이터를 조회할 때에는 콘솔에 log가 남지 않는다.
4. 레디스 캐시 삭제
- 데이터가 업데이트 되었음에도 캐시에 이전 데이터가 남아다면, 이전 조회기록을 가져오게 된다.
- 따라서, 해당 캐시의 내용을 업데이트하거나, 해당 내용을 삭제하는 것이 필요하다.
- 더불어, 캐시 공간 또한 한계가 있기 때문에 오래된 캐시 데이터는 삭제하는 것이 필요하다.
* 수업에서는, 매일 00시에 데이터를 받아 업데이트를 진행하기 때문에, 스케줄러 메소드에 작업을 했다.
(1) @CacheEvict를 사용하여 특정 value 의 데이터를 삭제한다.
- 앞서서 캐시한 조회 메소드에 @Cacheable(key = "#companyName", value = "finance") 설정을 했었다.
- @CacheEvict의 value에 설정했던 "finance"를 넣어주고, allEntries = true 를 하면 "finance"로 캐시된 모든 데이터를 삭제한다.
@CacheEvict(value = CacheKey.KEY_FINANCE, allEntries = true)
@Scheduled(cron = "${scheduler.scrap.yahoo}")
public void yahooFinanceScheduling() {
// 내용
}
- 만약, 특정 데이터만 삭제하고 싶다면, key값에 해당 데이터의 key값을 넣어주면 된다.
@CacheEvict(value = CacheKey.KEY_FINANCE, key = "someCompany")
@Scheduled(cron = "${scheduler.scrap.yahoo}")
public void yahooFinanceScheduling() {
// 내용
}
(2) @CacheEvict를 붙인 스케줄러에 @EnableCaching을 추가한다.
- 스케줄러는 Main 메소드와 다른 쓰레드를 사용한다.
- 스케줄러가 동작할 때마다, @EnableCaching이 이루어질 수 있도록 추가한다.
@Component
@AllArgsConstructor
@EnableCaching
@Slf4j
public class ScraperScheduler {
@CacheEvict(value = CacheKey.KEY_FINANCE, allEntries = true)
@Scheduled(cron = "${scheduler.scrap.yahoo}")
public void yahooFinanceScheduling() {
// 내용
}
}
(3) TTL (Time to Live) 설정하기
- TTL : 데이터의 유효기간
- 앞에서 CacheConfig작성시 RedisCacheConfiguration을 작성했던 부분에 TTL 기간을 추가한다.
@Configuration
@RequiredArgsConstructor
public class CacheConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory){
RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofDays(30)); // TTL 시간 지정
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(conf)
.build();
}
}
- 이탈리아의 한 해커가 MySQL 로 작업 중 DB 처리 속도가 너무 느린 것을 보고 레디스를 만들었다. - REDIS : Remote (외부에 있는) Dictionary (Key-Value형태) Server (서버) - 공식문서에 따르면, Redis는 인메모리 기반의 data structure store로, db/cache/message broker, streaming engine으로 사용될 수 있다고 한다. * 단, 많은 개발자들은 Redis를 Store가 아닌 Cache라고 분류한다. * Redis는 지속성을 보장하기 위해 데이터를 Disk에 저장할 수 있다. - Redis는 NoSQL로 분류되며, Key-Value 형태로 데이터를 저장하는데, Value 타입으로 문자열, 해쉬, 리스트, Set, sorted Set 형태의 다양한 자료구조를 지원한다. - Redis는 기본적으로 Single-Threaded이며, 자료구조는 Atomic 한 성질을 가지고 있다. * 서로 다른 트랜잭션에 대한 Read/Write를 동기화한다.
- 언제 쓸까? > 싱글 서버일 때 : Atomic 자료구조 + 캐시 기능 사용 ㄴ 이메일 인증 기능 ㄴ 좋아요, 조회수 처리 ㄴ 랭킹 보드로 사용 ㄴ 유저 API 제한 : ex. 주문 시 재고 확인 * 캐시 : 여기서의 캐시란, 나중에 할 요청 처리를 미리 저장해 두었다가 빠르게 서비스해주는 걸 말한다.
- 주의할 점 > 싱글 쓰레드 서버이므로 시간 복잡도를 고려해야한다. > 인-메모리 특성상 메모리 파편화, 가상 메모리 등의 이해가 필요하다.