D+70 redisDB사용, 카프카 프로젝트 설정

Bku·2024년 4월 13일
0

학원 일기

목록 보기
67/67
post-thumbnail

Rest Client 테스트 방법

화면을 확인하지 않고, 즉 프론트가 완성되지 않아도 백엔드의 controller 함수들이 잘 기능하는 지를 확인할 수 있다.

조회 기능 테스트

함수 옆에 qpi버튼을 누르면 테스트 화면으로 넘어간다. 위 화면에서 재생버튼을 누르면 테스트가 시작된다.테스트에 성공하면 위와같이 결과가 나온다.

CRUD의 모든 함수가 테스트가 가능하다.

저장 기능 테스트

조회 기능은 그냥 재생만 하면 결과가 나오는데, 저장기능은 data를 입력해주어야한다.

  1. Content-Type: application/json 을 적어주어야한다.
  2. 삽입에 들어갈 요소들을 그림과 같이 모두 적어준다.
  3. 결과가 다음과 같이 나온다.

수정 기능 테스트

  1. 수정은 url에 dno가 있어야 해당 객체를 수정한다고 인식한다.
  2. 수정도 마찬가지로 Content-Type: application/json 을 적어주어야한다.
  3. 수정할 내용을 그림과 같이 적어준다.
  4. 수정이 잘 되었다.

삭제 기능 테스트

삭제할 객체의 dno를 url에 입력해주고 실행하면된다.전체 조회에서 730이 없어진 것을 확인했다.

스프링에서 캐싱DB 사용하기

접속자들이 폭주할때 성능이 굉장히 떨어지므로 이럴때 캐싱 DB를 이용해서 해결을 할 수가 있다. 이것은 ram을 사용하여 여기에 저장을 하는 것이기에 휘발성 메모리임에 유의해야한다.

데이터 베이스 종류

  • 관계형 DB : 오라클, my-sql등이 있고 테이블, 인덱스, 컬럼등이 있는 구조이다.
  • No-sql DB: redis DB(캐싱 DB), Mongo DB(문서 DB) 등이 있다. 관계형 DB와는 다르게 테이블이 아닌 Map(redis), json(mongo)로 이루어져있다.

캐싱 DB

단기간에 속도 향상을 하고 싶은 데이터를 메모리에 저장하고 없앤다. 일시적인 데이터 저장을 하고 싶을 경우 사용하면 되고 , 많은 데이터를 저장할 수 없다(메모리 크기만큼만 저장가능). 대신 성능이 굉장히 빠르다.

메모리 데이터 베이스중 하나가 레디스다. 보통 이벤트성 프로그램에 많이 사용한다.

Redis

설치 방법

https://github.com/microsoftarchive/redis/releases 에서 설치가 가능하다.

Redis-x64-3.0.504.msi 파일을 다운로드하면 된다.

redis -> redis-ci파일을 실행하면 cmd창이 뜰것이다. 여기에 "ping"을 입력하면 "PONG"이 뜨면 서버가 잘 작동하는 것이다.
이 숫자가 localhost이다.

redis명령어

서버 가동 & 중지 & 접속 명령어

# 레디스 서버 실행
Redis-server

# 레디스 실행 확인
redis-cli ping
pong  (응답)

# 레디스 서버 중지
redis-cli shutdown  

# localhost:6379접속
redis-cli

# 정보보기
reids-cli info

# 원격접속
redis-cli -h #{호스트명} -p #{포트번호}

CRUD 명령어

127.0.0.1:6379> keys *
(empty list or set)

# set key / value 형태로 저장하기
127.0.0.1:6379>set k_one "one"
OK

127.0.0.1:6379>keys *
1) "k_one"

# mset 여러개의 key / value 형태로 저장하기
127.0.0.1:6379> mset k_two "two" k_tree "tree"
OK

127.0.0.1:6379> keys *
1) "k_tree"
2) "k_one"
3) "k_two"

# setex 소멸시간 지정해서 저장하기
127.0.0.1:6379> setex k_four 10 "four"
OK

#mget 여러개의 key를 조회하기
127.0.0.1:6379> mget k_one k_two
1) "one"
2) "two"

# del 해당 key와 value을 삭제하기
127.0.0.1:6379> del k_tree
(integer) 1

127.0.0.1:6379> keys *
1) "k_one"
2) "k_two"

# keys *검색어*  key 검색하기
127.0.0.1:6379> keys *k*
1) "k_one"
2) "k_two"

#rename key의 이름을 변경하기 rename 기존key 변경할key
127.0.0.1:6379> rename k_one one
OK

127.0.0.1:6379> keys *
1) "one"
2) "k_two"

# flushall 모든 데이터(key와 value)를 삭제
127.0.0.1:6379> flushall
OK

프로젝트생성

로깅설정 및 라이브러리 설정

로깅툴 및 클래스

툴은 전의 프로젝트와 아주 똑같이 했다.
또한 SimpleDms에서 dept관련 파일들을 모두 가져왔다.

라이브러리 가져오기

dependencies {



    //    Todo: jsp taglib 설정 : spring security용
    implementation 'org.springframework.security:spring-security-taglibs'

    //    sql 출력 결과를 보기위한 라이브러리 추가
    implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
    //    오라클 driver (과거: 11버전) -> 19버전용 라이브러리 추가 설치
    //  todo: 오라클 추가 라이브러리( 19c )
    implementation 'com.oracle.database.jdbc:ucp:19.14.0.0'
    implementation 'com.oracle.database.security:oraclepki:19.14.0.0'
    implementation 'com.oracle.database.security:osdt_cert:19.14.0.0'
    implementation 'com.oracle.database.security:osdt_core:19.14.0.0'

    //  todo: Redis : cache server
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.oracle.database.jdbc:ojdbc11'
    annotationProcessor 'org.projectlombok:lombok'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

나머지는 전의 프로젝트와 같지만 redis를 사용하기 위해

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

를 추가해줬다.

Redis spring 연결

properties에서 설정

properties에 그림과 같이 host와 port를 입력해주면된다. 스프링에서 지원하는 것이 아니기에 이름은 아무렇게나 만들어도 된다.

configuration 설정 클래스

@Configuration
public class RedisConfig {
    // 레디스 DB IP 속성
//    private String redisHost = "localhost"; 이렇게 하면 properties 에서의 속성값이 바뀌면 여기서도 바껴야한다. = 유지 보수가 떨어짐
    @Value("${redis.host}")
    private String redisHost;

    // 레디스 DB Port 속성
//    private String redisPort = "6379";
    @Value("${redis.port}")
    private int redisPort;

    // todo : 레디스 DB 접속정보 정의
    // 1) 각 조회에서 사용할 키 이름 설정

    // 2) 만료시간 설정 : 용량이 적기 때문에 오래된 데이터는 틈틈이 삭제되어야한다.

    //    레디스 DB 기본 설정을 위한 함수
    private RedisCacheConfiguration myDefaultCacheConfig(Duration duration) { // duration 에 만료시간을 넣어준다.
        return RedisCacheConfiguration          // 레디스 DB 캐쉬(성능향상) 설정 클래스
                .defaultCacheConfig()           // 아래 만료시간 등의 추가 설정을 기본으로 하겠다는 함수
                .entryTtl(duration)             // 만료시간 설정 함수
                 // java 클래스 -> json(js 객체)[잭슨 패키지가 바꿔준다.] -> json파일을 다식 레디스 DB 용 데이터(키, 값)로 바꿔주는 변환규칙을 설정하는 함수
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }

    // todo : 레디스 DB 상세 설정
    @Bean
    public RedisCacheManager cacheManager() {

        RedisCacheConfiguration cacheConfig = myDefaultCacheConfig(Duration.ofMinutes(10)) // 만료시간을 10분으로 설정한다는 뜻. 참고로 duration 은 분을 설정하는 객체
                .disableCachingNullValues();                                               // 캐싱할때 null 은 제외하겠다는 설정

        return RedisCacheManager.builder(redisConnectionFactory ()) // 레디스 DB 연결 (ip, port)
                .cacheDefaults(cacheConfig)                         // 위의 cacheConfig 변수 적용
                .withCacheConfiguration("depts", myDefaultCacheConfig(Duration.ofMinutes(5))) // depts 키에 대해 만료시간 5분 적용
                .withCacheConfiguration("dept", myDefaultCacheConfig(Duration.ofMinutes(3))) // dept 키에 대해 만료시간 3분 적용
                .withCacheConfiguration("emps", myDefaultCacheConfig(Duration.ofMinutes(5)))
                .withCacheConfiguration("emp", myDefaultCacheConfig(Duration.ofMinutes(3)))
                .build();
    }

    // todo : 레디스 DB 접속 설정하는 함수 : ip, port 설정
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(redisHost, redisPort);

        return new LettuceConnectionFactory(configuration);
    }
}

configuration 설정 클래스를 만들어서 설정을 따로 또 해주어야한다.

이 때 바로 변수에 속성값을 주는 것이 아니라 properties의 속성을 가져와서 변수에 넣어주는 것이 나중에 유지 보수에 유리하다. 값을 바로 넣어준다면 properties의 값이 바뀌면 또 클래스에서 바꿔주어야하므로 효율이 떨어진다.

이것을 가능하게 해주는 어노테이션이 @value이고 여기에 "${poroperties속성}"을 넣어주면된다.

Redis spring 에서 사용

레디스 DB 동작 과정

  • 레디스를 사용하지 않을 때 : 조회함수가 실행 -> DB sql 실행

  • 레디스를 사용할 때 :
    1st 실행 : 조회함수 실행 -> DB sql 실행 -> 레디스 DB 저장 -> 결과를 스프링 전송
    2nd 실행 : 결과가 있으면 바로 스프링으로 전송, 결과가 없으면 다시 1st를 실행

첫 번째 한번은 무조건 db를 거쳐야한다. 왜냐하면 레디스가

레디스 쓰기(Write around) 전략

sql DB 수정 삭제가 일어났을때 레디스에 값이 수정 삭제가 같이 일어나지 않으면 데이터 불일치가 일어난다.

이걸 일치 시키기위해 DB에 데이터가 수정 삭제 될때 레디스의 키가 같이 삭제 되도록 해주는 전략이다. 수정은 새로운 데이터가 들어오니 다시 DB에서 값을 가져와야한다.

service 클래스 어노테이션 추가

@Slf4j
@Service
@EnableCaching // 캐싱 기능 가능하게 해주는 어노테이션이다.
public class DeptService {

    @Cacheable("dept") // redis 의 캐시기능을 사용하게 한다. 함수별로 해주어야한다. 어노테이션 안에 키를 넣어주면
    public Optional<Dept> findById(int dno){// optional 을 하면 null 이 방지된다.
        Optional<Dept> optionalDept
                = deptRepository.findById(dno);
        return optionalDept;
    }
    

결과

시간이 굉장히 빨라지는 것을 확인할 수 있다.

전체조회 페이징 기능 레디스 버그

레디스에서 전체 조회시 페이징 버그가 발생한다. 원인은 pageable인데 이것을 무시하게 해주는 코드를 입력해주어야한다.

버그 보완 RestPage 클래스

@JsonIgnoreProperties(ignoreUnknown = true, value = {"pageable"})
public class RestPage<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestPage(@JsonProperty("content") List<T> content,
                    @JsonProperty("number") int page,
                    @JsonProperty("size") int size,
                    @JsonProperty("totalElements") long total) {
        super(content, PageRequest.of(page, size), total);
    }

    public RestPage(Page<T> page) {
        super(page.getContent(), page.getPageable(), page.getTotalElements());
    }

}
  1. pageable속성때문에 에러가 나는 것이기에 @JsonIgnoreProperties으로 pageable 속성을 무시하게 해준다.

  2. 사용할 것들만 RestPage함수에 넣어주고 이 함수를 return한다.

service 클래스에 전체조회 함수 수정

 @Cacheable("depts")
    public Page<Dept> findAllByDnameContaning(String dname, Pageable pageable){
        Page<Dept> page = deptRepository.findAllByDnameContaining(dname, pageable);
        return new RestPage<>(page); // pageable 무시 객체에 넣어 리턴
    }
  1. @Cacheable 어노테이션을 사용해서 캐시기능을 가능하게 만들어준다음 depts를 키로 주었다.

  2. service의 전체조회 함수의 return값에서 새로운 RestPage생성자인 new RestPage<>(page)를 만들어 page를 넣어주어 return해주면된다. 이렇게 해주면 page가 RestPage로 들어가서 pageable만 빠지고 나머지만 return이 된다.

Redis의 수정과 삭제 기

수정기능은 삭제를 한 다음 다시 데이터를 불러오는 것이기에 사실상 레디스는 조회와 삭제기능만 있다고 보면된다.

Service 클래스 에서 delete함수 수정

 @CacheEvict(value = "dept", key = "#dno")
    public boolean deleteById(int dno){
        if (deptRepository.existsById(dno) == true) {
            deptRepository.deleteById(dno);
            return true;
        }else {
            return false;
        }
    }
  1. @CacheEvict어노테이션으로 레디스에서 삭제기능이 되도록 해준다. key를 이용해서 dno를 인식해 해당 dno의 객체를 지워준다.

Service 클래스에서 update함수 수정

원래 save함수를 사용했지만 레디스에서는 save를 사용하지 않으므로 update함수를 따로 만들어준다.

 @CacheEvict(value = "dept", key = "#dept.dno")
    public Dept update(Dept dept){
        Dept dept2 = deptRepository.save(dept);
        return dept2;
    }
  1. 업데이트도 삭제를 해주는 기능이다. 그래서 @CacheEvict어노테이션을 사용해주어야한다.

  2. 같은 삭제 기능이지만 delete함수가 있는데도 update를 만드는 이유는 delete함수는 DeleteMapping이 실행될때만 실행이 되기에 PutMapping이 일어났을때 실행될 함수를 따로 만들어 주어야한다.

카프카를 사용한 홈쇼핑 만들기

쇼핑몰에서 카프카

msa에서 컨테이너끼리 통신을 할때 카프카를 이용한다.

홈쇼핑에서는 카프카는 소비자와 생산자의 사이를 중계해주고 소식전달 역할도 한다. 신뢰성이 높고 메세지 유실의 위험성도 적다.

또한 일시장애가 생겨서 결제가 안 되더라도 시간이 지나 복구되면 최종적으로 결제가 되도록 하는 기능도 해준다. 이것을 결과적 일관성이라고 한다.

프로젝트 생성

Rest Api

  • 상품 페이지

  • 장바구니 페이지

  • 주문 페이지

  • 결제 페이지

vue 생성하기

프로젝트 폴더에서 cmd를 열고

vue create 파일명

을 실행해준다.

그 뒤 설정을 위와 같이 해주면된다.

스프링 설정

dependncies 가져오기

dependencies {



	//    Todo: jsp taglib 설정 : spring security용
	implementation 'org.springframework.security:spring-security-taglibs'

	//    sql 출력 결과를 보기위한 라이브러리 추가
	implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
	//    오라클 driver (과거: 11버전) -> 19버전용 라이브러리 추가 설치
	//  todo: 오라클 추가 라이브러리( 19c )
	implementation 'com.oracle.database.jdbc:ucp:19.14.0.0'
	implementation 'com.oracle.database.security:oraclepki:19.14.0.0'
	implementation 'com.oracle.database.security:osdt_cert:19.14.0.0'
	implementation 'com.oracle.database.security:osdt_core:19.14.0.0'

	//    todo: apache kafka라이브러리 : 메세징 서버로 마이크로 서비스에선 필수 이다.
	implementation 'org.springframework.kafka:spring-kafka'

	//   model mapper
	implementation 'org.modelmapper:modelmapper:2.4.2'


	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.oracle.database.jdbc:ojdbc11'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

전의 프로젝트에서 가져온 후 이번에는 kafka를 위해 새로운 속성 2개를 추가해줬다.

//    todo: apache kafka라이브러리 : 메세징 서버로 마이크로 서비스에선 필수 이다.
	implementation 'org.springframework.kafka:spring-kafka'

	//   todo : model mapper : entity <->dto를 왔다갔다 하게 해줄 라이브러리 
	implementation 'org.modelmapper:modelmapper:2.4.2'

메세징 서버관련 라이브러리와 enity와 dto를 호환시켜줄 라이브러리를 추가했다.

properties

이것도 전의 프로젝트에서 다 가져온뒤 아래 속성을 추가해줬다.

# batch size 설정 : 연관관계 설정 시 N+1 문제 최소화
#  여러 개의 SELECT 쿼리들을 하나의 IN 쿼리로 만들어줍
spring.jpa.properties.hibernate.default_batch_fetch_size=1000

join쿼리를 어노테이션으로 적용시킬때 발생하는 성능저하를 최소화해주는 속성을 추가해줬다.

또한 카프카 접속 설정도 추가했다.

# todo : 카프카 접속 설정
# consumer bootstrap servers가 따로 존재하면 설정
spring.kafka.bootstrap-servers=localhost:9092

# 식별 가능한 Consumer Group Id -> 소비자 그룹 이름 설정
spring.kafka.consumer.group-id=academy

#오프셋 -> 소비자가 읽은 메세지를 표시해두는 것 -> 이걸 해줘야 중복 전송을 막을 수 있다.
# 설정 : earliest(오래된) / latest(최근), 최초설정이나 갑자기 장애등의 이유로 사라졌을경우 설정을 해주어야한다.
spring.kafka.consumer.auto-offset-reset=earliest

# 데이터를 받아올 때, key/value 를 역직렬화 : json 에서 (key, value) 로  변경시 변환 설정
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer

# producer bootstrap servers 가 따로 존재하면 설정 :(key, value) 에서 json 으로  변경시 변환 설정
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.k

설명은 설정의 주석을 참고하자

도커 파일 다운

docker-compose.yml 파일

version: "3"
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    expose:
      - "2181"
  kafka:
    image: wurstmeister/kafka
    container_name: kafka
    ports:
      - "9092:9092"
    expose:
      - "9092"
    environment:
      KAFKA_LISTENERS: PLAINTEXT://:9092
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

위 내용이 들어가있는 yml파일을
프로젝트 폴더에 넣었다.

이 파일은 도커의 기능을 확장하는 파일로, 우리가 필요한 여러개의 도커 이미지를 하나의 파일만으로 사용할 수 있게 만들어주는 파일이다.

위 경로에서 아래 명령어를 실행해주면 설치가 완료된다.

	docker-compose up

vue 설정

1 .푸트스트랩 태그걸기

2. 부트스트랩 5.2.3버전 업그레이드

 npm install bootstrap@5.2.3

3. 버전호환을 위한 main.js에 import

이걸해줘야 드롭다운 기능을 사용할 수 있다(버전 오류인듯)

import 'bootstrap/dist/js/bootstrap.bundle'
import 'bootstrap/dist/css/bootstrap.min.css' 

4. axios라이브러리 다운로드

npm install axios

5. axios 공통함수 만들기

utils - http-common.js에 공통함수를 만들었다.

import axios from "axios";

export default axios.create({
    baseURL : "http://localhost:8000/api",// 관례적으로 공통url을 "api"로 많이 사용한다.
    headers : {
        "Content-Type": "application/json" // '문서의 종류는 json파일이다'라는 것을 알려주는 것이다.
    }// 문서 종류를 넣는 곳이다. 
}) // 다른 파일에서도 사용할 수 있게 해주는 것

6. 페이징 기능을 위해 bootstrap-vue-3 라이브러리 설치

  npm install bootstrap-vue-3

그리고 페이지 기능을 사용하기 위해 import를 추가했다.

 // TODO: bootstartp vue3 import
        import BootstrapVue3 from 'bootstrap-vue-3'
        // TODO: bootstartp vue3 css import
        import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'

.use(BootstrapVue3)함수도 넣어줬다.

최종 main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'bootstrap/dist/js/bootstrap.bundle'
import 'bootstrap/dist/css/bootstrap.min.css'
// TODO: bootstartp vue3 import
import BootstrapVue3 from "bootstrap-vue-3";
// TODO: bootstartp vue3 css import
import "bootstrap-vue-3/dist/bootstrap-vue-3.css";
createApp(App).use(BootstrapVue3).use(store).use(router).mount('#app')
profile
기억보단 기록

0개의 댓글