코드로 살펴보는 데이터 그리드 기반 대용량 처리 아키텍처
DESCRIPTION
http://www.opennaru.com/ http://opennaru.tistory.com 데이터 그리드를 기반으로 대용량 트랜잭션을 처리할 수 있는 아키텍처 프로토타입을 소개합니다. 아키텍처의 주요 특징은 비동기 메세징을 이용하여 신속하게 데이터를 입력받고, 데이터그리드를 이용하여 데이터베이스의 부하를 낮추는 것입니다. 주요 적용 오픈소스 Wildfly , JBoss Data Grid, MariaDB 이고, 적용 기술들은 WebSocket, Rest API, JMS , EDB (Event Driven Bean), Spring, MyBatis 등 입니다. 주요해결 과제는 다음과 같습니다. * 대용량 데이터처리를 위한 빠른 프로세싱을 위하여 비동기 메세지를 사용 * 장애시에도 메세지 전달 보장 ( Reliable Message Delivery ) * 데이터베이스의 부하인한 전체 시스템 성능 저하 방지를 위한 인메모리 데이터 그리드 적용 * 고성능 양방향 네트워크 인터페이스를 위한 WebSocket 사용 오픈나루 블로그 - http://opennaru.tistory.com/ 오픈나루 홈페이지 - http://www.opennaru.com/TRANSCRIPT
In-Memory Data Grid 기반 대용량 처리
Architecture Prototype
opennaru.com | 2013 | All Rights Reserved 1
인메모리 데이터그리드기반의 대용량 처리 아키텍처
• WebSocket을 이용한 대용량 데이터 입력 (Async)
• Restful API 를 이용한 데이터 조회 및 조작 (Sync)
• JBoss Data Grid 를 이용한 데이터베이스 Cache
MariaDB Database
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
opennaru.com | 2013 | All Rights Reserved 2
시스템 구현 방안
• 데이터 젂송은 WebSocket 프로토콜을 사용
• 테스트 용도로 RESTful API 구현(RESTEasy 사용)
• 데이터 입력과 처리를 분리하기 위하여 Message Queue를 사용
• 데이터 처리는 MDB(Message Driven Bean)을 사용하여 동시에 처리할 수 있도록 함
• JBoss Data Grid의 캐시를 사용하여 SQL 쿼리 수를 최소화
• 간단한 테이블 구조를 사용(User 정보)
• 코드 테이블은 On-Demand 방식으로 JBoss Data Grid 캐시에 적재
opennaru.com | 2013 | All Rights Reserved 3
시스템 상세 아키텍처
MariaDB Database
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
opennaru.com | 2013 | All Rights Reserved 4
MariaDB 테이블 정보
opennaru.com | 2013 | All Rights Reserved 5
메시지 Flow(POST)
JBoss
Data Grid DB에 저장
Enrich
opennaru.com | 2013 | All Rights Reserved 6
Header text
opennaru.com | 2013 | All Rights Reserved 7
WebSocket
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
• JSR 356 표준 API
• Full duplex bi-directional 통싞
• Undertow ( Wildfly )
@ServerEndpoint("/websocket") public class WebSocketServer {
public static ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<String, Session>(); @OnMessage public String onMessage(String message, Session session) {
System.out.println("-------------- WebSocket ------------"); System.out.println(">>>>> Received : "+ message); MessageSender.sendMessage(session.getId(), message); System.out.println("-------------------------------------"); return null;
} }
opennaru.com | 2013 | All Rights Reserved 8
JMS Queue
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
• Java Messaging Service
• 메시지 젂달 보장
• 비동기 통싞을 위한 서비스
• HornetQ 구현체 사용(JBoss 포함)
<jms-destinations> <jms-queue name="demoQueue">
<entry name="java:jboss/queue/demoQueue"/> <durable>true</durable>
</jms-queue> </jms-destinations>
opennaru.com | 2013 | All Rights Reserved 9
Message Driven POJO(Bean)
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
• Listener 사용 비동기 처리
• JMS Queue의 메시지를 받아 처리
• 멀티 스레드로 동시 처리
@Component public class MessageExtractor implements MessageListener {
@Autowired private CustomerService customerService; private ObjectMapper mapper; public void onMessage(Message message) {
try { String msg = ((TextMessage) message).getText(); Command command = mapper.readValue(msg, Command.class); System.out.println("================ onMessage =============="); System.out.println(">>>>> command=" + command); ...
} catch (Exception e ) { }
opennaru.com | 2013 | All Rights Reserved 10
JAX-RS – RESTful API
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
• JAX-RS JSR-311 표준 API
• EJB, Spring Integration
• RESTEasy 사용(JBoss 포함)
@Controller @Path("/api") public class CustomerController {
@GET @Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"}) @Path("/customer/{userid}") public Customer getCustomerById(@PathParam("userid") String userid) throws Exception { return customerService.getCustomerById(userid); } @POST @Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"}) @Path("/customer/{userid}") public Customer updateCustomer(@PathParam("userid") String userid, Customer customer) throws Exception {
customer.setUserid(userid); return customerService.updateCustomer(customer);
} @DELETE @Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"}) @Path("/customer/{userid}") public Customer deleteCustomer(@PathParam("userid") String userid, Customer customer) throws Exception {
customer.setUserid(userid); return customerService.deleteCustomer(customer);
opennaru.com | 2013 | All Rights Reserved 11
@Service
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
• Spring MVC Framework
• Spring 컴포넌트
@Service public class CustomerService {
private Logger logger = LoggerFactory.getLogger(CustomerService.class); @Autowired CustomerDao customerDao; public Customer getCustomerById(String userid) {
return customerDao.getCustomerById(userid); }
}
opennaru.com | 2013 | All Rights Reserved 12
@Repository, @Cacheable
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
• MyBatis를 사용하여 구현
• Infinispan Spring 연동 모듈 사용
@Repository public class CustomerDaoImpl extends AbstractDao implements CustomerDao {
@CachePut(value="customerCache", key="#customer.userid") public Customer insertCustomer(Customer customer) {
int ret = getSqlSession().insert("customer.insertCustomer", customer); return customer;
} @CacheEvict(value="customerCache", key="#customer.userid") public Customer deleteCustomer(Customer customer) {
int ret = getSqlSession().delete("customer.deleteCustomer", customer); return customer;
} @CachePut(value="customerCache", key="#customer.userid") public Customer updateCustomer(Customer customer) {
int ret = getSqlSession().update("customer.updateCustomer", customer); return customer;
} }
opennaru.com | 2013 | All Rights Reserved 13
JBoss Data Grid(Library Mode)
WebSocket Server
@ServerEndpoint
Java Messaging Service
demoQueue
JAX RS
@Path @Controller
Message Driven Bean
@Service
MyBatis
@Repository @Cachable
Message Driven Bean Message Driven POJO(Bean)
@MessageDriven
JBoss
Data Grid
JBoss Enterprise Application Platform
• JSR 107, 347 표준 API 지원
• JBoss Data Grid(Infinispan) 사용
• Spring Framework 연동 모듈 제공
<namedCache name="customerCache"> <jmxStatistics enabled="true"/> <unsafe unreliableReturnValues="false"/> <clustering mode="distribution">
<stateTransfer awaitInitialTransfer="false" chunkSize="10000" timeout="240000"/> <sync replTimeout="200000"/> <hash numOwners="2" numSegments="80"/> <l1 enabled="true" lifespan="600000"/>
</clustering> </namedCache> <namedCache name="zipcodeCache">
<jmxStatistics enabled="true"/> <unsafe unreliableReturnValues="false"/> <clustering mode="distribution">
<stateTransfer awaitInitialTransfer="false" chunkSize="10000" timeout="240000"/> <sync replTimeout="200000"/> <hash numOwners="2" numSegments="80"/> <l1 enabled="true" lifespan="600000"/>
</clustering> </namedCache>
opennaru.com | 2013 | All Rights Reserved 14
데모 시스템 구성
test11 JBoss(Wildfly) Instance
test12 JBoss(Wildfly) Instance
Java SE WebSocket Client
opennaru.com | 2013 | All Rights Reserved 15
장애 복구 후 처리 속도 문제
• JMS(HornetQ)는 처리 속도를 향상하기 위한 버퍼링 기능 제공함
• Consumer에 메시지를 미리 가져다 놓아 다음 메시지 처리 속도를 향상 시킴
• consumer-window-size는 1 MB 기본값
• 속도가 느린 Consumer이 경우 버퍼링 되기 때문에 오히려 속도가 느려짐
• 속도가 느린 Consumer에서는 consumer-window-size를 0 으로 설정
Message Queue
Consumer
메시지 처리
메시지 처리
메시지 처리
opennaru.com | 2013 | All Rights Reserved 16
Header text
opennaru.com | 2013 | All Rights Reserved 17
In memory storage engines
Distributed across a cluster providing “networked memory”
Pacemakers to databases
Provide simple key,value storage
Linear scalability and elasticity due to distributed a
lgorithms
What is a Data Grid
opennaru.com | 2013 | All Rights Reserved 18
Cache 적용 방안과 이슈
B C
D
지연시간
Read 지연시간
A B C
D
Application
A
지연시간
Read
WAS
A
Application Cache
A
B C
D A
Read
DataGrid #1
DataGrid #2
DataGrid #3
DataGrid #4
B C
D
A
A
B
C
D WAS
Application
A
C
D
B
A
지연시간
Read
WAS #1
Application Cache
B C
D
A’
A’ B
C
D A
WAS #2
Application Cache
A B
C
D A
Step1 : UPDATE … SET ...’A” …
Step2 : put() 캐쉬 불일치 (Incoherent)
DB와 불일치 (Inconsistency)
No Cache Local Cache
Cache Consistency Issue Distributed Cache
opennaru.com | 2013 | All Rights Reserved 19
가상화 기반 환경에서 Scale out 고려 사항
DataGrid #1 DataGrid #2 DataGrid #3 DataGrid #N
Virtual Machine
WAS #1
Application
Virtual Machine
WAS #2
Application
Virtual Machine
WAS #3
Application
Virtual Machine
WAS #1
Application
Virtual Machine
WAS #2
Application
Virtual Machine
WAS #3
Application
Virtual Machine
WAS #N
Application
opennaru.com | 2013 | All Rights Reserved 20
opennaru.com | 2013 | All Rights Reserved 21
Eviction (제거)
• Eviction는 메모리가 부족해지지 않도록 메모리에서 엔트리를 삭제하는 과정
• 데이터 손실을 막기 위해서는 CacheStore 에 저장
• Eviction은 젂체 클러스터를 대상으로 하지 않고 개별 노드를 대상으로 작업
• Eviction 젂략
• NONE - 설정되어있는 Eviction 젂략 없음
• FIFO - first-in-first-out (선입 선출) 젂략
• LRU - least-recenty-used 젂략
• UNORDERED - 무작위 Eviction 젂략
• LIRS - Low Inter-reference Recency Set 패턴
• Eviction 선언 예
• 최대 1000 개 엔트리, LRU Eviction 젂략, 500 ms 마다 Eviction 쓰레드 실행
<infinispan>
<namedCache name="evictionCache">
<eviction maxEntries="1000“ strategy="LRU” wakeUpInterval="500" />
</namedCache>
</infinispan>
opennaru.com | 2013 | All Rights Reserved 22
Expiration (맊료) – 1/2
• Eviction와 Expiration 차이
• Eviction - 엔트리 최대수를 넘었을 경우에 삭제
• Expiration - 엔트리 보관 기간을 넘겼을 경우에 삭제
• Expiration는 지정된 시간을 넘긴 엔트리를 삭제
• 엔트리에 lifespan, maximum idle time을 설정하고 초과하는 엔트리는 제거
• Expiration 엔트리는 Eviction 엔트리와 같이 passivate 되지 않음
• Expiration 엔트리는 글로벌하게 삭제
• 메모리, CacheStore, 클러스터 와이드 모두
• Expiration의 예
• HTTP Session
• SFSB Session
opennaru.com | 2013 | All Rights Reserved 23
Expiration (맊료) – 2/2
• evictionCache 라는 이름의 Cache 선언
• 최대 1,000 개 엔트리맊 메모리에 저장
• Expiration 쓰레드는 500ms 마다 실행
• 엔트리는 생성된 지 60,000 ms 되거나 사용된지 10,000 ms 가 지나면 둘 중 먼저의 경우에 맊료
<infinispan>
<namedCache name="evictionCache">
<eviction maxEntries="1000"
strategy="LRU” />
<expiration
wakeUpInterval="500"
lifespan="60000"
maxIdle="10000” />
<loaders passivation="true">
<loader
class="org.infinispan.loaders.file.FileCacheStore">
<properties>
<property name="location"
value="${java.io.tmpdir}"/>
</properties>
</loader>
</loaders>
</namedCache>
</infinispan>
opennaru.com | 2013 | All Rights Reserved 24
Header text
opennaru.com | 2013 | All Rights Reserved 25
Query API
• 풀텍스트 검색 엔진 Apache Lucene의 Lucene Directory 로 구현
• put시에 데이터 index를 생성/저장
• Hibernate Search API를 사용해 검색
opennaru.com | 2013 | All Rights Reserved 26
Header text
opennaru.com | 2013 | All Rights Reserved 27
•캐쉬 조작을 JTA 트랜잭션(transaction)상에서 사용할 수 있습니다. • MVCC(Multi-Versioned Concurrency Control) • 캐쉬 엔트리 락 • commit/rollback의 지원 • 동일 트랜잭션(transaction)내에서의 복수의 캐시 인스턴스 조작 • 데드락 검출
try { utx.begin(); Integer value = cache1.get(key); // get에서는 분산 락은 취득하지 않는다 cache1.lock(key) // 명시적인 분산 락 취득[1] value = cache1.get(key); value = value + 1; cache1.put(key, value); // 데이터 변경 cache2.put(key, value); // 암묵적인 분산 락의 취득[2], 데이터의 변경 utx.commit(); // 변경의 반영, 분산 락 개방 } catch (Exception e) { utx.rollback(); // 변경의 취소, 분산 락 개방 }
• Distribution 캐쉬의 경우, commit 시 데이터를 복제 합니다.
[1] get에서는 락을 취득하지 않기 때문에, get→put를 배타적으로 하려면, 명시적으로 락을 취득할 필요가 있습니다. [2] put 조작은 락을 취득하기 때문에, 이 조작에서는 암묵적으로 락 취득을 합니다. 암묵적인 락 취득하게 하려면, 캐쉬
설정을<transaction lockingMode="PESSIMISTIC">과 같이 지정해야 합니다.
JTA 트랜잭션 지원
opennaru.com | 2013 | All Rights Reserved 28
•배치 API는, put을 여러 차례 배치로 조작할 때 성능을 향상시키기 위해 사용할 수 있는 API 입니다. •배치 API는 JTA 트랜잭션(transaction)의 지원과 거의 동일한 기능이 되지맊, 트랜잭션(transaction)에 참가 할 수 있는 자원이 1개 JDG 캐시 인스턴스로 한정되는 점이 다릅니다.
try { cache.startBatch(); // 배치 트랜잭션 시작 cache.put("k1", "value"); // 분산 락 취득, 데이터의 변경 cache.put("k2", "value"); // 분산 락 취득, 데이터의 변경 cache.put("k3", "value"); // 분산 락 취득, 데이터의 변경 cache.endBatch(true); // 변경의 반영, 분산 락 개방 } catch (Exception e) { cache.endBatch(false); // 변경의 취소, 분산 락 개방 }
배치 API 지원
opennaru.com | 2013 | All Rights Reserved 29
void putAll(Map<? extends K,? extends V> map, long lifespan, TimeUnit unit)
NotifyingFuture putAllAsync(Map<? extends K,? extends V> data)
putAll
• putAll API는, put을 여러 차례 배치로 조작할 때 성능을 향상시키기 위해 사용할 수 있는 API 입니다. • Collection의 객체를 한꺼번에 Cache에 put하여 배치 Loading시에 유리합니다. •배치 Loading시 put / putAll을 비교하면 성능이 몇 배 이상 빨라집니다.
opennaru.com | 2013 | All Rights Reserved 30
Header text