AWS 소프트웨어 아키텍처 설계

1. 클라우드 환경 소프트웨어 아키텍처 목표 이미지


본 절에서는 아마존 AWS 클라우드 환경에서 소프트웨어 아키텍처 목표 이미지에 대해 설명한다.

 

아마존 AWS 클라우드 환경에서 소프트웨어 아키텍처 목표 이미지는 아마존 AWS 환경에서 가장 클라우드 환경에 최적화된 시스템을 구축했을 때 소프트웨어 아키텍처 모습이라고 판단된다.

 

아마존 AWS 환경에 최적화된 시스템은 아마존 클라우드 Managed서비스를 많이 적용한 시스템 이라기보다는 클라우드 환경에 맞는 아키텍처와 사상을 가진 시스템일 것이다. 예를 들어 서버리스 아키텍처 서비스 람다를 적용했다고 클라우드 네이티브 시스템이 아니라 그러한 서비스를 사용하지 않았다 하더라도 클라우드 사상에 더 가깝다면 클라우드 네이티브 어플리케이션인 것이다.

 

클라우드 네이티브에 가깝다고 보여지는 클라우드 환경 특징은 아래와 같다.

  • Web 서버와 Application 서버의 경우 대용량 서버보다는 저용량 서버로 부하 분산하고 필요에 따라 Scale-out하는 구성 => 어플리케이션 서버 분산
  • DB서버는 읽기/쓰기 부하 분산 등을 위해 Write용 DB와 Read용 DB를 분리 구성하며, 업무별 부하 분산을 위해 업무별 DB 분리 구성 => DB 서버 분산
  • 비즈니스 요구사항을 신속하게 구현하고 배포하기 위해 어플리케이션을 커다란 하나로 구성하지 않고 잘게 나누어 독립적으로 배포/실행 => 어플리케이션 분산
  • 클라우드 환경에서 인스턴스를 포함한 모든 자원은 필요에 따라 쉽게 만들고 필요가 없을 경우 쉽게 폐기하고 모니터링할 수 있어야 하며, 이러한 과정을 자동화할 수 있어야 한다. 예를 들어 오라클 DB를 사용한다면 RDS오라클을 사용하면 Managed서비스가 되지만 EC2에 오라클을 설치해서 사용하면 Managed 서비스가 아니다.


위와 같은 특징을 가지고 WEB/WAS/DB 에 대한 목표 이미지를 그린 것이 그림 1이다.

                                                                   

그림 1. 클라우드 환경 소프트웨어 아키텍처 목표 이미지


위 그림에 대한 설명은 아래와 같다.

  • Web서버/WAS 서버는 오토스케일링을 통해 무한대로 확장가능하며, CPU 사용률 등 사전에 정해진 룰에 따라 Scale-out 됨
  • DB 서버는 업무별로 분산되어 업무별로 부하를 분산하고 있으며, 필요에 따라서 읽기 부하 처리를 위한 Read용 DB 구성이나 샤딩이 가능
  • 어플리케이션은 업무별로 분산되어 업무별 WAS 그룹에 분산 배포되며, 오토스케일링은 업무 단위로 수행되므로 특정 업무에 대한 부하 폭증 시 해당 업무용 WAS 인스턴스만 증가될 수 있음

2. 클라우드 환경 소프트웨어 아키텍처 개요


아마존 AWS 클라우드 환경은 On-Premise 환경과 많은 부분이 달라질 수 있으며, 소프트웨어 아키텍처와 그에 따른 어플리케이션도 많은 부분이 변경될 수 있다. 본 장에서는 아마존 AWS 클라우드 환경 소프트웨어 아키텍처 특징과 방향에 대해 기술한다.

아마존 AWS 클라우드 환경 소프트웨어 아키텍처가 On-Premise 환경과 다른 점은 아래 표 2와 같다.

 

항목

On-Premise 환경

아마존 AWS 환경

미들웨어 설계

(Web/WAS 구성)

-         Scale-Up, Scale-Out 모두 감안하여 설계

-         피크 시   용량으로 대용량 서버로 Web/WAS노드 수 확정하여 설계하는 것이 일반적

-         세션 공유   필요시 WAS 세션 클러스터링 기능 적용

-         Scale-Up, Scale-Out 모두 가능하나 Scale-Out 위주로 설계

-         대용량   서버보다 작은 서버로 Scale-out 가능하도록 설계

-         Multicast가 지원되지   않아 Multicast 방식 WAS 세션 클러스터링 적용이   안되며 다른 세션 공유 방법 필요

DB 설계

-         미션크리티컬한 비즈니스의 경우   오라클 RAC로 가용성 확보하는 사례가 많음

-         모노리틱 아키텍처로 한 DB에 모든 데이터 존재

-         수십테라 대용량 데이터도 하나의 DB로 처리가능

-         MultiCast 미지원으로 오라클 RAC는 지원하지 않음

-         아마존 RDS서비스를 적용할 경우 Write용 Maste DB와   Read용 Read-Replica 구성이 일반적

-         RDS에서 지원가능한 용량   5테라로 그 이상은 DB 분할 필요하며 DB 용량   제한에 따라 대용량 데이터의 경우 업무별 분산 혹은 샤딩 필요

어플리케이션 설계

-         모노리틱 아키텍처로 한 DB에 모든 데이터 존재하므로 어플리케이션도 하나

-         DB가 하나이므로 테이블간 조인에 제한 없음

-         어플리케이션이 하나이므로 서비스간   호출이 자유로움

-         DB Read/Write 분리할 경우 어플리케이션도 Read/Write분리 필요

-         DB 업무별 분산/샤딩할   경우 어플리케이션도 분산 설계 필요

-         DB가 분산될 경우 분산된 DB테이블 간 조인 안됨

-         어플리케이션 분산될 경우 다른 노드에 있는 서비스간 호출은 리모트 콜이므로 분산 트랜잭션 고려한 설계   필요

빌드/배포

-         어플리케이션이 하나이므로 미들웨어가   다중화되어 WAS가 여러대라 하더라도 같은 어플리케이션이 배포됨

-         어플리케이션이 분산되어 노드별로 다른 어플리케이션 배포

오토스케일링에

따른 시스템 구성 변화

-         MCA,   EAI, FEP, 배치 스케줄러, APM   등 많은 시스템이 인스턴스에 Agent나 어댑터 설치하고 구성하여 사용

-         인스턴스는 미리 정해져 있어야   함

-         어플리케이션 서버 인스턴스가  오토스케일링에 따라 동적으로 증감될 수 있어 설치 구성 방식 소프트웨어는   사용이 어려움

표 2. 아마존 AWS 클라우드 환경 소프트웨어 아키텍처 특징

아마존 AWS 클라우드 환경 소프트웨어 아키텍처를 WEB/WAS/DB 기준으로 몇 가지 유형으로 구분하면 아래와 같다.

  • 모노리틱 아키텍처 – 어플리케이션/DB를 하나로 구성하는 구조. DB Read/Write 분리 없음. WAS에 대한 오토스케일링은 적용함.  클라우드 전환 방법 중 Lift & Shift 유형
  • Read 분산 아키텍처 – Read부하 분산용 Read-Replica DB를 분리하고, 어플리케이션도 Read용 프로그램을 구분함
  • 업무별 분산 아키텍처 – DB와 어플리케이션을 업무별로 분산함
  • 샤딩 아키텍처 – 한 업무에 대한 Write부하 분산을 위해 DB를 분산함

 

위 유형별로 변경되는 내용과 아마존 AWS 적용 서비스는 아래 표 3과 같다.

아키텍처 유형

클라우드   전환 유형

소프트웨어 아키텍처/

어플리케이션 변경 내용

아마존 AWS

적용 서비스

모노리틱

아키텍처

(A)

-         Lift & Shift

-         Web/WAS까지 전환하고   어플리케이션 변경 최소화

-         아마존 DNS 서비스 적용

-         서버 인스턴스 EC2로 변경

-         AutoScaling 적용

-         부하 분산 ELB 적용

-         (선택) 아마존 RDB 서비스 RDS 적용

-         (선택) 정적 컨텐츠 배포용   아마존 스토리지/CDN 적용

-         Route53

-         EC2

-         AutoScaling

-         ELB

-         (선택) RDS

-         (선택) S3/CloudFront

 Read 분산

아키텍처

(B)

-         Refactoring

-         DB 구성 변경(Read-Write 분리)

-         DB 구성 변경에 따른 어플리케이션 일부 변경

-         아마존 RDB 서비스 RDS 적용

-         WAS/어플리케이션 멀티 데이터소스 적용

-         (선택)Read부하 경감을   위한 캐시 적용

(A)서비스 적용 후  추가 서비스

-         RDS

-         (선택) ElasitCache

업무별

DB 분산

아키텍처

(C)

-         Refactoring

-         DB 구성 변경(업무별 분리)

-         DB 구성 변경에 따른 어플리케이션 변경 많음

-         세션 공유

-         (선택) 서비스   디스커버리 서비스 적용

-         (선택) 서비스간   연계를 위한 메시지큐 적용

-         (선택) 분산   트랜잭션 혹은 분산처리 기능 적용

(B)서비스 적용 후  추가 서비스

-         DynomoDB 혹은 ElasitCache

-         (선택) API Gateway

-         (선택) SQS

DB 샤딩

아키텍처

(D)

-         Refactoring

-         WAS/어플리케이션 데이터소스 라우팅 기능   적용

-         Aggregation 기능 적용

(A)서비스 적용

표 3. 아마존 AWS 클라우드 환경 WEB/WAS 기준 소프트웨어 아키텍처 유형

위 유형 각각에 대해 2.3 절부터 하나씩 기술한다.


3. 모노리틱 아키텍처

아마존 AWS 클라우드 환경에 기존 시스템을 이전할 때 인프라만 전환하고 어플리케이션 및 소스 변경을 최소화할 수 있는데, 해당 유형은 클라우드 전환 방법 중 “Lift & Shift”로 불리는 유형이며, 신규로 클라우드 환경에 시스템 구축할 때도 On-Premise 환경에서 모노리틱 아키텍처로 구축할 때와 유사한 아키텍처로 시스템을 구축하면 모노리틱 아키텍처 유형이 된다.

3.1.   구성도

                       

그림 2. 모노리틱 아키텍처 구성도

3.2.  구성 특징

아키텍처

구성 요소

구성   내용

전환 시 변경사항

부하분산/

WEB/WAS

-         오토스케일링을   위해서 WEB/WAS모두 ELB로 부하 분산 구성함

-         Web/WAS 제품 변경 : 상용 Web/WAS 일 경우 오픈소스 Web/WAS로 변경

-         예) Weblogic/Jeus => Tomcat/Lena

|

DB

-         DB분산은 하지 않고 마스터 DB 하나로 구성하여 Read/Write 모두 처리

-         DBMS 변경 검토 : 오라클 => RDS 오라클/Mysql/MariaDB

-         NoSQL DB로 변경은 검토하지 않음

어플리케이션

-         오토 스케일링이 가능하도록 특정   노드 종속성이 없도록 설계 구현함

-         DB 변경이 없을 경우 변경 없으나 오라클 DB가 MySQL/Maria 등으로 변경될 경우 쿼리 수정이 발생할 수 있음

표 4. 모토리틱 아키텍처 구성 내용

4. Read분산 아키텍처

아마존 AWS 클라우드 환경에 기존 시스템을 이전할 때 인프라를 전환하고 DB 읽기 부하를 분산하기 위해 쓰기 처리용 마스터DB와 읽기 처리용 Read-Replica로 DB를 분산하고 어플리케이션도 DB 구성에 맞게 구성하는 방식이다. 해당 유형은 클라우드 전환 방법 중 “Refactoring”에 해당하는 유형이며, 읽기용 DB를 분산함에 따라 하나의 WAS에서 두 개의 DB에 연결해야 하므로 멀티데이터소스 설정이 필요하고, 쓰기용 프로그램과 읽기용 프로그램 분리로 인해 일부 어플리케이션 수정이 발생할 수 있다.

4.1. 구성도

                       

그림 3. Read분산 아키텍처 구성도

4.2. 구성 특징

아키텍처

구성 요소

구성   내용

전환 시 변경사항

부하분산/

WEB/WAS

-         오토스케일링을   위해서 WEB/WAS모두 ELB로 부하 분산 구성함

-         Web/WAS 제품 변경 : 상용 Web/WAS 일 경우 오픈소스 Web/WAS로 변경

DB

부하 분산

-         마스터 DB는 하나로 쓰기 처리하고 N개까지 될수 있는 슬레이브 DB는 읽기 처리 전용으로 구성

-          

어플리케이션

-         WAS에서 멀티 데이터소스 설정

-         서비스나 컨트롤러 메소드 단위로   쓰기용 서비스와 읽기용 메소드를 구분하여 DB 접속 구분

-         쓰기용 프로그램과 읽기용 프로그램   구분해야 함

표 5. Read분산 아키텍처 구성 내용

5. 업무별 DB 분산 아키텍처

아마존 AWS 클라우드 환경에 기존 시스템을 이전할 때나 신규 시스템을 구축할 때 업무별로 DB를 분산하여 구축할 수 있다. 해당 유형은 클라우드 전환 방법 중 “Refactoring”에 해당하는 유형이며, 모노리틱 아키텍처 기반 시스템에서 전환시에는 어플리케이션 변경이 많이 발생하는 유형이다.

5.1. 구성도

                       

그림 4. 업무별 DB분산 아키텍처 구성도

5.2. 구성 특징

아키텍처

구성 요소

구성   내용

전환 시 변경사항

부하분산/

WEB/WAS

-         ELB로 부하분산하나 오토스케일링은   업무별로 그룹화된 단위로 가능

-         업무그룹별로 어플리케이션을 분리하고 WAS도 분리

DB

부하 분산

-         업무별로 DB가 분리되고 어플리케이션도 업무별로 분리됨

-         어플리케이션에서는 마스터DB는 1개만 접속가능

-         업무그룹별로 DB를 분리

-         분산된 DB에서도 공통 데이터는 사용이 가능하도록 동기화 등에   대한 설계 및 구현

어플리케이션

-         서비스간 연계 방법을 정해서   설게/구현해야 함

-         서비스간 호출은 리모트 콜이나 큐방식 연계 등으로 변경하고 분산 트랜잭션 요건에 대응하는 처리로 변경해야   함

표 6. 업무별 DB 분산 아키텍처 구성 내용

6. 샤딩 아키텍처

아마존 AWS 클라우드 환경에서 한 업무에 대해 읽기 부하 분산을 위해 DB 분산하면 샤딩 아키텍처가 된다.

6.1.  구성도

                       

그림 5. 샤딩 아키텍처 구성도

6.2. 구성 특징

아키텍처

구성 요소

구성   내용

전환 시 변경사항

부하분산/

WEB/WAS

-         ELB로 부하분산

-         Web/WAS 제품 변경 : 상용 Web/WAS 일 경우 오픈소스 Web/WAS로 변경

DB

부하 분산

-         메인 DB와 별개로 쓰기 부하 분산을 위해 샤딩DB 구성하며 샤딩 키에   따라 데이터를 분산

-         데이터가 샤딩 키에 따라 이관되어야 함

어플리케이션

-         샤딩 키에 따라 타겟 DB에 접속해야 하므로 멀티 데이터소스 설정 및 멀티 데이터소스 라우팅 필요

-         샤딩 키에 따라 DB에 분산되어 있는 데이터를 한번에 읽어서 Aggregation 하는 기능 구현이 필요할 수 있음

표 7. 샤딩 아키텍처 구성 내용 



1. 서비스 분배


클라우드 기반 분산 어플리케이션에서는 여러 서버에 나눠진 서비스를 어떻게 호출하고 요청을 배분할 것인가 하는 요건에 대해 3가지 방안이 있을 수 있는 데, 그림 6와 같이 Centralized, Decentralized, Distributed 방식이다.

 

아마존 클라우드 환경에서는 그림 6에 있는 Decentralized 방식이 ELB/HA Proxy와 대응되며, Distributred 방식이 API G/W, 혹은 오픈 소스 서비스 디스커버리 방식에 대응되는데, 본 문서에서는  2가지 방식에 대해 기술하며 Centralized 방식은 클라우드 환경에 적합하지 않은 방식으로 보고 기술하지 않는다.

                                         

그림 6. 분산 서비스 호출방식(15년 분산과제 자료)

1.1. 서비스 디스커버리 방식

서비스 디스커버리 방식은 여러 서버에 나눠진 서비스를 유레카나 아마존 API Gateway 같은 서비스를 이용하여 찾아내서 호출하는 방식이다.

 

아래 그림 7은 오픈소스인 유레카를 사용해 서비스 등록 및 서비스 호출하는 내용이다. 상세한 내용에 대해서는 자사 Knoledge Asset 사이트(KAMS)에서 “분산 아키텍처”로 검색하여 “분산 아키텍처 Framework” 에셋에 포함되어 있는  “분산 아키텍처 설계서_서비스디스커버리_v1.0.doc” 문서를 참조하기 바란다.


그림 7. 서비스 디스커버리 구성도

아마존 클라우드에서는 분산된 서비스 호출을 위해 API Gateway 서비스를 제공하고 있다. 클라우드 과제에서 API Gateway는 범위에 포함하지 않아API Gateway 관련 내용은 아마존 AWS 사이트를 참조한다.

 

https://aws.amazon.com/ko/api-gateway/?nc2=h_m1


==============================================================================================

1.2. 서비스 분배

서비스 분배 방식은 서비스 디스커버리 서비스를 활용하지 않고 ELB나 HA Proxy등을 활용하여 port나 uri를 이용하여 분배하는 방식이다.

 

서비스 분배 방식을 통한 호출구성은 아래와 같다.

FrontEnd에서 Rest API로 ELB를 통해 서비스를 호출하며, ELB에서는 URI로 구분하여 타겟 노드로 부하를 분산한다.


그림 8. 서비스 호출 방식

 

2. 서비스 연계



클라우드 환경에서 서비스를 분산하고 DB를 분산하게 되면 서비스간 연계 시 피호출 서비스가 다른 노드에 존재하는 되며, 다른 노드에 분산된 서비스간 연계방법이 필요하며, 아마존 AWS에서는 REST API를 이용한 서비스 호출 또는 메시지큐를 이용한 비동기방식을 권고하고 있다.

 

본 절에서는 노드간 분산된 서비스간 연계 방법에 대해 기술한다.

2.1. 서비스 연계 방식

서비스 분산, DB분산 시스템에서는 서비스간 연계를 위해 분산 트랜잭션과 메시시 큐 방식, 분산 서비스 호출 방식 등을 고려해 볼 수 있다.

분산 트랜잭션은 기존 2 Phase commit 이 지원되는 Resource에 대해서 동일 트랜잭션에서 처리할 수 있도록 지원되는 방식이나, titely-coupled 된 방식으로 인하여, 다른 Resource의 문제 발생 시 전체 Service에 미치는 영향이 커 최근 권장하는 방식은 아니다.

 최근에는 마이크로 서비스를 이용한 REST API를 호출하는 방식과, Enterprise Architecture에서 많이 활용하였던 메시지 큐 연결 방식을 주로 활용하고 있다.

 

 각 연계 방식의 특징은 아래와 같다.

 

 

분산 트랜잭션

분산 서비스 호출

메시지 큐

동기/비동기

동기

동기

비동기

트랜잭션 처리

2PC

1PC

1PC

장점

부수적인 솔루션 없이 기존 트랜잭션에 대한 보장이 가능

Interface의 통일화, 개별   서비스의 upgrade가 독립적으로 이루어질 수 있음

개별 서비스의 가용성과는 상관 없는 Loosely-Coupled형태의 구성   가능. 다양한 토폴로지 이용가능

단점

과도하게 사용될 경우, 해당   Resource(DB)에 부하 발생 및 성능 저하 가능

업무적으로 상태 관리를 해주어야 하며, 부수적인 메커니즘이 필요.

단계가 많아지면 오케스트레이션/배포가 복잡해짐

비동기 처리 특성을 잘 확인하여야 하며, 메시지 큐에 대한 지속적인 모니터링   및 관리가 필요함

특징

각 Resource 및 AP에서   분산 Transaction을 처리할 수 있어야 함

REST/SOA같은 표준화된   Protocol을 활용

대용량 처리에도 적합

표 8. 서비스 연계 방식

현재 개발 대상으로 삼은 JPetstore는 분산 서비스 호출(REST API)과 메시지 큐(진행중)를 이용하여 구성하였다.

 

2.2. REST 연계

 연계하고자 하는 대상이 REST API를 노출할 경우, Http 프로토콜을 활용하여, 간단한 Request / Response 방식의 동기 호출을 수행할 수 있다.

 분산 서비스의 호출 순서는

 

  1. 분산 서비스 Repository에서 해당하는 서비스의 URI를 획득한 후 (Discovery)
  2. 서비스 Gateway 에 요청
  3. 서비스 Gateway à Rest Service Server 호출

 

형태로 구성되는게 일반적이나, Discovery 부분은 선택사항으로 둘 수 있다.

서비스 Gateway는 API Gateway서비스를 지원하는 서버를 활용하여 구성하거나, Proxy Server 또는 L4를 이용한 형태로 구성할 수 있다.

 


REST 연계 방식

간략하게 Gateway 방식을 비교해 보면 아래와 같다.

 

 

Gateway   Server

Proxy   Server/L4

DNS 활용

구성

Gateway Server가 Front   Endpoint를 구성하고 있으며, 요청이 들어올 경우, 이를 Banckend service에 적절하게 배분

Reverse Proxy 형태로   Backend Server에 Request 전달

DNS에서 IP를 Round-Robin형태로 제공, 이를 활용하여 Backend Server 접근하는 방식

장점

인증/미터링/재호출 등 API 호출 시 필요한 필수 기능들을 Middle Tier에서   공통적으로 관리해 줄 수 있음

간단한 구성으로, Backend Service에 대한 고가용성/부하 분산 기능 제공

공개 인터페이스 구성시 중간 단계 구성 없이 활용할 수 있음

단점

초기 설정 및 활용시 복잡한 절차 및 실제 호출 구조 및 Backend server에서도   필수 기능(인증) 구현이 필수적임

Auto Scale Out 구성이 어려움.   API에 필요한 기능(인증/미터링 등)을 직접 구현해야 함

Fail Node에 대한 DNS   Entry 수정을 해 줄 수 있는 모니터링 기능이 필요. API에 부수적인 기능들을   반드시 독립적으로 구현해야 함

내부 Service일 경우 별도의 DNS구성   필요

특징

AWS에서 서비스로 제공

AWS ELB로 활용 가능

AWS Route53기능 활용 가능

표 9. Rest 연계 방식

현재 JPetstore는 DNS & Proxy Server를 활용한 형태로 구성되었다.

2.3. REST 서비스 구현

REST Backend Service는 Spring 4.X 이상에서 제공하는 Rest Controller 기능을 이용하여 구현하였다.

 

Rest Controller Annotation을 활용하면, 요청받은 Http Method와 URI를 손쉽게 처리할 수 있으며, 필요한 정보를 추출하여, 실제 구현 메쏘드에 전달 할 수 있다.

 

REST API에서 활용되는 Http Method는 초기 GET/POST/PUT/DELETE 정도가 있었으나, 이후 확장 메쏘드가 추가되어, PATCH/OPTIONS/HEAD 등이 추가 되었으나, 전체를 활용하지 않고, 기본 메쏘드만 활용하는 것으로 구성한다.

 

HTTP Method

CRUD 맵핑

설명

GET

SELECT

해당 URI가 가리키는 전체 속성 값을 가지고 온다.

POST

INSERT

해당 URI에 Resource를   등록한다.

PUT

UPDATE

해당 URI가 가리키는 Resource의   속성 값을 Update한다.

DELETE

DELETE

해당 URI가 가리키는 Resource를   삭제한다.

표 10. HTTP 메소드별 CRUD 매핑

REST API를 호출하는 Client는

  1. Apache Http Client를 이용하여 직접 구현한 경우
  2. Spring RestTemplate 기능을 이용하여 구현한 경우

로 구분되어 지며, Exception에 대한 처리는 아직 미구현 상태이다. (추가적인 Exception 처리 방안 설계 필요)

2.3.1.1.  POST

  • Backend Service 구현

 

URL

/account

파라미터

Ik.am.jpetstore.domain.model.Account 객체

(JSON을 이용하기 때문에,   Jackson을 통한 직렬화가 자동으로 이루어짐)

실제 구현 코드

(Controller)

                                                                     

    

설명

Post의 경우, @RequestMapping(method = RequestMethod.POST)를 반드시 설정하여야   한다.

입력 파라미터에   @RequestBody 를 사용할 경우, Http Request Contents Body에   들어가 있는 JSON Contents가 Jackson을   통해서 자동으로 매핑된다.

 

이후 구현 사항은, 일반적인 Service를 호출하여 Transaction을 발생시킨다.

 

  • Client 호출 부분 구현

URL

/account

파라미터

Ik.am.jpetstore.domain.model.Account 객체

(JSON을 이용하기 때문에,   Jackson을 통한 직렬화가 자동으로 이루어짐)

구현방안

Apache Http Client 활용

실제 구현 코드

(Controller)

    

설명

Apache Client의 경우에는 사용할 Http Method에 따라 HttpPost/HttpGet 과 같이 새로운 Request Class를   생성하여 호출하여야 한다.

중요한 점은,   content-type에 “application/json”을 명시하여, Backend Service에서 정상적인 호출 관계를 받아 볼 수 있게 해야 한다.

전달되는 Parameter는 Jackson Library의 ObjectMapper기능을 이용하여 Serialize 된 JSON String을 Content에 태워서 보내야 한다.

 

2.3.1.2. GET

  • Backend Service 구현

 

URL

/product?keyword=?

파라미터

상품 이름 검색에 활용되는 검색어 값(String)

실제 구현 코드

(Controller)

    

    

설명

Get의 경우, @RequestMapping(method = RequestMethod.GET)를 반드시 설정하여야 한다.

Get Request URL에 위와 같이 Query String(?keyword=?&…)이 들어가 있는 경우,   @RequestParams를 이용하면, 해당   Query String의 개별 Value 값을 추출하여 파라미터 값으로 바로 활용할   수 있다.

 

Return Value로 List Collection이 넘어가도 되며, 자동으로 JSON의 Array   형태로 변형되어 Client로 전달된다.

 

  • Client 호출 부분 구현

URL

/product?keyword=?

파라미터

상품 이름 검색에 활용되는 검색어 값(String)

구현방안

Spring RestTemplate 기능 활용

실제 구현 코드

(Controller)

    

설명

RestTemplate의 경우, Get은 모두 Object형태의 결과 값을 가지고 오기 때문에, GetForObject 메쏘드를   활용하게 된다.

일반 Class 형태의 Return값을 가지고 올 경우와는 달리, List<T> 형태의 결과 값은 모두 T[] 형태의 Array 타입으로 변형되는 것에 주의 하여야 한다. (이를   위해서, GetForObject 2번째 Parameter에 Product[].class 를 사용한 부분을 눈여겨 볼 것)

이후,   Arrays.asList 형태로 변형하여 List<T>를 활용하면 된다.

 

2.3.1.3.  PUT

  • Backend Service 구현

 

URL

/account

파라미터

Ik.am.jpetstore.domain.model.Account 객체

(JSON을 이용하기 때문에,   Jackson을 통한 직렬화가 자동으로 이루어짐)

실제 구현 코드

(Controller)

    

 

    

 

설명

PUT의 경우, @RequestMapping(method = RequestMethod.PUT)를 반드시 설정하여야 한다.

 

마찬가지로,   @RequestBody annotation을 이용하여, Contents에서 Account 객체를 가지고 온다.

 

Return Value가 없는 경우, 호출 측에서는 정상적인 처리여부를 확인하기   어려우므로, 원래 객체를 돌려주던가, 아니면 boolean값을 던져주면, 마찬가지로 Boolean 값을 호출측에서 받을 수 있다.

(현재 구현은 잘못된 예시는 보여주고 있음)

 

  • Client 호출 부분 구현

URL

/account

파라미터

Ik.am.jpetstore.domain.model.Account 객체

(JSON을 이용하기 때문에,   Jackson을 통한 직렬화가 자동으로 이루어짐)

구현방안

Apache Http Client 활용

실제 구현 코드

(Controller)

    

설명

직렬화/역직렬화   부분만 신경쓰면 큰 차이가 없으며, StringEntity를 활용하여, HttpPut에 직렬화된 Object 객체를 삽입할 수 있음

 

2.3.1.4. DELETE

  • 미구현된 내용임
  • GET과 유사하게 구현하며, 단지 RequestMethod.DELETE 로 수신할 것.

2.4. 메시지 큐

2.4.1.1.  메시지 큐 구성 방식

메시지 큐를 사용한 방식에는 동기 방식과 비동기 방식이 있는데, 아래 그림 9과 같다.


그림 9. 메시지 큐 동기-비동기 방식

            

본 문서에서는 Standard Queue를 사용하여 서비스 연계를 구현한 방식에 대해 기술한다.

2.4.1.2. 메시지 큐 구현

현 Jpetstore에서는 Message Queue를 이용한 연계가 포함되어 있지 않아, 세부 구현 내용은 향후 작성한다.


3. 세션 처리

클라우드 환경에서 분산 시스템 구축을 위해서는 어플리케이션을 stateless하게 설계해야 하며 세션정보 사용을 최소화하고 필요한 정보는 요청시마다 담아 보내도록 설계해야 한다.

 

그러나 기본적으로 stateless인 웹의 특성상 사용자정보 등 최소한의 정보는 사용자 편의를 위해 stateful하게 유지해야 하는 요건이 있을 수 있는데, stateful 방식 구현을 위한 세션 관련 기능은 표 11과 같다.


항목

내용

WAS 제공 기능

Spring   F/W

제공기능

어플리케이션 설계

고려사항

정보 저장

Stateless인 웹 환경에서 statefull하게 구현하기 위해 세션 정보 저장 필요.

쇼핑몰의 경우 세션에   아래와 같은 정보 저장이 필요

l  카트정보

l  사용자정보

HttpSession   기능   제공

Bean   객체 scope 설정으로 세션에 저장가능

세션에 저장하여 어플리케이션간/멀티 인스턴스간 공유할 데이터 설계

어플리케이션

정보 공유

Session은 Context 별로 관리가 되기 때문에 동일 WAS에 Application을 올려도 Session 객체 공유가 되지   않음

세션 클러스터링 기능   제공

오픈 소스 기반 외부   저장소 지원

 

중복로그인

방지

중복 로그인 방지는 하나의 계정을 2명 이상 사용자가   동시에 사용하는 것을 방지하는 것

WAS 제품에 따라 다름

N/A

중복로그인 방지 정책   결정 및 설계

세션 타임아웃

처리

WAS에서 발행한 Sessionid를 일정 시간 사용하지 않으면 무효화

WAS 제품에 따라 다름

WAS에 종속적

세션 타임아웃 처리 방법 및 타임아웃 시간 등 설계

표 11. 세션 관련 기능

클라우드와 같은 분산 환경에서는 사용자 정보와 같은 세션 정보를 was 노드 메모리에 적재하게 되면 세션 정보를 공유할 수 없고 클라우드 환경에서 인스턴스가 자동으로 증감할 경우 세션에 저장된 정보가 유실된다. 본 장에서는 클라우드 환경에서 세션 정보를 멀티 노드간 공유하기 위한 방법을 기술한다.

 

3.1. 단일 인스턴스에서의 스프링 세션 처리 방식

스프링에서는 세션정보 처리하는 방법은 세션정보 관리를 직접 코딩하는 방법과 스프링 시큐리티 기능을 사용해서 스프링에서 기제공되는 기능을 사용하고 코딩을 최소화하는 방법, WAS에서 제공하는 세션 클러스터링 기능을 사용하는 방법 등이 있다.

 

3.1.1. 스프링 시큐리티를 활용한 방식

PoC 대상인 JPetStore 샘플 어플리케이션에서는 스프링 시큐리티 기능을 사용해서 처리하고 있다.

사용자 로그인에 대한 처리는 아래 표 12와 같이 로그인 페이지와 로그인이 필요한 페이지를 스프링 시큐리티 파일에서 지정하는 방식으로 처리하고 있다.

 

<sec:http auto-config="true"   use-expressions="true">

        <sec:form-login   login-page="/account/signonForm"

              login-processing-url="/account/signon"

              authentication-failure-url="/account/signonForm?error=true"   />

          <sec:logout delete-cookies="JSESSIONID"   logout-url="/account/signoff"

              logout-success-url="/" />

          <sec:intercept-url pattern="/account/editAccount*"

              access="isAuthenticated()" />

          <sec:intercept-url pattern="/order/**"   access="isAuthenticated()" />

      </sec:http>

표 12. JPetStore 샘플 로그인 및 보안 설정(spring-security.xml)

 

PoC 대상인 JPetStore 샘플 어플리케이션에서는 카트 정보 세션을 아래와 같이 처리하고 있다. 카트 정보에 대한 처리를 코딩을 하는 것이 아니라 설정 파일에서 Cart 오브젝트의 scope을 “session”으로 정의하여 세션에 담도록 처리한다.

 

<bean id="cart" class="ik.am.jpetstore.domain.model.Cart“  scope="session">

          <aop:scoped-proxy />

 </bean>

 <mvc:resources mapping="/resources/**"

          location="/resources/,classpath:META-INF/resources/"

        cache-period="#{60   * 60}" />

<mvc:default-servlet-handler />

표 13. 카트 정보 세션 처리(spring-mvc.xml)

PoC 대상인 JPetStore 샘플 어플리케이션에서는 사용자 정보에 대해 표 14와 같이 처리하고 있다 사용자정보를 돌려주는 서비스를 지정하여 최초에 사용자 정보가 필요할 경우 해당 서비스가 호출되어 사용자정보를 db에서 읽어서 돌려주면 세션정보에 해당 사용자정보를 보관한다.

 

<sec:authentication-manager>

        <sec:authentication-provider

              user-service-ref="userDetailsService">

              <sec:password-encoder ref="passwordEncoder" />

          </sec:authentication-provider>

</sec:authentication-manager>

표 14. JPetStore 사용자 정보 설정(spring-security.xml)

사용자 정보 구현 서비스는 기정의된 UserDetailsService 인터페이스를 구현하여 사용자명으로 db에서 사용자정보를 조회하고 선호하는 카테코리 상품 목록과 함께 돌려주도록 작성되어 있다.

 

@Service("userDetailsService")

public class UserDetailsServiceImpl   implements UserDetailsService {

      @Override

    public   UserDetails loadUserByUsername(String username) throws   UsernameNotFoundException {

     

          Account account = accountService.getAccount(username);

       

          if (account == null) {

              throw new UsernameNotFoundException(username + " is not   found.");

          }

        List<Product>   myList = catalogService.getProductListByCategory(account

                .getFavouriteCategoryId());

          return new ik.am.jpetstore.domain.model.UserDetails(account, myList);

    }

}

표 15. 사용자정보 구현 서비스(UserDetailServiceImpl.java)

3.1.2. 스프링 시큐리티를 활용하지 않는 방식

스프링 시큐리티를 활용하지 않을 경우 사용자 로그인 처리, 사용자 정보를 세션에 저장하고 세션에서 읽어오는 처리, 그리고 세션에 담아야 할 데이터에 대한 처리 등을 소프트웨어 아키텍트나 개발자가 직접 코딩하여 처리한다.

해당 방식에 대한 상세한 내용은 본 문서에서는 다루지 않는다.

3.2. 분산 Session 도구 및 선정

분산 환경에서 멀티 노드 간 세션정보 사용이 필요할 경우 멀티 인스턴스간 세션 공유를 위해 아래와 같은 방법을 사용할 수 있다.

l  WAS 에서 제공하는 세션 클러스터링 기능 사용

l  WAS 에서 제공하는 세션 클러스터링 기능을 사용하지 않고 세션 정보를 저장할 수 있는 외부 저장소 사용

 

아래 그림 10은 WAS세션 클러스터링에 대한 내용이며, WAS 세션 클러스터링 적용 시 사용자가 어느 서버와 연결되어도 Session이 끊기지 않고 서비스를 이용할 수 있게 된다.


그림 10. 세션 클러스터링


아마존 AWS에서는 Multicast를 지원하지 않으므로 Tomcat의 세션 클러스터링은 사용할 수 없고, 자사의 WAS 제품인 Lena를 사용할 경우 AWS에서 세션 클러스터링을 사용할 수 있다.

 

아마존 AWS에서는 세션공유가 필요할 경우 AWS 서비스인 DynamoDB나 ElastiCache를 사용하여 세션정보를 공유하는 것을 권장하고 있다.

 

WAS 세션 클러스터링 기능을 사용하지 않고 외부 저장소를 통해 세션 정보를 공유할 경우 아래와 같은 방법이 있다.

l  서블릿 스펙에 HTTPSession을 override한 기능을 사용 : DynamoDB 기능 사용, Redis Spring Session 기능 사용

l  외부저장소에 세션정보를 저장/조회 기능을 직접 구현 : Redis 클라이언트를 활용한 저장/조회 기능 구현

 

아마존에서 ElastiCache 서비스를 사용하여 Redis 캐시 서버에 세션 정보를 저장하는 경우, Redis Spring Session 과 같이 HttpSession을 Override한 오픈 소스 라이브러리를 사용하여 코딩 없이 세션에 정보를 저장할 수 있고, Redis Client를 사용하여 직접 세션에 정보를 저장하고 조회하는 등 관리할 수 있으며, 해당 내용은 아래 표 16와 같다.


항목

HttpSession Override

Redis 클라이언드 코딩

세션정보

관리 방법

HttpSession을 override한 라이브러리를 사용하여, 세션에 저장할 정보를   객체단위로 관리함

Redis 클라이언트를 사용해 세션에 저장할 정보를 Redis에서 지원하는 hash 등 데이터타입을   이용하여 직접 코딩하여 관리

세션정보

Get/get

session.setAttribute();

 

session.getAttribute();

jedis.hset("userData",   "gildong", "gildong|hong|Seoul");

 

jedis.hget("userData",   "gildong");

스프링 시큐리티

재사용

스프링 시큐리티를 사용하여   로그인 및 사용자정보를 관리할 경우 코딩 변경없이 재사용가능

스프링 시큐리티 중 로그인, 사용자정보 부분을 상속받아 구현 필요

세션에서

사용자정보

획득 방법

UserDetails userDetails =

(UserDetails) SecurityContextHolder

.getContext().getAuthentication().getPrincipal();

String   userDetails= jedis.hget("userData", "gildong");

장점

l  코딩이 필요 없음

l  객체 단위로 저장하지   않으므로 세션에 저장되는 데이터 자료구조가 변경되어도 기저장된 데이터를 읽을 수 있음

l  Tomcat   WAS 버전, 스프링 F/W 버전   등에 영향이 없음

단점

l  오픈소스 라이브러리를   사용하므로 Tomcat 버전 등에 따라 다르게 동작할 수 있음

l  클래스 단위로 정보 저장   시 클래스가 버전이 바뀌면 저장된 정보가 객체단위 복원이 안됨

l  세션에 저장할 정보에   대해 자료구조를 설계하고 저장, 조회 등 관리 기능을 직접 코딩해야 함

권장여부

l  권장하지 않음

l  빠른 구축이 필요하고   세션 관련 요건이 간단할 때 사용가능

l  권장함

l  세션 정보 관리 기능에   대한 구현이 가능할 경우 사용가능

표 16. Redis에 세션정보 공유 방법 비교



3.3.  Spring & Redis를 이용한 분산 Session 구성

스프링 세션은 스프링 프레임웍 기반으로 사용자 정보를 관리하기 위한 API와 구현을 제공한다.

상세한 내용은 아래를 참고한다.

 

http://projects.spring.io/spring-session/

 

스프링과 레디스를 이용한 분산 세션이란 스프링 프레임웍 기반으로 세션 정보를 레디스 캐시 서버에 저장하고 관리하는 것을 의미한다. 본 절에서는 스프링 프레임웍에서 레디스에 세션 정보를 저장하기 위해서 설정해야 될 내용과 코딩 부분을 기술한다.

 

3.3.1. Spring Redis Session 사용을 위한 Maven 설정

Tomcat 서버에서 Spring Redis Session을 사용하기 위해서 프로젝트 pom.xml 파일에 아래 의존성 내용을 추가한다.

 

<dependency>

<groupId>org.springframework.session</groupId>

 <artifactId>spring-session-data-redis</artifactId>

 <version>1.0.2.RELEASE</version>

 <type>pom</type>

</dependency>

<dependency>

 <groupId>cglib</groupId>

 <artifactId>cglib</artifactId>

 <version>3.2.0</version>

</dependency>

표 17. pom.xml 설정 추가 내용

 

3.3.2. Spring Redis Session을 이용하기 위한 Web.xml 설정

Tomcat 서버에서 Spring Redis Session을 사용하기 위해서 프로젝트 web.xml 파일(“/src/main/ webapp/WEB-INF/ web.xml”)에 아래 설정 내용을 추가한다.

 

<context-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>

            classpath*:META-INF/spring/applicationContext.xml

<!--  sessionRedis.xml   설정 추가 -->

            classpath*:META-INF/spring/sessionRedis.xml

            classpath*:META-INF/spring/spring-security.xml           

        </param-value>

    </context-param>

 

<!--  WAS에서 제공하는 세션 정보를 redis를 통해서 전달하게 처리하는 Filter 설정 추가 -->

 <filter>

    <filter-name>springSessionRepositoryFilter</filter-name>

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

 </filter>

 <filter-mapping>

    <filter-name>springSessionRepositoryFilter</filter-name>

    <url-pattern>/*</url-pattern>

 </filter-mapping>

표 18. web.xml 설정 추가 내용

 

3.3.3. Redis 서버 설정 파일 추가

Tomcat 서버에서 Spring Redis Session을 사용하기 위해서 프로젝트 appServlet 폴더(“/src/main/ webapp/WEB-INF/spring/”) 아래에 아래 redis 설정 파일 내용을 추가한다.

내용 중 hostname과 port는 연결하고자 하는 redis 서버 주소와 포트를 적어준다. 로컬 pc에 redis 서버를 설치한 경우 로컬 주소를 기입한다.

 

<?xml version="1.0"   encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xmlns:context="http://www.springframework.org/schema/context"

 xmlns:p="http://www.springframework.org/schema/p"

 xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd

    http://www.springframework.org/schema/context   http://www.springframework.org/schema/context/spring-context.xsd">

 

 <context:annotation-config />

 <bean

    class="org.springframework.session.data.redis.config.annotation.web.http.

RedisHttpSessionConfiguration" />

 <bean

    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"

p:port="6379"   p:hostName="127.0.0.1" />

</beans>

 

 

표 19. sessionRedis.xml 파일 내용

 

3.3.4. Redis Spring Session 샘플 코드 테스트

위 모든 설정을 마친 후 샘플 코드를 아래와 같이 작성하여 session 객체에 정보 저장 시 Redis 서버에 저장되는 것을 확인할 수 있다.

 

package com.lgcns.au.cloudwork;

 

import java.text.DateFormat;

import java.util.Date;

import java.util.Locale;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import   org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import   org.springframework.web.bind.annotation.RequestMapping;

import   org.springframework.web.bind.annotation.RequestMethod;

 

import javax.servlet.http.HttpSession;

import   org.springframework.web.bind.annotation.RequestMapping;

import   org.springframework.web.bind.annotation.RequestParam;

 

@Controller

public class LogInController {

 

 @RequestMapping(value = "/Login",   method = RequestMethod.GET)

 public   String login(Locale locale, Model model) {

 

return "Login";

 }

 

 @RequestMapping(value =   "/LoginCheck", method = RequestMethod.POST)

 public   String loginCheck(@RequestParam String username,

     @RequestParam String password,   HttpSession session) {

  //   Temporary password matching

  if   (password.equals("pass")) {

              

   session.setAttribute("id",   username);

     return "home";

  }

  return   "Login";

 }

}

표 20. Jedis Java샘플 코드 테스트

 

위와 같은 소스를 작성하여 로컬 pc에서 톰캣 2개 인스턴스를 다른 포트로 구동하고 한 인스턴스로 로그인 한 후에 세션 정보를 확인하고 다른 인스턴스에서 세션정보를 가져와서 표시하도록 하면 세션 정보가 공유되는 것을 확인할 수 있다.


그림 11. 테스트용 로그인 화면


그림 12. 로그인 후 세션 정보 확인


Redis에 저장된 세션 정보를 레디스 서버에 접속 후 Hash 타입으로 저장된 세션 정보를 “hgetall” 명령어와 키값을 주고 아래와 같이 확인할 수 있다.

 

명령어 : hgetall 세션키값

예) hgetall spring:session:sessions:ca9f1764-ff29-482f-ab18-4f22ff4c93f7


그림 13. Redis에 저장된 세션정보 확인Spring Redis Session 이용 시 개발자 코딩 내용

Spring Redis Session을 적용하고 스프링 시큐리티를 사용할 경우 세션에 사용자 정보 저장 관련하여 코딩할 부분은 싱글 인스턴스 세션 처리 방식과 동일하다.

 

3.4. Tomcat & Dynamo DB를 이용한 분산 Session 구성

본 절에서는 Tomcat서버 기반의 환경에서 DynamoDB를 이용하여 세션 처리하는 방법에 대해 기술한다. 2대 이상의 EC2 instance가 있고 앞 단에 ELB 구성하여 로드밸런싱되는 환경을 전제로 한다.

3.4.1. ELB stickiness 설정

ELB의 stickiness 관련 옵션을 그림 14 와 같이 설정한다. [Cooke Name] 항목의 값은 tomcat에서 기본적으로 생성하는 JSESSIONID 를 입력한다.


그림 14 ELB stickiness 설정

3.4.2. Tomcat 설정파일 수정

$CATALINA_HOME/conf/context.xml 파일에 아래의 내용을 추가한다. DynamoDB 접속 정보를 Manager 태그안에 awsAccessKey와 awsSecretKey 속성을 이용하여 직접 설정할 수도 있지만, 보안성 측면에서 Role, Policy 등을 이용하여 설정하는 것을 권장한다. createIfNotExist 값을 true로 설정하면, DynamoDB에 table 이 존재하지 않는 경우 “sessionId”라는 명칭의 item을 갖는 “Tomcat_SessionState”라는 명칭의 table을 default로 생성한다. Region은 N.Virginia(us-east-1) 가 default 이므로 해당 region에서 table 확인이 가능하다.

 

<Manager   className="com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager"

createIfNotExist="true" />

 

3.4.3.  Library 추가

https://github.com/aws/aws-dynamodb-session-tomcat/releases 사이트에서 1.0.1 버전의 jar파일(AmazonDynamoDBSessionManagerForTomcat-1.0.1.jar)을 다운로드하여 $CATALINA_HOME/lib 디렉토리에 저장한다.

 

3.4.4. DynamoDB sessionId확인

Tomcat 서버를 재기동한 후 브라우저에서 확인한 JSESSIONID 값이 DynamoDB Tomcat_SessionState table 의 sessionId item에도 존재하는 것을 확인할 수 있다. ELB의 stickiness 설정을 하였으므로 특정 클라이언트의 요청은 특정 EC2 instance에서 처리되며 해당 EC2 instance가 서비스 불가 상태가 되면 다른 EC2 instance로 요청이 보내지며 이 때 session 의 정보는 유지가 된다.



그림 15 브라우저 JSESSIONID 확인


그림 16 DynamoDB sessionId 확인


3.5. Lena를 이용한 분산 Session 구성

LENA 서버에서 세션 클러스터링은 그림 16처럼 별도 세션 서버를 두어 세션정보를 공유한다.


그림 16. LENA 서버 세션 클러스터링

4. 분산 환경 구성

클라우드 환경에서 DB를 분산했을 경우 분산 Insert/select 처리, 분산 트랜잭션 등에 대한 구현이 필요할 수 있으며 본 절에서는 해당 내용에 대해 기술한다.

4.1. Single Datasource vs MultiDatasource

On-Premise 환경 모노리틱 아키텍처에서는 어플리케이션도 하나, 데이터베이스도 하나이므로 어플리케이션에서 데이터베이스에 접근하는 경로인 데이터소스도 싱글 데이터소스인 경우가 대부분이다.

 

클라우드 환경에서는 데이터베이스가 분산되고 어플리케이션도 분산되어 하나의 어플리케이션에서 여러 개의 데이터베이스에 연결해야 되는 경우가 있을 수 있으며, 그 유형은 아래와 같다.

 

l  Read/Write DB 분리(Read-Replica 패턴) : 마스터 DB와 Read-Replica DB로 구성하며, 트랜잭션 단위로 타켓 DB를 구분해야 하며, 구분하지 않을 경우 분산 트랜잭션이나 분산 조회가 발생함

l  업무별 DB 분리 : 업무별 DB 분리 시 어플리케이션도 업무별로 분리하여 업무별로는 1개의 타겟DB를 갖도록 구성함

l  샤딩 : 하나의 업무에서 키값에 따라 분산한 여러 DB에 접근하여 처리하도록 구성

 

4.2. DB분리 시 Multi Datasource 설정

본 절에서는 Multi Datasource 설정에 대해 기술한다. DB 분리 유형에 따라 멀티 데이터소스를 설정하고 프로그램이 어떤 데이터소스를 사용할 것인지 정해야 하는데 그 유형은 아래와 같다.

 

l  Read/Write DB 분리(Read-Replica 패턴) : 컴파일 시 프로그램별로 타겟 데이터소스 지정

l  업무별 DB 분리 : 컴파일 시 프로그램별로 타겟 데이터소스 지정

l  샤딩 : 런타임에 샤딩 키 등 사전에 정의된 구분자로 타겟 데이터소스 지정

4.2.1. Read/Write Datasource 구성

 

클라우드 환경에서는 아래  그림 17과 같이 마스터 DB 1대에서 CUD를 처리하고 읽기 전용 DB에서 조회를 처리하는 아키텍처 구성이 일반적이며 , 따라서 1대 Tomcat 서버에서 2대 이상 DB에 접속이 필요할 수 있다.

 

이 때 Tomcat 서버에서 여러 DB에 접속할 수 있도록 하고 서비스 단위로 타겟 서버를 지정할 수 있는 기능이 필요하며, 본 가이드는 해당 내용에 대해 기술한다.


그림 17. 클라우드 Read-Replica 패턴


4.2.2.  Read/Write DB 분리 시 멀티 데이터 소스 설정

본 절에서는 Read/Write DB 분리 시 Tomcat 서버, 스프링 프레임웍, Java PetStore 샘플 어플리케이션 기준으로 멀티 데이터소스 설정 방법에 대해 기술한다.

 

Tomcat 서버에서 2개 이상 데이터 소스 설정은 spring-jpetstore-env.xml  파일에 데이터베이스 접속 설정을 표 21와 같이 추가한다. 데이터소스 2개가 되며, 빈명은 dataSource_Catalog_CRD, dataSource_Catalog_Read와 같이 설정하였다.

 

<?xml version="1.0"   encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xmlns:jee="http://www.springframework.org/schema/jee"

    xmlns:jdbc="http://www.springframework.org/schema/jdbc"

    xsi:schemaLocation="http://www.springframework.org/schema/jdbc   http://www.springframework.org/schema/jdbc/spring-jdbc.xsd

               http://www.springframework.org/schema/jee   http://www.springframework.org/schema/jee/spring-jee.xsd

               http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">

 

<!--   Catalog CRD DB 연결 -->

    <bean id="dataSource_Catalog_CUD"   class="org.apache.commons.dbcp.BasicDataSource"

        destroy-method="close">

        <property name="driverClassName" value="org.mariadb.jdbc.Driver" />

        <property name="url" value="jdbc:mariadb://dev-catalog.cuwz4cmnibpf.ap-northeast-2.rds.amazonaws.com:3306/catalog?characterEncoding=UTF-8"   />

               <property name="username" value="tec" />

               <property name="password" value="lgcns1234" />

               <property name="testOnBorrow" value="true" />

        <property name="testOnReturn" value="true" />

        <property name="testWhileIdle" value="true" />

        <property name="timeBetweenEvictionRunsMillis" value="1800000" />

        <property name="numTestsPerEvictionRun" value="3" />

        <property name="minEvictableIdleTimeMillis" value="1800000" />

        <property name="defaultAutoCommit" value="false" />

    </bean>

   

        <!-- Catalog Read DB 연결 -->

    <bean id="dataSource_Catalog_Read"   class="org.apache.commons.dbcp.BasicDataSource"

        destroy-method="close">

        <property name="driverClassName" value="org.mariadb.jdbc.Driver" />

        <property name="url" value="jdbc:mariadb://dev-catalog-read.cuwz4cmnibpf.ap-northeast-2.rds.amazonaws.com:3306/catalog?characterEncoding=UTF-8"   />

               <property name="username" value="tec" />

               <property name="password" value="lgcns1234" />

               <property name="testOnBorrow" value="true" />

        <property name="testOnReturn" value="true" />

        <property name="testWhileIdle" value="true" />

        <property name="timeBetweenEvictionRunsMillis" value="1800000" />

        <property name="numTestsPerEvictionRun" value="3" />

        <property name="minEvictableIdleTimeMillis" value="1800000" />

        <property name="defaultAutoCommit" value="false" />

    </bean>

 

</beans>

표 21. 멀티 데이터소스 설정(spring-jpetstore-env.xml)


데이터소스 접속 설정 후에는 sqlSessionFactory 설정을 아래 표 22처럼 설정한다. 데이터소스 ref 항목에 앞에서 설정한 데이터소스명을 기재하고 bean명을 sqlSessionFactory_Catalog_CRD, sqlSessionFactory_Catalog_Read와 같이 구분할 수 있도록 기재하였고, CUD용 sqlSessionFactory인 sqlSessionFactory_Catalog_CRD은 CUD용 데이터소스인 dataSource_Catalog_CRD와 연결하였고, Read용 sqlSessionFactory인 sqlSessionFactory_Catalog_Read는 Read용 데이터소스인 dataSource_Catalog_Read와 연결하였다.

<!--   define the SqlSessionFactory -->

    <bean id="sqlSessionFactory_Catalog_CUD"   class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="dataSource_Catalog_CUD" />

        <property name="typeAliasesPackage" value="ik.am.jpetstore.domain.model" />

    </bean>

 

 

<!--   define the SqlSessionFactory -->

    <bean id="sqlSessionFactory_Catalog_Read"   class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="dataSource_Catalog_Read" />

        <property name="typeAliasesPackage" value="ik.am.jpetstore.domain.model" />

</bean>

표 22. sqlSessionFactory 등 설정(spring-jpetstore-infra.xml)


다음은 Mapper 설정으로 java 패키지 단위로 repository 에 대해서 표 24과 같이 sqlSessionFactory 를 설정해준다. 여기서 CUD를 프로그램이 위치한 패키지는 CUD용 데이터소스로 설정하고, 읽기 전용 프로그램이 위치한 패키지는 Read-olny용 데이터소스로 설정하면 된다.

“mybatis:scan” 태그 사용을 위해서는 “applicationContext.xml” 파일에 표 23에 굵은 글씨로 표시된 내용을 추가해야 한다.

표 23. mybatis:scan 사용을 위한 추가(applicationContext.xml)

 

아래에서 category 패키지만 Read용 sqlSessionFactory인 sqlSessionFactory_Catalog_Read와 연결하였고 나머지 모든 패키지는 CUD용 sqlSessionFactory와 연결하였다.

<mybatis:scan base-package="ik.am.jpetstore.domain.repository.account"   factory-ref="sqlSessionFactory_Account_CUD"></mybatis:scan>

<mybatis:scan base-package="ik.am.jpetstore.domain.repository.category"   factory-ref="sqlSessionFactory_Catalog_Read"></mybatis:scan>

<mybatis:scan base-package="ik.am.jpetstore.domain.repository.product"   factory-ref="sqlSessionFactory_Catalog_Read"></mybatis:scan>

<mybatis:scan base-package="ik.am.jpetstore.domain.repository.item"   factory-ref="sqlSessionFactory_Catalog_CUD"></mybatis:scan>

<mybatis:scan base-package="ik.am.jpetstore.domain.repository.order"   factory-ref="sqlSessionFactory_Order0_CUD"></mybatis:scan>

<mybatis:scan base-package="ik.am.jpetstore.domain.repository.sequence"   factory-ref="sqlSessionFactory_Order0_CUD"></mybatis:scan>

표 24. applicationContext.xml 설정

 

4.2.3. Read Replica에서 조회

스프링-MyBatis를 사용할 경우 Read Replica에서 조회 시 소스코드에서 달라질 부분은 없고 패키지단위로 mybatis 스캔 시 repository에 대한 sqlSessionFatory를 Read-Replica용으로 지정해주면 된다.

 

4.2.4.  Multi Datasource Routing 설정

멀티 데이터 소스 라우팅이란 멀티 데이터소스를 설정하고 런타임에 데이터소스를 정하여 연결할 수 있도록 하는 것이다.  이는 Read/Write DB 분리나 업무별 DB분리 시 멀티 데이터 소스를 설정하고 컴파일시에 타겟 DB를 정해주는 것과 달리 DB분산 중 샤딩을 적용할 경우에 런타임에 타겟 DB를 지정할 필요가 있을 경우 적용하는 방법이다.

 

Multi Datasource Routing이 동작하는 방식은 멀티 데이터소스를 정의한 후 사용할 데이터소스 정보를 ThreadLocal 변수에 저장하고 AbstractRoutingDataSource의 Lookup으로 ThreadLocal 변수에 있는 DB 정보를 사용하는 방식이다.

 

4.2.5. Sharding Datasource 연결 및 설정(분산 과제)

2015년 자사 분산 과제에서 DB 샤딩 처리를 위한 샤드프레임 기능을 개발하였다.

해당 과제에서 데이터를 분산하는 샤딩 아키텍처는 그림18과 같다. 그림과 같이 데이터를 한 DB에 두지 않고 DB를 나누어 샤딩했을 경우 단일 DB에 대한 Create/Read/ Update/Delete 처리와 다중 DB에 대한 처리가 필요할 수 있다.


그림 18. 데이터 분산 샤딩 처리

 

4.3. 분산 Insert 설계 및 구현

Write가 많이 발생하는 경우 DB분산을 고려해야 한다. 가장 먼저 고려할만한 방법은 서비스 분리 방안이며, 다량의 중요한 자료가 들어오는 서브 도메인이 있을 경우, 이는 독립될 수 있는 중요 업무영역으로 식별하여 별도 서비스로 분리한다. 두번째는 Tenant나 지역적으로 전체 DB가 깔끔하게, 분할 될 수 있는지 확인하는 방법이다. Tenant나 지역적으로 분리가 되는 경우, Tenant별 DB를 생성하여, 원 DB의 Write 부하를 해결할 수 있다. 세번째 방식은 단위(사용자/상품)별 분리가 가능한 경우이다. 즉 특정한 Hash키를 이용하여 전체 DB를 분리할 수 있다고 한다면, Hash를 이용한 DB 분리를 고려할 수 있으며 이러한 기법을 Sharding이라 한다. 본 절에서는 2015년 분산과제에서 개발한 샤드프레임 기능을 활용하여 분산 DB 환경에서 Insert하는 방법을 기술한다. 샤드 프레임 모듈 활용 시 처리 흐름은 그림 19와 같으며, 샤드 키에 따라 타켓 DB가 결정되어 처리된다.


그림 19. 분산 DB 환경 단일 DB처리 흐름

 

4.3.1.  관련 설정

  1. web.xml 설정
    ShardListener를 web.xml에 등록한다. context-param으로 shard.config가 필요하며, value는shardConfig.properties를 명시한다. shardConfig.properties는 classpath에 존재해야 한다.

<listener>

        <listener-class>shardframe.handler.ShardListener</listener-class>

</listener>

<context-param>

        <param-name>shard.config</param-name>

        <param-value>shardConfig.properties</param-value>

</context-param>

  1. Shard config
    shardConfig.properties파일은 classpath 아래에 반드시 존재 해야 한다.

shard.rule.class = shardframe.mapper.rule.simple.CustomShardRule

shard.key.cloumn = orderid

shard.count = 3

아래의 필수 항목 외에 프로젝트에서 필요한 경우 추가로 정의 할 수 있으면, ShardConfig 클래스를 호출하여 필요한 정보를 사용할 수 있다.

[필수 항목]

항목

Description

shard.key.cloumn

Shard key로 정의된 Data

shard.count

전체 Shard 개수 (Datasource 갯수)

shard.rule.class

Shard key와 Shard를 mapping 처리 하는 클래스   (full name)

해당 class는 ShardRule interface의 구현체여야 함

Shardframe에서는 CustomShardRule, CustomFileShardRule를 제공

  1. Spring context 설정
    Shard Datasource 등록은 일반적인 방법과 동일하게 설정한다. 다만, 각각의 Datasource를 DataSourceProxy에 추가적으로 등록하고, 이를 다른 Bean에서 DataSource로 주입 받는 추가적인 설정이 이루어져야 한다.

<bean id="dataSource0" class="org.apache.commons.dbcp.BasicDataSource"

        destroy-method="close">

        <property name="driverClassName" value="org.mariadb.jdbc.Driver" />

        <property name="url" value="jdbc:mariadb://shard0.cuwz4cmnibpf.ap-northeast-2.rds.amazonaws.com:3306/shard?characterEncoding=UTF-8"   />

        <property name="username" value="tec" />

        <property name="password" value="lgcns1234" />

        <property name="testOnBorrow" value="true" />

        <property name="testOnReturn" value="true" />

        <property name="testWhileIdle" value="true" />

        <property name="timeBetweenEvictionRunsMillis" value="1800000" />

        <property name="numTestsPerEvictionRun" value="3" />

        <property name="minEvictableIdleTimeMillis"   value="1800000" />

        <property name="defaultAutoCommit" value="false" />

</bean>

   

<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource"

        destroy-method="close">

        <property name="driverClassName" value="org.mariadb.jdbc.Driver" />

        <property name="url" value="jdbc:mariadb://shard1.cuwz4cmnibpf.ap-northeast-2.rds.amazonaws.com:3306/shard?characterEncoding=UTF-8"   />

        <property name="username" value="tec" />

        <property name="password" value="lgcns1234" />

        <property name="testOnBorrow" value="true" />

        <property name="testOnReturn" value="true" />

        <property name="testWhileIdle" value="true" />

        <property name="timeBetweenEvictionRunsMillis" value="1800000" />

        <property name="numTestsPerEvictionRun" value="3" />

        <property name="minEvictableIdleTimeMillis" value="1800000" />

        <property name="defaultAutoCommit" value="false" />

</bean>

   

<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"

        destroy-method="close">

        <property name="driverClassName" value="org.mariadb.jdbc.Driver" />

        <property name="url" value="jdbc:mariadb://shard2.cuwz4cmnibpf.ap-northeast-2.rds.amazonaws.com:3306/shard?characterEncoding=UTF-8"   />

        <property name="username" value="tec" />

        <property name="password" value="lgcns1234" />

        <property name="testOnBorrow" value="true" />

        <property name="testOnReturn" value="true" />

        <property name="testWhileIdle" value="true" />

        <property name="timeBetweenEvictionRunsMillis" value="1800000" />

        <property name="numTestsPerEvictionRun" value="3" />

        <property name="minEvictableIdleTimeMillis" value="1800000" />

        <property name="defaultAutoCommit" value="false" />

</bean>

       

<bean id="ShardDataSource" class="shardframe.dataaccess.DataSourceProxy">

        <property name="targetDataSources">

               <map>

               <entry key="dataSource0" value-ref="dataSource0"></entry>

               <entry key="dataSource1" value-ref="dataSource1"></entry>

               <entry key="dataSource2" value-ref="dataSource2"></entry>

               </map>

        </property>

        <property name="defaultTargetDataSource" ref="dataSource1" />

</bean>

 

<bean id="ShardTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <property name="dataSource" ref="ShardDataSource" />

</bean>

 

<bean id="ShardSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="ShardDataSource" />

        <property name="mapperLocations" value= "classpath:/ik/am/jpetstore/domain/repository/**/*.xml"/>

        <property name="typeAliasesPackage" value="ik.am.jpetstore.domain.model" />

</bean>

 

<bean id="ShardSqlSession" class="org.mybatis.spring.SqlSessionTemplate"   destroy-method="clearCache">

        <constructor-arg ref="ShardSqlSessionFactory" />

</bean>

 

4.3.2. 개발 방법

  1. Shard context setting
    ShardMapper 클래스를 이용하여 shard key(예시의 경우는 order id)를 context에 세팅한다.

...

order.setOrderId(getNextId());

ShardMapper.setShardContext(order.getOrderId()); // shard key context setting

|

orderService.insertOrder(order);

...

  1. Shard transaction manager 설정
    데이터분산용 Service는 트랜잭션 매니저를 Shard용 트랜잭션 매니저로 명시해서 사용해야 한다

...

@Transactional(value="ShardTransactionManager") // shard transaction

      public void insertOrder(Order order) {

...

orderDao.insertOrder("ik.am.jpetstore.domain.repository.order.

OrderRepository.insertOrder", order);

...

  1. DAO 개발
    데이터분산을 적용 할 DAO 클래스의 경우에는 SqlSessionDaoSupport를 상속받아 구현하지 않고, Shard용 SqlSesstion을 직접 주입받아 사용한다.

public class OrderDao {

 

        @Resource(name = "ShardSqlSession")

  SqlSessionTemplate shardSqlSession;

        public void   insertOrder(String queryId, Order order) {

               shardSqlSession.insert(queryId, order);

  };

 

4.4. 분산 트랜잭션 처리 설계 및 구현

분산 트랜잭션 처리 설계 부분은 “클라우드 아키텍처 가이드” 중 “통합 아키텍처” 부분을 참조하기 바라며, 분산 트랜잭션 처리를 위한 프레임웍 혹은 모듈 개발이 필요하기 때문에 본 문서에서는 다루지 않고 향후 과제로 남겨두도록 한다.

 

4.5. 분산 Aggregation 처리

분산 환경에서 Aggregation 처리는 본 문서에서 다루지 않는다.

 

4.6. 분산 환경 정적 컨텐츠 처리

On-Premise 환경에서 웹 어플리케이션은 Web서버나 WAS에서 정적 컨텐츠에 대한 처리를 담당한다. 빈번하게 사용되는 리소스에 대해서는 Web서버 앞에 캐시/프록시 성격의 장비를 두어 처리하기도 하며 Amazon CloudFront 서비스가 이에 해당한다. Amazon S3에 콘텐츠가 업로드되어 있다는 전제하에 CloudFront 를 이용하여 콘텐츠 제공하기 위한 과정은 다음과 같다.

  1. 아마존 콘솔의 Services > Networking & Content Delivery > CloudFront > Distributions 에서 [Create Distribution] 선택
  2. [Select a delivery method for your content]의 [Web] 섹션에서 [Get Started]를 선택
  3. [Create Distribution] 페이지의 [Origin Settings]에서 기존에 생성한 Amazon S3 버킷을 선택. [Origin ID], [Origin Path], [Restrict Bucket Access] 및 [Origin Custom Headers]에는 기본값을 그대로 사용.
  4. [Default Cache Behavior Settings], [Distribution Settings] 항목 기본값 그대로 사용. [Create Distribution]을 선택
  5. 배포를 생성하면 배포에 대한 Status 열의 값이 InProgress에서 Deployed로 변경되며, Deployed 로 변경된 이후에 링크테스트가 가능하다.

CloudFront Distribution 생성결과를 아래 그림과 같이 확인할 수 있다. CloudFront 에서 배포에 배정한 Domain Name 대신 다른 고유의 Domain Name을 사용하고자 하는 경우 위 4번의 [Distribution Settings] 단계에서 Alternate Domain Names(CNAMEs) 설정을 하면 된다.



그림 16 CloudFront Distribution 생성 결과


링크테스트한 결과는 다음과 같이 확인할 수 있다.


그림 17 CloudFront 링크테스트


위 이미지에 대한 S3 bucket URL은 https://s3.ap-northeast-2.amazonaws.com/au-poc-sorc-jpetstore/images/banner_cats.gif 이며 s3.ap-northeast-2.amazonaws.com/au-poc-sorc-jpetstore 대신 CloudFront의 Domain Name인 ddcelibcttslq.cloudfront.net 을 사용하여 해당 이미지를 제공받을 수 있다. 최초 요청 시에는 해당 이미지가 CloudFront 에 없으므로 S3 bucket으로부터 가져오며 위 그림처럼 Response Header의 x-cache 값이 Miss from cloudfront 로 표시된다. 이 후 해당 이미지를 재요청하게 되면 CloudFront 에 cache된 이미지가 제공되며 x-cache의 값도 Hit from cloudfront 로 표시된다.

 

PoC 대상인 jpetstore의 경우 별도의 Alternate Domain Names(CNAMEs)을 설정(static.lgcns-au-poc.tk)하고 그에 맞게 Route53 서비스에서도 관련 설정을 하였다. 소스단에서도 해당 설정을 적용하기 위해 코드 수정을 하였다. 우선 Alternate Domain Names(CNAMEs) 정보를 별도의 설정 파일에서 관리하도록 하였다.


그림 18 domain 정보 설정 파일

위 설정 파일의 정보를 사용하기 위해서 그림 19 와 같이 spring properties 설정을 추가하였다.


그림 19 spring properties 설정


화면소스에서는 spring:eval 태그를 이용하여 설정 파일의 property 정보를 사용한다.


그림 20 설정 파일 property 사용 코드

 

해당 화면을 브라우저에서 확인해보면 Alternate Domain 으로 요청하는 것을 볼 수 있다.





그림 21 alternate domain 정보 확인



5. 클라우드 환경 정적 컨텐츠 처리 방식

아마존 클라우드 환경에서는 그림 22과 같이 정적 컨텐츠 처리를 위한 아마존 스토리지 서비스인 S3를 활용하여 사용자가 웹서버가 아닌 S3에서 정적 컨텐츠를 다운받을 수 있도록 처리할 수 있다.


그림 22. Web 스토리지 S3 활용 정적 컨텐츠 처리 설계


아마존 클라우드 환경에서는 그림 23와 같이 정적 컨텐츠 처리를 위한 아마존 스토리지 서비스인 S3와 에지 캐시 서비스인 CloudFront를 활용하여 정적 컨텐츠를 처리하도록 설계한다.


그림 23. Cache Distribution 패턴


5.1. S3 활용 정적 컨텐츠 처리 구현

아마존 콘솔에서 Services > Storage > S3에 정적 컨텐츠를 upload한다. 예시는 S3 > au-poc-sorc-jpetstore > images 폴더에 이미지 파일을 upload 한 것이다. upload 후, 이미지 파일을 public으로 변경하면 S3 url로 접근이 가능하다.



그림 24. S3의 images 폴더


그림 25. images 파일 upload



그림 26. image 파일 public 으로 변경




그림 27. S3 url로 image 호출


5.2. 클라우드 환경 파일 공유

클라우드 환경에서 멀티 인스턴스간 파일 공유는 S3를 통해서 할 수 있다.

그림 28에서처럼 WAS 1번에 있는 서비스 A에서 S3에 파일 A를 업로드하면 WAS3번에 있는 서비스 B에서 S3에 있는 파일 A를 다운로드할 수 있다.



그림 28. WAS 멀티 인스턴스 간 파일 공유


AWS S3에 파일 업로드를 하기 위해서는 먼저 S3 버킷을 생성해야 한다. 버킷 생성은 AWS SDK for Java를 활용해 프로그램에서 할 수도 있으나 기생성되어 있는 경우 아래 표 25과 같이 크레덴셜을 사용해 아마존 S3에 연결하고 파일을 업로드하는 방식으로 할 수 있다.

 

private String uploadFile(String originalName, byte[] fileData) throws Exception {

        UUID   uid = UUID.randomUUID();          

        String   savedName = uid.toString() + "_" + originalName;

       

      

        AWSCredentials credentials = null;

        try {

            credentials = new   ProfileCredentialsProvider("default").getCredentials();

        } catch (Exception e) {

            throw new   AmazonClientException(

              "Cannot load the credentials from the credential profiles   file. " +

              "Please make sure that your credentials file is at the   correct " +

              "location (C:\\Users\\XXX\\.aws\\credentials), and is in   valid format.",

                    e);

        }

 

        AmazonS3 s3 = new AmazonS3Client(credentials);

        Region usWest2 = Region.getRegion(Regions.AP_NORTHEAST_2);

        s3.setRegion(usWest2);

              

 

표 25. AWS S3 파일 업로드를 위한 크레덴셜 및 리젼 설정




S3 클라이언트 객체 생성 후 리전을 설정한 후에는 아래 표 26에서처럼 버킷명과 파일 키명을 만든 후 File 객체를 이용해 S3 클래스의 putObject 메소드를 사용하여 파일을 업로드한다.

 

아마존 S3는 외부에서도 접속이 가능하므로 아래와 같은 코드를 이용하여 로컬 PC에서도 접속 테스트가 가능하다.

 

private String uploadFile(String originalName, byte[] fileData) throws Exception {

        UUID   uid = UUID.randomUUID();          

        …

 

        String bucketName = "au-poc-sorc-jpetstore";          

        String key = uid.toString() + "_" + originalName;

 

        try {

 

            File file = File.createTempFile(originalName, "");

            file.deleteOnExit();                       

 

            Writer writer = new   OutputStreamWriter(new FileOutputStream(file));

           

            String strFileData = new String(fileData);

            writer.write(strFileData);                     

            writer.close();

           

            s3.putObject(new   PutObjectRequest(bucketName, key, file));

 

 

        } catch   (AmazonServiceException ase) {

            System.out.println("Caught an AmazonServiceException, which means

your request made it " + "to Amazon S3, but   was rejected with an

error response for some reason.");

            System.out.println("Error Message:      " + ase.getMessage());

}

        return savedName;

                      

      }

표 26. S3 파일 업로드 처리 코드


위와 같이 파일을 업로드 하면 아마존 AWS 콘솔에서 해당 파일이 버킷에 생성된 것을 확인할 수 있다. 

파일 업로드 후 AWS 콘솔에서 해당 파일정보를 조회 결과는 아래 그림 29과 같다.


그림 29. 파일 업로드 후 파일 정보


파일 생성 확인 후 해당 파일을 인터넷에서 다운로드하려하면 그림 30과 같이 403에러가 발생한다. 에러가 발생하는 사유는 파일에 대한 권한 때문인데, SDK를 사용해 Java 프로그램에서 파일을 업로드한 경우 해당 프로그램을 구동한 크레덴셜이 있는 머신의 Java 프로그램에서는 파일을 다운로드 가능하지만, 인터넷 브라우저에서는 해당 파일을 다운로드 받을 수 없다.



그림 30. AWS 콘솔에서 파일 링크 클릭 시 오류


파일을 다운로드 받기 위해서는 AWS 콘솔에서 해당 파일에 대한 권한을 부여하거나 Java 프로그램에서 업로드시에 파일에 대한 권한을 부여해줘야 한다.


그림 31. S3 파일 업로드 후 권한 조회



그림 32. 파일 권한 부여


위 그림 32과 같이 파일 권한 부여 후에는 아래 그림 33와 같이 파일 내용이 정상 표시된다.



그림 33. 파일 권한 부여 후 파일 다운로드








6. WEB Server / ELB 연동

6.1. 개요

On-Premise 환경과 달리 클라우드 환경에서는 EC2 instance의 Autoscaling 이 가능하며 이것을 구현하기 위해서 ELB 서비스를 이용한다. 즉, WEB Server와 WAS 간의 연동을 직접하는 것이 아니라 사이에 ELB를 구성하여 WAS의 Autoscaling 이 가능하도록 할 수 있다.

결과적으로 WEB Server(EC2) ↔ ELB ↔ WAS(EC2) 와 같이 구성되어야 WEB Server와 ELB 연동을 위한 설정 내용을 기술한다. (WEB Server는 Apache HTTP Server 2.4.25 버전 사용)

 

6.2. EB Server 설정

6.2.1. LoadModule 추가

$APACHE_HOME/conf/httpd.conf 설정파일에 proxy 관련 module 을 추가한다.

LoadModule proxy_module   modules/mod_proxy.so

LoadModule   proxy_http_module modules/mod_proxy_http.so

6.2.2. Proxy 관련 설정

$APACHE_HOME/conf/httpd.conf 설정파일의 VirtualHost 태그에 proxy 관련 설정을 추가한다.

-      DocumentRoot : 어플리케이션 리소스의 root 경로

-      ServerName : WEB Server IP

-      ProxyPass, ProxyPassReverse : ELB URL, 아래 설정된 예시의 경우 http://52.78.75.101/ 요청은 모두 http://dev-elb-frt-1279101461.ap-northeast-2.elb.amazonaws.com:8080/ 로 보내게 된다.

<VirtualHost *:80>

    ServerAdmin   webmaster@dummy-host.example.com

    DocumentRoot "/sorc/webhome"

    ServerName 52.78.75.101

    ServerAlias www.jpetstore.com

    ErrorLog   "/logs/apache/vhost-error_log"

    CustomLog   "/logs/apache/vhost-access_log" common

 

    ProxyRequests Off

 

    <Proxy *>

        Order deny,allow

          Allow from all

    </Proxy>

 

    ProxyPass "/" "http://dev-elb-frt-1279101461.ap-northeast-2.elb.amazonaws.com:8080/"

    ProxyPassReverse "/" "http://dev-elb-frt-1279101461.ap-northeast-2.elb.amazonaws.com:8080/"

</VirtualHost>

6.3.  WEB Server restart 및 화면 확인

$APACHE_HOME/bin/apachectl –k graceful 명령을 수행하여 WEB Server restart 후에 http://52.78.75.101/jpetstore 에서 화면을 확인한다.



 

그림 34. jpetstore 화면 확인

댓글

이 블로그의 인기 게시물

파일처리(한번에 모두읽기, 라인단위로 읽기, 쓰기, 리스트처리, 특정길이만큼 읽기)

AWS 가용성,확장성

Serverless computing 도입시 고려사항