'2016/08/04'에 해당되는 글 9건

  1. 2016.08.04 MySQL Cluster
  2. 2016.08.04 스케일 아웃 측면에서 살펴본 MySQL의 특성
  3. 2016.08.04 In Memory Data Grid
  4. 2016.08.04 Do You Really Get Classloaders?
  5. 2016.08.04 JVM ClassLoader의 작동원리
  6. 2016.08.04 클래스로더 1, 동적인 클래스 로딩과 클래스로더 3
  7. 2016.08.04 java class loader #2
  8. 2016.08.04 java class loader #1 1
  9. 2016.08.04 [Java] ClassLoader API

MySQL Cluster

DB/MySQL 2016. 8. 4. 18:08

Introduction 

MySQL 클러스터는 분산 컴퓨팅 환경에서 high-availability와 high-redundancy를 채택하였다. MySQL 클러스터는 NDB 클러스터 스토리지 엔진을 사용하여, 클러스터에서 여러 개의 서버가 함께 돌아가도록 한다. MySQL 클러스터가 지원하는 운영 체제는 Linux, Mac OS X, Solaris 등 이다. 더 자세한 정보는 다음 사이트를 참고 하길 바란다. http://www.mysql.com/products/cluster

MySQL Cluster Overview 


MySQL 클러스터는 share-nothing 시스템에서 in-memory 데이터 베이스의 클러스터링을 가능하게 한다. 이러한 아키텍쳐는 특정한 하드웨어 및 소프트웨어를 요구하지 않으므로 비용을 절감할 수 있도록 하며, 각 콤포넌트가 고유 메모리와 디스크를 보유함으로 단일 취약점(single point of failure)을 가지지 않는다.

MySQL 클러스터는 일반 MySQL 서버에 NDB라는 스토리지 엔진을 통합하여, 다음 그림과 같이 MySQL서버, NDB 클러스터의 데이터 노드, MGM 서버가 포함된 컴퓨터와 데이터에 접근하기 위한 어플리케이션 프로그램으로 구성된다.

데이터가 NDB 클러스터 스토리지 엔진에 저장될 때, 테이블은 데이터 노드에 저장된다. 각 테이블은 클러스터의 MySQL 서버에서 직접 접근이 가능하다. 그래서 클러스터의 어떤 정보를 업데이트 하면, 다른 모든 MySQL서버에서 곧바로 확인할 수 있다.

MySQL 클러스터의 데이터 노드에 저장된 데이터는 미러링이 가능하며, 클러스터는 트랜잭션 중단 등 각 노드들의 상태에 대한 핸들링이 가능하다.

http://dev.mysql.com/doc/refman/5.0/en/images/cluster-components-1.png
MySQL 클러스터의 구성

Basic MySQL Cluster Concepts 


NDB는 높은 가용성과 데이터 지속성을 갖는 인 메모리 스토리지 엔진이다. DB 스토리지 는 failover와 로드 밸런싱 옵션을 설정할 수 있다. MySQL클러스터는 NDB 스토리지 엔진과 MySQL 서버로 구성되어 있으며, MySQL 클러스터의 클러스터 부분은 MySQL 서버에 독립적이다. MySQL 클러스터의 각 부분은 노드로 간주한다.

"노드"는 일반적으로 컴퓨터를 지칭하지만 MySQL 클러스터에서는 "프로세스"를 말한다.


클러스터 노드에는 세 가지 타입이 있으며, MySQL Cluster를 구성하기 위해 최소한 노드 세 개가 있어야 한다.

  • MGM node : 이 노드는 설정을 포함, 다른 노드를 관리하는 매니저 노드이다. 다른 노드보다 가장 먼저 실행되며 ndb_mgmd 명령으로 실행시킨다.
  • data node : 클러스터의 데이터를 저장하는 노드이다. ndbd 명령으로 실행시킨다.
  • SQL node : 클러스터 데이터에 접근하는 노드이다. MySQL 클러스터에서는 NDB 클러스터 스토리지 엔진을 사용하는 MySQL 서버가 클라이언트 노드이다. mysqld --ndbcluster나 mysqld 명령으로 실행시키는데, 이 때는 my.cnf 에 ndbcluster를 추가한다. 

MGM 노드는 클러스터 컨피그레이션 파일과 로그를 관리한다. 데이터 노드에 이벤트가 발생하면, 데이터 노드는 그에 대한 정보를 매니저 서버로 보내고, 매니저 서버는 클러스터 로그를 기록한다.


Simple Multi-Computer How-To 


다음과 같이 4대의 컴퓨터로 클러스터를 구성하는 것을 가정하고 있다. (4개의 노드로 구성되고, 각각의 노드는 편이성을 위해 IP로 지칭한다.)

아래에서 필요한 컴퓨터는 리눅스가 설치된 인텔 기반 데스크탑 PC이며, 4대 모두 동일한 이더넷 카드(100Mbps나 1기가 비트)가 필요하다.

Node IP Address

Management (MGM) node 192.168.0.10

MySQL server (SQL) node 192.168.0.20

Data (NDBD) node "A" 192.168.0.30

Data (NDBD) node "B" 192.168.0.40
http://dev.mysql.com/doc/mysql/en/images/multi-comp-1.png


설치 및 사용 시 주의할 점은 MySQL 클러스터는 클러스터 노드 간 커뮤니케이션에 암호화 및 보호 장치가 전혀 없으므로, 웹 상에서 사용하려면 방화벽을 사용하는 등의 보안상의 대책이 필요하다는 것이다.

MySQL Cluster를 사용하기 위해서는 -max 버전을 설치해야 한다. 모든 설치는 root권한으로 진행하며 작업에 필요한 파일은 /usr/local/ 에 저장한다.

1. /etc/passwd 와 /etc/group 파일에서 mysql 그룹과 유저가 있는지 확인한 후 없으면 다 음과 같이 생성한다.
# cd /usr/local
# groupadd mysql
# useradd -g mysql mysql

2. 유저와 그룹 생성 후 압축을 풀고, 심볼릭 링크를 걸어준다.
# tar -xzvf mysql-max-4.1.13-pc-linux-gnu-i686.tar.gz
# ln -s /usr/local/ mysql-max-4.1.13-pc-linux-gnu-i686 mysql

3. mysql 디렉토리로 이동하여 시스템 데이터베이스 생성을 위한 스크립트를 실행시킨다.
# cd mysql
# scripts/mysql_install_db --user=mysql

4. MySQL 서버와 데이터 디렉토리의 퍼미션을 설정한다.
# chown -R root .
# chown -R mysql data
# chgrp -R mysql .

5. 시스템 부팅 시 자동적으로 Mysql을 실행할 수 있도록 설정한다.
# cp support-files/mysql.server /etc/rc.d/init.d/
# chmod +x /etc/rc.d/init.d/mysql.server
# chkconfig --add mysql.server

6. MGM (management) 노드를 별도의 PC에 설치할 경우 mysql 데몬은 설치하지 않아도 무방하다. 위와 같이 설치한 후 MGM 서버는 다음과 같이 설치를 계속한다.
# cd /usr/local/mysql/bin/
# cp ndb_mgm* /usr/local/bin/
# chmod +x ndb_mgm*

7. 각 데이터 노드와 SQL 노드는 MySQL서버 옵션과 connectstring에 대한 정보가 포함된 my.cnf파일이 필요하고, MGM노드는 config.ini 파일이 필요하다. 에디터를 열어 다음과 같이 편집한 후 파일을 저장한다.
# vi /etc/my.cnf 
[MYSQLD]			      # Options for mysqld process:
Ndbcluster			# run NDB engine
ndb-connectstring=192.168.0.10	# location of MGM node

[MYSQL_CLUSTER]		      # Options for ndbd process:
ndb-connectstring=192.168.0.10	# location of MGM node

8. MGM 노드의 설정 파일을 만들기 위해 적당한 디렉토리를 만든 후 에디터를 열어 다음과 같이 편집한다.
 # mkdir /var/lib/mysql-cluster
# cd /var/lib/mysql-cluster
# vi config.ini

[NDBD DEFAULT]		# Options affecting ndbd processes on all data nodes:
NoOfReplicas=2		# Number of replicas
DataMemory=80M	         # How much memory to allocate for data storage
IndexMemory=18M	         # How much memory to allocate for index storage
                  		# For DataMemory and IndexMemory, we have used the
                  		# default values. Since the "world" database takes up
                  		# only about 500KB, this should be more than enough
                  		# for this example Cluster setup.

[TCP DEFAULT]		# TCP/IP options:
portnumber=2202	         # This the default; however, you can use any
                  		# port that is free for all the hosts in cluster
                  		# Note: In MySQL 5.0, this parameter is deprecated;
                  		# it is recommended that you do not specify the 
                  		# portnumber at all and simply allow the port to be
                  		# allocated automatically

[NDB_MGMD]			# Management process options:
hostname=192.168.0.10		# Hostname or IP address of MGM node
datadir=/var/lib/mysql-cluster	# Directory for MGM node logfiles

[NDBD]				# Options for data node "A":
				# (one [NDBD] section per data node)
hostname=192.168.0.30		# Hostname or IP address
datadir=/usr/local/mysql/data	# Directory for this data node's 
				# datafiles

[NDBD]				# Options for data node "B":
hostname=192.168.0.40		# Hostname or IP address
datadir=/usr/local/mysql/data	# Directory for this data node's 
				# datafiles

[MYSQLD]			         # SQL node options:
hostname=192.168.0.20	         # Hostname or IP address
datadir=/usr/local/mysql/data	# Directory for SQL node's datafiles
				# (additional mysqld connections can be
				# specified for this node for various
				# purposes such as running ndb_restore)

설치와 설정 과정이 끝났다. 이제 실행을 해 보자.

클러스터 노드들은 각각 실행되어야 한다. 실행 순서는 매니지먼트 노드를 가장 먼저 실행할 것을 권한다. 그 다음은 스토리지 노드와 SQL노드 순이다.
1. 매니지먼트 호스트에서 MGM 노드 프로세스를 실행시켜 보자. 컨피그레이션 파일을 찾을 수 있도록 -f 옵션을 주도록 한다.
# ndb_mgmd -f /var/lib/mysql-cluster/config.ini
MGM 노드를 다운시킬 때에는 다음과 같이 하면 된다.
# ndb_mgm -e shutdown

2. 다음으로 데이터 노드 호스트에서 NDBD프로세스를 실행시킨다. --initial 이란 인수는 ndbd를 처음 실행할 때와 컨피그레이션이 바뀐 후 재시작 할 때만 사용한다.
# ndbd --initial

3. SQL 노드는 다음과 같이 mysql.server를 실행시킨다.
# /etc/rc.d/init.d/mysql.server start

4. 이제 모든 노드가 실행되었으니 MGM 노드 클라이언트를 띄워 간단히 테스트를 해보자.

Ndb_mgm명령어를 입력하였을 때 정상적으로 동작하는 모습은 다음과 같이 프롬프트가 떨어지는 모습이다.
# ndb_mgm
-- NDB Cluster -- Management Client --
ndb_mgm> 

5. 이제 show명령어를 사용하여 클러스터의 모든 노드들이 정상적으로 연동되는지 확인을 해 보자. HELP 를 입력하면 다른 명령어들도 확인해 볼 수 있다. 다음과 같이 4개의 노드를 구성하는 것에 성공하였다.
ndb_mgm> show
Connected to Management Server at: 192.168.0.10:1186
Cluster Configuration
---------------------
[ndbd(NDB)]     2 node(s)
id=2    @192.168.0.30  (Version: 4.1.13, Nodegroup: 0, Master)
id=3    @192.168.0.40 (Version: 4.1.13, Nodegroup: 0)

[ndb_mgmd(MGM)] 1 node(s)
id=1    @192.168.0.10 (Version: 4.1.13)

[mysqld(API)]   1 node(s)
id=4   (Version: 4.1.13)

ndb_mgm>

MySQL 클러스터의 제한 


MySQL Cluster 4.1.x 버전은 다음과 같은 사용상의 제한점을 지닌다.

  • 트랙잭션 수행 중의 롤백을 지원하지 않으므로, 작업 수행 중에 문제가 발생하였다면, 전체 트랙잭션 이전으로 롤백하여야 한다.
  • 실제 논리적인 메모리의 한계는 없으므로 물리적으로 허용하는 만큼 메모리를 설정하는 것이 가능하다.
  • 컬럼 명의 길이는 31자, 데이터베이스와 테이블 명은 122자까지 길이가 제한된다. 데이터베이스 테이블, 시스템 테이블, BLOB인덱스를 포함한 메타 데이터(속성정보)는 1600개까지만 가능하다.
  • 클러스터에서 생성할 수 있는 테이블 수는 최대 128개이다.
  • 하나의 로우 전체 크기가 8KB가 최대이다(BLOB를 포함하지 않은 경우).
  • 테이블의 Key는 32개가 최대이다.
  • 모든 클러스터의 기종은 동일해야 한다. 기종에 따른 비트저장방식이 다른 경우에 문제가 발생하기 때문이다.
  • 운영 중 스키마 변경이 불가능하다.
  • 운영 중 노드를 추가하거나 삭제할 수 없다.
  • 최대 데이터 노드의 수는 48개이다.
  • 모든 노드는 63개가 최대이다. (SQL node, Data node, 매니저를 포함) 


MySQL Cluster FAQ 


Cluster 와 Replication의 차이

리플리케이션은 비동기화 방식이고, 클러스터는 동기화 방식이다. 따라서 리플리케이션은 일방적으로 데이타를 전달하여 복제를 하지만 클러스터는 동기방식이므로 데이타를 복제한 후 결과를 확인하기 때문에 데이타 누락이 발생하지 않는다. 다만 복제한 결과를 확인해야 하기 때문에 Cluster가 Replication보다는 속도가 느리다. 또한 Replication의 경우 복제된 데이터에 대한 신뢰를 할 수 없다.


Cluster가 사용하는 네트워크 (How do computers in a cluster communicate?)

MySQL 클러스터는 TCP/IP를 통해 서로 통신한다. 최소한 100Mbps의 이더넷을 사용해야 하며 원활한 통신을 위해 gigabit 이더넷을 권고한다. 실제 데이터가 메모리에 존재하여 사용되며 물리적인 측면에서 봤을 때 CPU, 메모리, 각 노드간의 통신을 위한 네트워킹이 주를 이룬다. 이중 가장 속도가 느린 네트워크의 속도를 높임으로써 전체적인 빠른 동작이 가능하도록 해야 한다. 또한, 더욱 빠른 SCI 프로토콜도 지원하며, 이는 특정 하드웨어를 필요로 한다.


클러스터를 구성하기 위해 컴퓨터가 얼마나 필요한가?

최소한 3대가 있어야 클러스터 구성이 가능하나, MGM 노드와 SQL 노드, 스토리지 노드 둘, 이렇게 4 대로 구성하길 권한다. 하나의 노드가 실패했을 때 지속적인 서비스를 하기 위해서 MGM노드는 분리된 컴퓨터에서 실행되어야 한다.


클러스터에서 각 컴퓨터들이 하는 일은?

MySQL 클러스터는 물리적, 논리적으로 구성된다. 컴퓨터는 물리적 요소이며 호스트라고 불리기도 한다. 논리적, 기능적 요소는 노드이다. 노드는 역할에 따라 MGM 노드, data 노드(ndbd), SQL 노드로 나뉜다.


어떤 OS에서 사용할 수 있는가?
MySQL 4.1.12 현재 MySQL 클러스터는 공식적으로 Linux, Mac OS X, Solaris를 지원한다.


MySQL 클러스터가 요구하는 하드웨어 사양은?

NDB가 설치되고 실행되는 모든 플랫폼이면 가능하나, 당연히 빠른 CPU, 높은 메모리에서 더 성능이 좋다(64-bit CPU에서 더 빠르다). 네트워크은 일반 TCP/IP를 지원하면 되고, SCI 를 지원하려면 특정 하드웨어가 요구된다.


MySQL 클러스터가 TCP/IP를 이용한다면 하나 이상의 노드를 인터넷을 통해 다른 곳에서 실행시킬 수 있는가?

가능하다. 하지만 MySQL 클러스터는 어떠한 보안도 제공되지 않으므로, 외부에서 클러스터 데이터 노드나 매니저 노드에 직접 접근하지 못하도록 해야 한다.


클러스터 사용을 위해 새로운 프로그래밍 언어나 쿼리를 배워야 하나?

표준 (My)SQL 쿼리나 명령을 사용하므로 그러지 않아도 된다.


클러스터 사용 시 에러나 경고 메시지는 어디서 찾나 ?
두 가지 방법이 있다. MySQL창에서 SHOW ERRORS나 SHOW WARNINGS로 확인하는 방법과 프롬프트 상태에서 perror --ndb error-code 를 사용하는 방법이 있다.


MySQL Cluster transaction-safe? 어떤 테이블 타입이 클러스터를 지원하나?
MySQL에서 NDB 스토리지 엔진과 생성된 테이블은 트랜잭션을 지원한다. NDB는 클러스터링만 지원하는 MySQL 스토리지 엔진이다.


"NDB" 의 의미는?
"Network Database".


클러스터를 지원하는 MySQL 버전은? 소스를 컴파일 해야 하나?

MySQL-max 4.1.3부터 지원한다. 바이너리 파일은 컴파일을 할 필요가 없다.


RAM은 얼마나 필요한가? 디스크는 사용하지 못하나?

클러스터는 오직 in-memory이며, 모든 테이블 데이터(인덱스 포함)가 RAM에 저장된다. 클러스터에서 필요한 RAM용량은 다음 공식으로 계산한다. (SizeofDatabase * NumberOfReplicas * 1.1 ) / NumberOfDataNodes


ERROR 1114: The table 'my_cluster_table' is full

위와 같은 에러가 발생했을 때는 할당된 메모리가 부족한 경우이다.


FULL TEXT 인덱스를 지원하는가?

현재 지원하지 않는다.


하나의 컴퓨터에서 여러 개의 노드가 돌아가는가?

가능하긴 하지만 권하진 않는다. 각 노드들이 다른 컴퓨터에서 실행되는 것이 더 안정적이다.


클러스터를 재시작하지 않고 노드를 추가할 수 있는가?

할 수 없다. MGM 이나 SQL 노드를 추가하려면 새로 시작해야 한다.


어떻게 기존의 MySQL 데이터베이스를 클러스터로 임포트 하는가?

ENGINE=NDB 나 ENGINE=NDBCLUSTER 옵션을 가진 테이블은 임포트할 수 있다. 또는 ALTER 기능으로 기존의 테이블을 클러스터로 변환 사용할 수 있다.

- ALTER TABLE OLD_TABLE ENGINE=NDBCLUSTER;


Arbitrator란 ?

클러스터에서 한 개 혹은 그 이상의 노드가 실패할 경우, MGM 서버나 다른 노드가 그 노드의 역할을 대신하여 다른 노드들로 하여금 실패한 노드와 같은 노드로 인식하게 하는 기능을 한다. 이런한 역할을 하는 노드를 중재인이라고 한다.


클러스터 shut down시에 어떤 일이 일어나는가?

클러스터 데이터 노드의 메모리에 있던 데이터가 디스크에 쓰여지고, 그 다음에 클러스터가 시작될 때 다시 메모리에 로드된다.


클러스터에서 다른 매니저 노드를 구성하는 것은?

fail-safe에 있어서 도움이 된다. 단지 하나의 MGM 노드 만이 클러스터를 컨트롤 할 수 있지만 MGM 노드 하나를 primary로, 추가의 매니저 노드를 primary MGM 노드가 실패했을 때 인계받도록 하면 된다.


5. MySQL Cluster Glossary 


Cluster

일반적으로 Cluster는 하나의 업무를 수행하기 위해 함께 동작하는 컴퓨터 세트이다. NDB Cluster는 자료저장, 복구, 컴퓨터 간의 분배 관리 등을 시행하기 위해 MySQL을 사용하는 Storge Engine 이다. MySQL Cluster는 in-memory storage를 사용한 shared-noting 아키텍쳐에서 분산된MySQL DB를 지원하기 위해 NDB엔진을 사용하여 함께 돌아가는 컴퓨터 그룹이다.


Configuration Files

클러스터, 호스트, 노드에 관계된 직접적인 정보를 포함하는 파일이다. 클러스터 시작 시 Cluster의 MGM 노드가 읽어들인다.


Backup

디스크나 다른 Long-term Storage에 저장되는 모든 클러스터 데이타, 트랜젝션, 로그의 완전한 카피를 말한다.


Restore

백업에 저장되는 것과 같이 클러스터에 그 전 상태로 되돌리는 것을 말한다.


CheckPoint

일반적으로 데이타가 디스크에 저장될 때 체크포인트에 도달한다고 말한다. 클러스터에서는 Committed된 트랜잭션을 디스크에 저장하는 시간을 말한다. NDB Storage Engine에는 일관되게 클러스터의 데이타를 보존하기 위해 두 종류의 CheckPoint가 있다. LocalCheckPoint(LCP) : 싱글 노드의 체크포인트. 그러나 클러스터의 모든 노드에서 LCP를 사용한다. LCP는 디스크에 노드의 모든 데이타를 저장하도록 한다(보통 매 몇 분마다). 클러스터 Activity의 노드와 레벨, 다른 요인에 의해 저장되는 데이타의 양은 의존적이다. GlobalCheckPoint(GCP) : GCP는 모든 노드의 트랜잭션이 동기화되고, redo-log가 Disk에 저장될 때 몇 분마다 발생한다.


Cluster Host

MySQL Cluster의 구성 컴퓨터. 클러스터는 물리적 구조와 논리적 구조를 가진다. 물리적으로 클러스터는 Cluster Host라는 컴퓨터의 수로 구성된다.


Node

MySQL Cluster의 논리적, 기능적 요소를 말하며 Cluster Node라고도 한다. MySQL Cluster에서는 node란 용어를 Cluster의 물리적 Component인 Process를 지칭한다. MySQL Cluster가 동작하기 위해 3가지 타입의 노드가 있다.


MGM node - MySQL Cluster에서 다른 노드들의 설정 정보, 노드의 시작과 정지, 네트워크 파티셔닝, 백업과 저장 등을 포함하여 다른 노드들을 관리한다.


SQL node (MySQL Server) - 클러스터의 데이터 노드안에 저장된 데이터를 Serve 하는 MySQL Server 인스턴스. 데이타를 저장, 분배, 업데이트하는 클라이언트는 MySQL Server를 통해 접근 가능하다.


Data node - 이 노드는 실제 데이타를 저장한다. 현재 싱글 클러스터는 총 48개의 데이타 노드를 지원한다.

싱글 머신에 한 개 이상의 노드가 공존할 수도 있고, 한 머신에 완전한 클러스터를 구성하는 것도 가능하다. MySQL 클러스터에서 호스트는 클러스터의 물리적 컴퍼넌트이며, 노드는 논리적 혹은 기능적인 컴퍼넌트, 즉 프로세스라는 것을 잊지 말자.


Node group

데이터 노드의 집합. 노드 그룹 안의 모든 데이터 노드는 같은 데이터(fragment)를 포함한다. 그리고 싱글 그룹의 모든 노드는 다른 호스트에 존재해야 한다.


Node failure

MySQL 클러스터는 클러스터를 구성하는 어느 한 노드의 기능에만 의존적이지 않다. 클러스터는 하나 혹은 몇 개의 노드가 실패해도 계속될 수 있다.


Node restart

실패한 클러스터 노드의 리스타팅 과정.


Initial node restart

노드의 이전의 파일 시스템을 지우고 시작하는 클러스터 노드의 과정. 소프트웨어 향상과 그 밖의 특별한 상황 등에 사용된다.


System crash(or System fail)

클러스터의 상태가 확인되지 않는 등 많은 클러스터 노드가 실패했을 때 일어날 수 있다.


System restart

클러스터의 리스타팅과 디스크 로그 및 체크 포인트로부터 reinstall하는 프로세스를 말한다. 클러스터를 shutdown 한 이후에 일어나는 과정이다.


fragment

데이터베이스 테이블의 한 부분. NDB스토리지 엔진에서 테이블을 나누어 fragments의 수에 따라 저장한다. Fragment는 파티션이라 불리기도 한다. MySQL 클러스터에서 테이블은, 머신과 노드 간의 로드 밸런싱을 용이하게 할 수 있도록 fragment된다.


Replica

NBD 스토리지 엔진에서 각 테이블 프레그먼트는 여분을 포함하여 다른 데이터 노드에 저장된 많은 replica를 갖는다. 현재는 fragment 당 4개 이상의 replica가 가능하다.


Transpoter

노드들 간의 데이터 이동을 제공하는 프로토콜 TCP/IP(local), TCP/IP(remote), SCI, SHM(MySQL 4.1 버전에서 실험적임)


NDB(Network DataBase)

NDB는 MySQL클러스터에서 사용하는 스토리지 엔진을 말 함. NDB 스토리지 엔진은 모든 일반적인 MySQL 컬럼 타입과 SQL문을 지원하며, ACID(DB무결성 보장을 위한 트랜잭션)성질을 가진다.


Shared-nothing architecture

MySQL 클러스터의 이상적인 아키텍쳐. 진정한 Shared-nothing setup 에서 각 노드는 분리된 호스트에서 실행된다. 이러한 배열은 싱글 호스트나 싱글 노드가 아니면 SOF나 시스템 병목현상이 전체적으로 발생할 수 있다는 데 있다.


In-memory storage

각 데이터 노드에 저장된 모든 데이터는 그 노드의 호스트 컴퓨터의 메모리에 유지된다. 클러스터의 각 데이터 노드를 위해, (데이터 노드의 수로 나뉜 replica의 수 * 데이터베이스 사이즈)만큼의 가용 RAM의 양을 확보해 두어야 한다. 그러니까, 데이터베이스가 1기가의 메모리를 차지하고, 4개의 replica와 8개의 노드로 클러스터를 구성하고자 하면, 각 노드당 최소 500MB의 메모리가 필요하다. 그리고 OS와 다른 어플리케이션 프로그램이 쓰는 메모리가 추가로 필요하다.


Table

관계형 데이터베이스에서는 table은 일반적으로 동일하게 구조화된 레코드의 set을 가리킨다. MySQL 클러스터에서 데이터베이스 테이블은 fragment의 set으로써 데이터 노드에 저장되고, 각 fragment는 추가로 데이터 노드에 복제된다. 같은 fragment를 replicate한 데이터 노드의 set이나 fragment의 set을 노드 그룹이라 한다.


Cluster Programs : 명령어들

서버 데몬
  • ndbd : 데이터 노드 데몬
  • ndb_mgmd : MGM서버 데몬
클라이언트 프로그램
  • ndb_mgm : MGM 클라이언트
  • ndb_waiter : 클러스터의 모든 노드들의 상태를 확인할 때 사용
  • ndb_restore : 백업으로부터 클러스터의 데이터를 복구할 때 사용

Contributors 

처음 작성자 : 송은영,f405(ccotti22) f405@naver.com (2006.2.1 15:37)

작성일 : 2005년 8월 10일 수요일

이 문서는 MySQL Cluster 4.0대의 매뉴얼을 번역, 정리한 것으로 틀린 부분을 다소 포함할 수 있으며, 저는 그에 대한 책임을 지지 않겠습니다.

부족하지만 다른 분들도 공부하는데 도움이 되길 바랍니다.

그리고 이 문서를 작성하기 전 참고한 리눅스 및 MySQL 문서들을 작성하신 많은 선배님들에게 감사의 말씀을 드립니다. 그리고 이 경어는 생략하였습니다. 양해의 말씀을...

출처 - http://nuninaya.tistory.com/127

:

스케일 아웃 측면에서 살펴본 MySQL의 특성

DB/MySQL 2016. 8. 4. 18:07

스케일 아웃 측면에서 살펴본 MySQL의 특성

최근 인터넷 서비스들은 글로벌을 지향하고 소셜 네트워크 기능들을 추가하다 보니 데이터의 양과 트래픽의 양이 급속히 증가하고 있다. 또한 가용성 확보 목적으로 RDBMS도 이젠 스케일아웃(Scale-Out)에 자유롭지 못한 상황에 놓이게 되었다. RDBMS가 여러 측면에서 최상의 조합 기능을 제공하지만, 개별 영역의 장점을 추구하는 솔루션들보다 좋은 성능을 낼 수 없는 건 당연하다. 스케일아웃 관점에 맞는 솔루션을 찾을 필요가 있어 MySQL의 스케일아웃 측면에서 어떤 솔루션들이 있는지 살펴보고자 한다. 

[필자] 하호진 | KTH에서 포털 서비스 및 플랫폼(Identity/Payment) 개발 업무를 수행했으며, 현재 와이즈에코에서 이사로 일하고 있다. 개인 블로그 mimul.com/pebble/default를 운영한다.


1. MySQL Partitioning

1) 개요

파티셔닝은 대규모 테이블을 여러 개의 작은 파티션으로 분할해 성능을 높이는 기술이다. 각 행에 대해 각각의 파티션에 분할하며, MySQL에서는 5.1버전부터 지원한다. 파티셔닝은 주로 데이터 웨어하우스와 같은 거대한 테이블을 사용하는 경우에 위력을 발휘한다. 일반적으로 B 트리 인덱스가 커지면서 검색 및 삽입 속도가 저하된다(계산 순서는 O(log m N)). 하지만, 테이블을 파티션으로 나누는 것으로, B 트리 인덱스의 오버헤드를 줄일 수 있다.


 
그림 1 파티션된 테이블

파티션 테이블은 데이터의 분류 조건에 의해 여러 개의 작은 파티션으로 나뉘어 저장된다. 

+ 제약 사항
- 파티셔닝된 테이블은 스토리지 엔진이 같아야 한다. 
-외부키 제약은 사용할 수 없다.
-FULLTEXT 인덱스를 사용할 수 없다. 
-GIS 컬럼을 사용할 수 없다.
-임시 테이블, MERGE 스토리지 엔진, CSV 스토리지 엔진은 파티셔닝할 수 없다.


2) 방식

+ RANGE 
파티션마다 칼럼의 값이 취할 범위를 지정하는 방식이다.

 


 + LIST

파티션 마다 칼럼의 값을 직접 지정하는 방식이다.

 

+ HASH
HASH 값을 갖고 파티션을 할당하고, 할당이 사용자에 의해 정해지는 것이 아니라 MySQL에 맡기는 방식이다.

 

파티션 방식의 수식을 좀 더 이해하기 쉽게 설명하면, 내부적으로‘2012-05-15’라는 입력 값이 들어간다면 birthday가 아래와 같이 처리되어 파티션 1에 저장된다.


+ KEY

HASH와 비슷하나 분할 결정 값은 Primary Key(혹은 Unique Key)에 대해 서버 측의 결정 알고리즘(MySQL 클러스터:MD5, 다른 스토리지 엔진:PASSWORD())에 의해 결정되는 방식이다.
- PRIMARY/UNIQUE 키가 없으면 사용 불가
- UNIQUE 키가 NOT NULL이 아니면 불가(NULL 값은 MD5, PASSWORD 함수 적용 불가)

 

+ 서브 파티셔닝 RANGE와 LIST일 때 각 파티션을 더 분할할 수가 있다. 이것을 서브파티션이라고 한다.



  


3) 장단점

+ 장점
- 대량의 데이터 저장
- 테이블을 분할할 수 있기 때문에 많은 양의 데이터를 저장할 수 있다.

- 부하 경감
- 테이블의 데이터를 분할할 수 있어서 쿼리에 검색되는 데이터가 줄어든다.
- 해당 디스크에 남아있는 분할 데이터를 받을 확률이 높기 때문에 캐시 히트율도 높아진다.
- 집계 함수(SUM/COUNT)가 병렬 처리가 가능해 속도 향상을 가져 온다.

+ 단점
- 분할 방법의 정의, 관리, 그것을 취급하는 애플리케이션 측의 구현, 조사 비용 등이 증가한다.


4) 고려사항

+ 장점
 -테이블 설계 시 PK는 파티셔닝키와 연관돼야 하고 PK를 제외한 추가 제약 조건은 불가능하만, 테이블 특성에 맞는 것들만 파티셔닝 테이블로 설계해야 한다. 그리고 파티셔닝 키가 모든 조회 조건에 들어가야 한다는 점도 유의해야 한다. ? 
그 외는 1) 개요의 제약 사항을 참고하기 바란다.



2. MySQL Cluster

1) 개요

MySQL Cluster는 공유 디스크를 사용하지 않는 Active-Active 형태의 데이터베이스 클러스터에서 트랜잭션을 지원하고 MySQL의 SQL 문장을 사용할 수 있는 관계형 데이터베이스이다. 단일 장애 지점을 없애기 위하여 99.999%의 가용성(연간 5분 정도의 정지시간)을 달성하기 위한 설계가 반영돼 있다. 데이터를 여러 서버에 분산함으로써 동시 다발적으로 대량으로 발생하는 데이터 업데이트를 신속하게 처리하고, 확장성을 높인 접근이다. 온라인 백업뿐만 아니라 클러스터에 서버 추가와 업데이트도 온라인 상태에서 할 수 있기 때문에 클러스터를 중단 없이 운용할 수 있다. MySQL Cluster의 시발점은 통신장비 업체 에릭슨 휴대 통신망의 가입자 데이터베이스용으로 개발된 Ericsson Network DataBase(NDB)라고 한다. 2003년부터 MySQL 서버의 기능과 통합해 제품화하고, 현재 MySQL 서버와 별도의 제품으로 개발?판매가 진행되고 있다. 

+아키텍처
MySQL Cluster에서 공유 디스크를 사용하지 않는 대신 여러 서버에 데이터를 분산 배치하고, 항상 여러 서버가 동일한 데이터 사본을 갖고 있게 한 접근이다. 각 노드의 특성은 다음 <표 1>과 같다.



 

각 테이블의 기본 키 또는 고유 키의 해시를 계산해 파티션을 내부적으로 만들고, 해시 값과 데이터 노드의 수에 따라 각 파티션의 데이터를 데이터 노드에 분산한다. 분산된 데이터는 같은 그룹 데이터 노드의 복제본에 중복 저장된다. 파티션의 데이터가 변경되면 동일한 파티션을 가진 데이터 노드에 동기적으로 변경 사항이 반영되기 때문에 각 데이터 노드의 데이터는 항상 동일하게 유지된다.



 
그림 2 Cluster

클러스터에 있는 데이터 노드? 비트 패킷을 보내 모니터링하고 데이터 노드가 중지된 경우 클러스터에서 자동으로 분리된다. 또한 재해 복구 데이터 노드는 같은 그룹의 데이터 노드에서 최신 변경사항까지 차이를 취득해 데이터를 자동으로 반영하고 데이터를 동기화할 때 클러스터에 다시 참여한다.


+ 특징
- 자동 파티셔닝
- 분산 프라그먼트
- 동기화 방식
- 자동으로 이뤄지는 고속 데이터 노드

+ 페일오버
- 자동 재동기화
-트랜잭션 지원

+ 제약 사항
- 인메모리 인덱스
- 크로스 테이블 조인, 레인지 스캔 등을 지원 안 함
- 포린키 지원 안 함
- 롱 트랜젝션 지원 안 함
- 네트워크 지연 시 치명적
- 모든 클러스터의 기종은 동일해야 함. 기종에 따른 비트 저장방식이 다른 경우에 문제 발생



2) 방식

+ Shared-Nothing



 
그림 3 Shared-Nothing

각 데이터 노드별 독립적인 스토리지를 연결하고 스토리지 공유를 하지 않기 때문에 오라클 RAC와는 달라 스토리지 장애 발생 가능성을 차단하는 구조이다.


+Data Partitioning(데이터 분산 구조)

 
그림 4 Data Partitioning

테이블 Row 단위의 여러 노드에 분산해 쓰기 성능 확장이 가능하고 하나의 노드 그룹 시스템에 동일한 복제본으로 이중화 구성을 했다.


3) 장단점


+ 장점
  -고성능?고가용성
  - 어플리케이션에서 샤딩 기능이 필요 없어 개발 및 유지보수 비용 절감
  - Join 실행 가능
  - 샤드(Shard) 간 ACID 보장
  - 네트워크 홉이 크게 줄어 높은 처리 성능과 짧은 대기 시간 제공
  - 즉각적인 페일오버 및 복구 지원

+ 단점
  - MySQL Cluster 7.2에서 많은 개선이 있었지만 테이블 조인 성능이 떨어짐
  - 클러스터 격리 수준이 READ-COMMITTED



4) 고려사항

+ Distribution Awareness 고려한 설계 MySQL Cluster 내에서 데이터의 분산은 이른바 Sharding과 같은 원리로 이뤄지고 있다. 그래서 Distribution Awareness가 제대로 고려되지 않은, 데이터 노드 수가 증가하면 네트워크 병목현상이 일어나 성능 저하를 유발한다. 이를 위해서는 Sharding 방식으로 테이블을 정의하고 응용 프로그램에서 사용하는 쿼리도 조정한다면 효율을 높일 수 있다. 예를 들어 아래와 같이 Distribution Awareness를 이용하기 위해서는 테이블 정의와 쿼리를 조정해야 한다.


 

그리고 Distribution Awareness가 효과가 있는지 확인하려면 'EXPLAIN PARTITIONS SELECT ...'를 이용해 파티션이 하나만 선택됐는지 확인한다.

+ 키 설계도 중요하다.
MySQL Cluster는 Hash 인덱스 및 Ordered 인덱스라는 두 유형의 인덱스가 있다. Ordered 인덱스는 한 테이블에서 여러 개를 만들 수 있지만, 해시 인덱스는 테이블마다 하나만 만들 수 있다. 즉, 해시 인덱스로 구성되는 기본 키뿐이다. 한편, UNIQUE KEY 제약 조건을 이용하는 경우에는 해시 인덱스가 될 수 있다. 하지만 MySQL Cluster에서는 UNIQUE 키를 정의하면 내부적으로 지원 테이블이라는 다른 테이블이 생성된다. 지원 테이블만큼 테이블에 대한 참조가 증가하므로 UNIQUE KEY 제약 조건을 이용하는 키는 성능이 떨어짐을 알아야 한다.



3. MySQL Replication

1) 개요

MySQL은 마스터/슬레이브 복제 기능을 제공한다. MySQL에서 지원하는 것은 비동기식 복제로 마스터 서버에서 실행되는 SQL 문을 슬레이브 서버로 전송하고 슬레이브로 다시 실행해 데이터를 일관되게 유지하는 기법을 사용하고 있다. 이것은 스테이트먼트 기반 리플리케이션(SBR, Statement Based Replication)이라고 한다. SBR는 그 구조에 근본적인 문제가 있다. 예를 들어 UUID() 함수를 사용할 경우 마스터와 슬레이브에서 각각 UUID()를 실행하면, 각기 다른 결과로 나타난다. 그 결과, 마스터와 슬레이브는 데이터가 불일치하게 된다. 이 문제를 해결하려면 슬레이브로 전달되는 정보를 스테이트먼트 대신 실제로 테이블에 기록된 내용 또는 행 자체를 복제한다. 그것을 구현하는 것이 로우 기반 리플리케이션(RBR, Row Based Replication)이다. 

MySQL 5.1에서 UUID()을 복제하지 못할 스테이트먼트를 포함할 때만 자동으로 SBR에서 RBR로 전환 MIXED 모드가 기본값(binlog_format)으로 돼 있다. 

+아키텍처

 
그림 5 Replication

MySQL Replication은 READ 관련 스케일아웃만 가능할 뿐, WRITE 관련 스케일아웃은 불가하다. 만약 Replication 운영 시 마스터 트래픽이 과도하게 발생하면, 마스터와 슬레이브 간 데이터 동기화 지연 현상이 발생하기도 한다는 점을 염두에 둬야 한다. 동작의 흐름을 살펴본다면 아래와 같다.

① Slave I/O 스레드가 Master에 접속
② Master가 Slave를 인증하고 Slave와의 세션 개시
③ Slave I/O 스레드가 바이너리 로그파일(파일명, 위치)을 요구
④ Master(binlog dump 스레드)가 요구된 지점으로부터 이벤트를 바이너리 로그에서 읽어 들여 Slave에 전송
⑤ Slave I/O 스레드는 받아 낸 이벤트를 relay-log에 기록
⑥ Slave SQL 스레드가 relay-log 내용을 읽어 들여 SQL문을 실행
⑦ Master에 새로운 이벤트가 있으면 Master가 Slave에 송신



2) 방식

+ Linux Heartbeat + MySQL Replication

 
그림 6 Heartbeat + MySQL replication

Heartbeat 프로토콜을 사용해 노드 간 생존 확인 메시지를 송수신해 장애 여부를 인지한다. 두 서버 사이에는 가상(Virtual) IP가 위치해 장애 시 가상 IP는 살아있는 서버로 송수신해 장애를 피한다.


+ L/B + MySQL Replication

 
그림 7 L/B + MySQL Replication

여러 대의 슬레이브를 앞에 두고 부하 분산을 목표로 하며??서 마스터에서 장애가 발생하면 L/B는 자동 IP를 슬레이브로 향하게 해 서비스가 중단 없이 가능하게 된다.


+Linux Heartbeat + DRBD + MySQL Replication

 
그림 8 Heartbeat + DRBD + MySQL Replication

디스크 미러링 소프트웨어(DRBD; 단방향 복제, 동기 복제, 오픈소스)를 활용해 HA를 구성하는 방식이다. 이는 동기 방식으로 데이터 복제가 이뤄져 데이터 불일치가 없는 장점을 갖고 있다.

3) 장단점

+ 장점
  - MySQL 서버의 부하 분산 가능
  - 실시간 백업 가능


+ 단점
  - 장애가 발생시 슬레이브에 반영이 안 될 가능성 존재
  - Master와 Slave 간 데이터 동기화 지연 발생 가능성 존재
  - 장애 복구가 수동으로 이뤄짐



4) 고려 사항

+ MySQL 5.6.3에서는 slave multithread를 지원하지만, 이전 버전의 경우 Slave는 하나의 스레드로만 SQL을 실행하기 때문에 서버 간 동기화 지연 현상이 발생한다. 따라서 replicate_do_db 혹은 replicate_do_table 옵션을 사용해 실제로 적용할 객체들만 선별적으로 동기화하는 것이다. 이는 서비스 단위로 기능을 나눌 수도 있고, 역할별로 기능을 나눌 수 있게 할 수 있다. 그 외에 롱 트렌젝션을 제거하고, Slave 개수를 잘 조절하고, 작업 집합이 InnoDB 버퍼 풀보다 훨씬 큰 경우는 Slave pre-fetching 기술도 활용해 볼 만하다.

+ MySQL Replication에서 5.5버전부터 Semi-syncronous 기능이 추가됐다. 부하량이 적은 환경에서 마스터와 슬레이브 간의 데이터 정합성을 강화시키는 방법이므로 필요 시 활용하면 좋다.



4. MySQL Sharding

샤딩은 물리적으로 다른 데이터베이스에 데이터를 수평 분할 방식으로 분산 저장하고 조회하는 방법을 말한다. 여러 데이터베이스를 대상으로 작업해야 하기 때문에 경우에 따라서는 기능에 제약이 있을 수 있고(JOIN 연산 등) 일관성(consistency)과 복제(replication) 등에서 불리한 점이 많다. 과거의 샤딩은 애플리케이션 서버 레벨에서 구현하는 경우가 많았는데 최근에는 이를 플랫폼 차원에서 제공하려는 시도가 많다. 그중 미들티어에 샤딩 플랫폼을 둔 Gizzard, Spider, Spock Proxy에 대해 알아본다.


1) Gizzard


+ 개요
트위터(Twitter)에서 자체 개발한 데이터를 분산 환경에 쉽게 구성?관리하고, 장애 발생에 유연하게 대처할 수 있는 오픈소스이면서 미들웨어다. Gizzard 의 핵심이 되는 것은 샤딩(Sharding)이라는 기술이다. 트위터에 따르면, 샤딩은 파티션과 복제라는 두 기술로 구성됐다. 파티션은 하나의 데이터베이스를 해시 등을 사용해 여러 부분으로 나눔으로써 여러 데이터베이스에 분산하는 것을 말하고, 복제는 한 데이터 복제를 다른 서버에 만드는 것이다. 데이터를 복제해 전체 탄력성이 증가함과 동시에 대량의 액세스에 대해 복수의 서버가 동시에 응답할 수 있기 때문에 성능 향상을 기대할 수 있다. 

+ 아키텍처



 
그림 9 Replication

Gizzard는 데이터 저장소와 클라이언트 애플리케이션 사이에 위치해 모든 질의는 Gizzard를 통해 이뤄지고 있어 Gizzard가 분할?복제 키를 쥐고 있다.

 
그림 10 Gizzard Partition

① Gizzard 는 데이터 매핑 선행 테이블에서 데이터 조각을 관리한다. 키에 해당하는 값이 어디에 저장됐는지 샤드 정보를 갖고 있다. 특정 데이터에 관한 키 값을 해시 함수로 돌리고 결과값을 Gizzard에 전달하면, Gizzard는 해당 데이터가 어떤 구역에 속할지 숫자 정보를 생성한다. 이러한 함수는 프로그래밍 가능하기 때문에 사용자 성향에 맞춰 지역성 혹은 균형성 면에서 최적화할 수 있다.



 
그림 11 Gizzard Replication

② Gizzard는 'Replication Tree'로 복제 데이터를 관리한다. 매핑 테이블이 지칭하는 각 데이터 조각들은 물리적 또는 논리적인 형태로 구현될 수 있다. 즉, 물리적인 형태는 특정 데이터 저장소를 의미하고, 논리적인 형태는 데이터 조각에 관한 트리를 의미한다. 트리 안의 각 브랜치들은 데이터의 논리적인 변형을 나타내고, 각 노드들은 데이터 저장소를 의미한다.

③ Gizzard는 장애 상황에도 강한 시스템이다. 특정 파티션 내에 있는 복제 데이터가 유실됐을 지라도, Gizzard는 남은 다른 정상적인 복제 데이터 시스템에 읽기/쓰기를 전환 요청한다. 대응 시나리오는 데이터 조각 안의 특정 복제 데이터 불능이 발생하면 Gizzard는 최대한 빠르게 다른 정상적인 복제 데이터에 읽기/쓰기 작업을 시도해서 정상적인 응답을 주고, Gizzard 내부에서도 문제가 발생했던 복제 데이터가 정상으로 돌아오면, 변경 이력을 다시 적용해 동기화한다.



 
그림 12 Gizzard Migrations

④ Gizzard는 데이터 이전 방안을 제시할 수 있다. 데이터 노드가 A에서 B로 이전한다고 가정할 때 Replicate 샤드가 A와 B 사이에 위치하고 Writeonly는 B 앞에 위치해서 A의 데이터를 B로 복제하게 된다. Writeonly 샤드는 B가 사용 가능한 시점까지 유지되다가 B가 사용 가능한 시점이 되면 WriteOnly는 빠지고 Replicate가 동시에 A와 B에 데이터를 복제하며 동기화해 준다.

 + 고려사항 Gizzard는 동일 데이터를 동시에 변경하려면 데이터 충돌(Confliction)이 발생한다. 그래서 Gizzard는 데이터가 적용 순서를 보장하지 않기 때문에 모델링 시 반드시 이러한 점을 염두에 둬야 한다.



2) SPIDER for MySQL

+ 개요
SPIDER는 Kentoku SHIBA가 개발한 스토리지 엔진이다. MySQL의 파티셔닝 기능을 사용해 파티션마다 다른 서버로 데이터를 저장할 수 있게 한 구조이다. Sharding 기능이 가능한 스토리지 엔진이라고 할 수 있다. 물론 데이터가 저장된 서버가 다른 경우에도 조인할 수 있는 장점이 있다. 이러한 구조를 구현할 수 있었던 배경은 MySQL의 유연한 스토리지 엔진구조가 있었기 때문이다. 

+ 아키텍처

 
그림 13 Spider for MySQL

Spider 는 위 그림처럼 테이블 링크를 통해 로컬의 MySQL 서버처럼 활용할 수 있는 구조이다. 내부적으로는 클러스터의 약점인 애플리케이션에서 구현하던 분산 환경에서의 조인과 트렌잭션 문제를 Spider가 해결해 준다. 더불어 Spider는 XA 트렌잭션과 테이블 파티셔닝을 지원한다. GPL 라이선스 정책을 갖고 있다. 여기에 병BR>+ 고려사항
① SPIDER 테이블을 드롭해도 데이터 노드의 데이터는 사라지지 않는다. SPIDER은 FEDERATED 스토리지 엔진과 마찬가지를 생성하는 스토리지 엔진으로 테이블 DROP 때는 링크만 제거된다. 더불어 TRUNCATE 명령문은 원격 MySQL Server의 데이터를 모두 클리어해 버리므로 주의하자.
② MySQL Cluster처럼 Spider도 파티션을 전제로 한 스토리지 엔진이기 때문에 Distribution Awareness를 고려한 설계를 해줘야 한다.
③ 전체 텍스트 검색과 R-Tree 인덱스를 지원하지 못한다.



3) Spock Proxy

+ 개요
Spock Proxy는 MySQL Proxy를 바탕으로 제작된 샤딩 플랫폼이다. 데이터베이스들을 수평적 분할해 여러 샤드(shard)로 나눠줘 응용 프로그램들이 직면한 중요한 문제들을 해결해 줬고, 이로써 데이터베이스는 성능과 확장성을 쉽게 높일 수 있다. 주요 역할은 Proxy가 클라이언트로부터 전송되는 쿼리를 가로채 데이터가 분할된 기법에 따라 적절한 MySQL 서버에 쿼리를 전송한다. 이후 각 MySQL 서버로부터의 응답을 모아 일반 MySQL 결과인 것처럼 클라이언트로 다시 전송한다.

+ 아키텍처


 
그림 14 Spock Proxy

Spock proxy는 MySQL DB의 Table(universal_db)에 샤딩 규칙을 저장한다. 그래서 애플리케이션 서버로부터 전달받은 SQL을 파싱해, 이 SQL에 shard key가 있는지 파악한다. 만약 shard key가 있다면 universal_db에 기록된 기준에 따라 MySQL 인스턴스를 찾아 SQL을 전달한다. 이런 방식을 사용할 경우 SQL에 shard key와 관련한 정보를 기술할 필요가 없는 장점으로 인해 기존 애플리케이션을 그대로 이용할 수 있다. 

+ MySQL Proxy와 다른 점
Spock Proxy는 MySQL Proxy에서 갈라진 것이다. 코드의 많은 부분이 변경되지 않았지만 기반 아키텍처가 다르다.

① MySQL Proxy는 최적화를 위해 Lua 스크립트 언어를 지원하지만, Spock Proxy는 그렇지 않다. 성능상의 이유로 C/C++를 활용했다.
② MySQL Proxy는 각 클라이언트가 개별 데이터베이스에 직접 인증하는 것을 허용한다. 이는 클라이언트가 proxy 작업에 앞서 최소 연결을 해야 하기 때문에 문제가 있는 방법이다. Spock Proxy는 클라이언트로부터 독립된 서버 연결을 하도록 설계됐다.
③ Spock Proxy는 클라이언트와 서버 연결을 분리해, 관리할 최소/최대 연결 크기를 허용한다. 시작 시에 proxy는 최소 연결 개 수만큼을 미리 연결해 놓는다.
④ 다중 서버 Send/Recv가 가능하다. 그래서 다수의 서버에 요청을 보내고, 이 결과들을 합치는 것 또한 가능하다. 

+ 고려사항
Spock Proxy의 제약사항을 보면 쉽게 구현 단계에서 고려 사항들이 도출될 것이다 
① 데이터베이스들간의 조인이 불가능하다.
② 저장 프로시저와 트랜잭션 지원은 제한적이다.
③ 중첩 질의는 불가능하다.
④ SELECT에서 2개 테이블의 JOIN은 지원하지만, GROUP BY는 지원하지 않는다.
⑤ INSERT에서 칼럼명은 항상 표현돼야 한다.
⑥ MySQL 함수는 데이터들이 여러 파티션에 걸쳐 있는 경우 MAX, MIN, SUM, AVERAGE만 지원한다.
⑦ Spock Proxy는 read only (slave)와 read/write (master)를 구분하지 못한다.
⑧ auto_increment 값이 다르다.



5. 결론

MySQL은 다양한 방식의 스케일아웃을 지원하는 솔루션들이 있고, 지속적으로 개발?보완되고 있다. 중요한 건 우리가 용도에 적합한 솔루션들을 선택할 수 있는 혜안을 갖고 판단할 필요가 있다는 것이다. 비즈니스에 어느 정도 제약성도 있다는 점을 기획자들이 인지할 수 있게 지속적으로 커뮤니케이션 하는 활동도 필요하다. 거기에 DBA나 프로그래머들은 제약 사항 및 고려 사항들을 숙지하고 있어야 하며, 운용하면서 발생될 수 있는 문제점을 애플리케이션이든, 자체 미들웨어를 갖추든 보완하는 자세도 필요해 보인다. 트위터나 페이스북 등에서 행해지는 MySQL을 보완해 주는 미들웨어들을 유심히 살펴본다면 힌트를 얻을 수 있을 것이다.





출처 : 한국데이터베이스진흥원

제공 : DB포탈사이트 DBguide.net


'DB > MySQL' 카테고리의 다른 글

MySQL Cluster  (0) 2016.08.04
MySQL root 패스워드 분실  (0) 2015.05.21
MSSQL 문자열 구분자 분리 (테이블 반환 함수)  (0) 2012.12.11
MSSQL PROC ARRAY  (0) 2012.12.11
MySQL 프로세스 리스트 보기  (0) 2011.04.22
:

In Memory Data Grid

Network/Network 2016. 8. 4. 16:31

IMDG의 특징

저장소로 디스크 대신 메인 메모리를 사용하는 것은 전혀 새로운 시도가 아니다. 디스크보더 더 빨리 수행 결과를 얻기 위해 MMDB(Main Memory DBMS)를 사용하는 사례는 일상에서도 찾을 수 있다. 대표적인 예는 휴대 전화를 사용할 때이다. SMS나 통화를 시도할 때 상대방 정보를 빠른 시간 안에 찾기 위해 대부분의 통신사는 MMDB를 사용하고 있다.

IMDG(In Memory Data Grid)는 메인 메모리에 데이터를 저장한다는 점에서 MMDB와 같지만 아키텍처가 매우 다르다. IMDG의 특징을 간단히 정리하면 다음과 같다.

  • 데이터가 여러 서버에 분산돼서 저장된다.
  • 각 서버는 active 모드로 동작한다.
  • 데이터 모델은 보통 객체 지향형(serialize)이고 non-relational이다.
  • 필요에 따라 서버를 추가하거나 줄일 수 있는 경우가 많다.

즉, IMDG는 데이터를 MM(Main Memory)에 저장하고 확장성(Scalability)을 보장하며, 객체 자체를 저장할 수 있도록 구현됐다. 오픈 소스와 상용 제품을 구별하지 않으면 다음과 같은 IMDG 제품이 있다.

이 글에서는 제품의 기능과 성능을 비교하지는 않고, IMDG의 아키텍처를 살펴보고 어떻게 활용할 수 있을지 검토해 볼 것이다.

왜 메모리?

2012년 6월 현재 SATA(Serial ATA) 인터페이스를 사용하는 SSD(Solid State Drive)의 성능은 약 500MB/s 정도이고, 고가의 PCI Express를 사용하는 SSD는 약 3,000MB/s에 이른다. 10,000 RPM SATA HDD의 성능이 약 150MB/s 정도니까 SSD가 HDD보다 4~20배 정도 빠르다고 할 수 있다. 하지만 이에 반해 DDR3-2500의 성능은 20,000MB/s에 이른다. 메인 메모리의 처리 성능은 HDD보다 800배, 일반적인 SSD보다 40배, 가장 빠른 SSD보다 약 7배 빠르다. 게다가 요즘의 x86 서버는 서버 하나당 수백 GB 용량의 메인 메모리를 지원한다.

Michael Stonebraker에 따르면 전형적인 OLTP(online transaction processing) 데이터 용량은 약 1TB 정도이고, OLTP 처리 데이터 용량은 잘 증가하지 않는다고 한다. 만약 1TB 이상의 메인 메모리를 사용하는 서버 사용이 보편화된다면, 적어도 OLTP 분야에서는 모든 데이터를 메인 메모리에 둔 채 연산을 하는 것이 가능해진다.

컴퓨팅 역사에서 '좀 더 빠르게'는 언제나 최고의 덕목으로 추구해야 할 가치였다. 이렇게 메인 메모리 용량이 증가하게 된 만큼 영구 저장소 대신 메인 메모리를 저장소로 사용하는 플랫폼이 등장할 수 밖에 없게 된 것이다.

IMDG 아키텍처

메인 메모리를 저장소로 사용하려면 극복해야 하는 약점 두 가지가 있다. 용량의 한계와 신뢰성이다. 서버의 메인 메모리의 최대 용량을 넘어서는 데이터를 처리할 수 있어야 하고, 장애 발생 시 데이터 손실이 없도록 해야 한다.

IMDG는 용량의 한계를 분산 아키텍처를 이용하여 극복한다. 여러 기기에 데이터를 나누어 저장하는 방식으로 전체 용량 증가를 꾀하는 Horizontal Scalability 방식을 사용한다. 또한 신뢰성은 복제 시스템을 구성해 해결한다.

제품마다 세세한 차이가 있지만 IMDG 아키텍처를 일반화하면 그림 1과 같이 나타낼 수 있다.

imdg1

그림 1 IMDG 아키텍처

애플리케이션 서버(AS)에는 IMDG가 제공하는 클라이언트 라이브러리가 있고, 이 라이브러리를 이용해 IMDG에 접근한다.

많은 IMDG 제품이 데이터를 RDBMS 등에 동기화하는 기능을 제공한다. 그러나 이러한 별도의 영구 저장 시스템(RDBMS 등)을 반드시 구성해야 하는 것은 아니다. 일반적으로 IMDG에서는 직렬화를 통해 객체를 저장할 수 있도록 한다. Serializable 인터페이스를 구현한 객체를 저장할 수 있도록 한 제품도 있고, 독자적인 직렬화 방법을 제공하는 IMDG도 있다. 당연히 schemaless 구조라 사용 편의성이 매우 높다.

개념상 객체를 저장하고 조회할 수 있도록 한 In Memory Key-Value Database로 이해할 수 있다. IMDG에서 사용하는 데이터 모델은 Key-Value 모델이다. 그래서 이 키(key)를 이용해 데이터를 분산시켜 저장할 수 있다. NHN에서 사용하는 분산 메모리 캐싱 시스템인 Arcus와 같이 Consistency Hash 모델을 사용하는 것부터Hazelcast와 같이 단순한 modulo 방식을 사용하는 것까지 다양한 방식이 있다. 이렇게 저장할 때 반드시 하나 이상의 다른 노드를 복제 시스템으로 삼아서 장애 발생에 대처할 수 있도록 한다.

인터페이스는 제품별로 다양하다. 어떤 제품은 SQL-like한 형태의 문법을 제공하여 JDBC를 통해 접근하는 제품도 있고, Java의 Collection을 구현한 API를 제공하는 경우도 있다. 즉 여러 노드를 대상으로 하는 HashMap이나 HashSet을 사용할 수 있는 것이다.

IMDG는 Arcus와 같은 캐시 시스템과는 사용과 목적이 다르다. 그림 2는 Arcus의 아키텍처를 간단하게 표현한 것이다.

imdg2

그림 2 캐시 시스템인 Arcus의 아키텍처

Arcus와 같은 캐시 시스템도 메인 메모리를 저장소로 사용하고 Horizontal Scalability를 확보했다는 점에서는 IMDG와 같다. 하지만 그림 1과 그림 2에서와 같이 사용 형태와 목적은 크게 다르다. 캐시 시스템에서 영구 저장소의 사용은 필수이지만, IMDG에서 영구 저장소의 사용은 선택이다.

표 1 캐시 시스템과 IMDG의 읽기, 쓰기 성능 비교

 캐시 시스템IMDG
읽기캐시 안에 데이터가 있다면 데이터 베이스에서 읽어오지 않는다. 
캐시 안에 데이터가 없을 때는 데이터베이스에서 읽어온다.
언제나 IMDG에서만 읽어온다. 항상 메인 메모리에서 읽어오기 때문에 빠르다.
쓰기영구 저장소에 쓰기 때문에 캐시 시스템 적용과 쓰기 성능 향상은 관계 없다.영구 저장소에 데이터를 동기화하도록 구성하더라도, 제품에 따라 비동기 쓰기를 지원하는 제품이 있다. 비동기 쓰기를 지원하는 경우에는 매우 높은 수준의 쓰기 성능을 기대할 수 있다.

이외에도 데이터를 마이그레이션할 수 있는지, 신뢰성을 보장하는지, 복제 기능을 제공하는지 등의 차이가 캐시 시스템과 IMDG의 차이다.

IMDG의 기능

다음은 IMDG 제품 가운데 하나인 Hazelcast의 기능이다. HazelCast는 더블 라이선스 정책을 취하고 있는 제품으로, ElasticMemory와 같은 기능을 사용하려면 상용 라이선스를 구입해야 한다. 그러나 많은 기능이 오픈소스라서 별도의 비용 없이 사용할 수 있으며, 사용 레퍼런스 정보를 찾아 보기가 매우 쉽다.

HazelCast의 기능이 다른 모든 IMDG에서 제공하는 일반적인 기능이라고 할 수는 없지만 IMDG의 기능을 살펴보기에는 매우 좋은 예라서 간단하게 소개하겠다.

DistributedMap & DistributedMultiMap

Map<?, ?>을 구현한 클래스다. 여러 IMDG 노드에 Map 데이터가 분산 배치된다.

RDBMS의 테이블(table)은 Map<Object key, List<Object>>로 표현할 수 있기 때문에, RDBMS를 샤딩해서 쓰는 것과 비슷한 데이터 분산 효과를 얻을 수 있다. 더구나 HazelCast는 DistributedMap에서 SQL-like한 기능을 사용할 수 있도록 했다. Map에 있는 value를 검사할 때 WHERE 구문이나 LIKE, IN, BETWEEN 같은 SQL-like 구문을 사용할 수 있다.

HazelCast는 모든 데이터를 메모리에 두는 것뿐만 아니라 영구 저장소에 저장하는 기능도 제공한다. 이렇게 영구 저장소에 데이터를 저장하면 캐시 시스템으로 사용하도록 구성할 수 있다. LRU(Least Recently Used) 알고리즘이나 LFU(Least Frequently Used) 알고리즘을 선택해, 꼭 필요한 데이터만 메모리에 두고 상대적으로 잘 찾지 않는 나머지 데이터는 영구 저장소에 두게 할 수도 있다.

또한 MultiMap을 분산 환경에서 사용할 수 있도록 했다. 어떤 key를 조회하면 Collection <Object> 형태의 value 목록을 얻을 수 있다.

Distributed Collections

DistributedSet이나 DistributedList, DistributedQueue 등을 사용할 수 있다. 이런 Distributed Collection 객체에 있는 데이터는 어느 하나의 IMDG 노드가 아니라 여러 노드가 분산 저장된다. 그렇기 때문에 여러 노드에 저장된 단 하나의 List 객체 또는 Set 객체 유지가 가능하다.

DistributedTopic & DistributedEvent

HazelCast는 publish 순서를 보장하는 Topic 읽기가 가능하다. 즉 분산 Message Queue 시스템으로 이용할 수 있다는 뜻이다.

DistributedLock

말 그대로 분산 Lock이다. 여러 분산 시스템에서 하나의 Lock을 사용해 동기화할 수 있다.

Transactions

DistributedMap, DistributedQueue 등에 대한 트랜잭션을 사용할 수 있다. 커밋/롤백을 할 수 있기 때문에 더 신중한 연산이 필요한 곳에서도 IMDG를 사용할 수 있다.

대용량 메모리 사용과 GC

앞에서 소개한 대부분의 제품은 구현 언어로 Java를 사용한다. 수십 GB 크기의 힙을 사용해야 하는 만큼 Full GC에 필요한 시간도 상당히 오래 걸릴 수 있다. 그렇기 때문에 IMDG에서는 이런 제약을 극복할 수 있는 방법을 마련해 적용하고 있다. 바로 Off-heap 메모리(Direct Buffer)를 사용하는 것이다.

JVM에 Direct Buffer 생성을 요청하면 JVM은 힙 바깥의 공간에서 메모리를 할당해 사용한다. 이렇게 할당한 공간에 객체를 저장하도록 하는 것이다. Direct Buffer는 GC 대상 공간이 아니기 때문에, Full GC 문제가 발생하지 않게 된다. 보통 Direct Buffer에 대한 접근은 Heap Buffer보다 느리다. 하지만 큰 공간을 할당할 수 있고 Full GC에 대한 부담을 줄일 수 있기 때문에 매우 큰 용량의 메모리 공간을 사용할 때 Full GC 시간을 없앨 수 있어 항상 일정한 처리 시간을 확보할 수 있다는 것이 장점이다.

imdg3

그림 3 Heap Buffer와 Direct Buffer 디스크 비교(이미지 출처: Terracotta)

그러나 Direct Buffer를 이용해 객체를 저장하고 조회하는 데는, Memory Allocator를 만드는 것과 같은 매우 전문적인 기술이 필요하다. 그렇기 때문에 이러한 Off-heap 메모리를 사용해 객체를 저장하는 기능은 상용 IMDG에서만 제공하고 있다.

마치며

현재까지 IMDG를 주로 사용하는 곳은 캐시 시스템이다. 그러나 IMDG는 주저장소로 발전될 가능성이 매우 높은 플랫폼이다. 많은 경우 분산 Map은 충분히 RDBMS의 테이블을 대신할 수 있다. 제품에 따라 분산 Lock을 제공하는 제품이 있는데, 이런 분산 Lock을 바탕으로 정합성(Integrity Constraint) 기능을 제공할 수 있다면, 본격적으로 RDBMS를 대체할 수 있다.

이렇게 할 경우 백엔드 시스템으로 RDBMS를 사용해 통계 처리에 대응할 수 있을 것이다. 인터넷 서비스에서 RDBMS 사용이 보조 목적으로 바뀌는 것이다. 정합성 기능이 제공된다면 빠른 속도를 바탕으로 한 쾌적한 사용자 경험은 물론, 빠른 처리 속도를 바탕으로 그동안 제공하기 어려웠던 기능을 제공할 수 있는 기회가 생기는 것이다.

imdg4

그림 4 정합성을 지원하는 IMDG


출처 - http://d2.naver.com/helloworld/106824

:

Do You Really Get Classloaders?

Language/JAVA 2016. 8. 4. 16:00

Part I: An Overview of Java Classloaders, Delegation and Common Problems

In this part, we provide an overview of classloaders, explain how delegation works and examine how to solve common problems that Java developers encounter with classloaders on a regular basis.

Introduction: Why you should know, and fear, Classloaders

Classloaders are at the core of the Java language. Java EE containers, OSGi, various web frameworks and other tools use classloaders heavily. Yet, something goes wrong with classloading, would you know how to solve it?

Join us for a tour of the Java classloading mechanism, both from the JVM and developer point-of-view. We will look at typical problems related to classloading and how to solve them. NoClassDefFoundError, LinkageError and many others are symptoms of specific things going wrong that you can usually find and fix. For each problem, we’ll go through an example with a corresponding solution. We’ll also take a look at how and why classloaders leak and how can that be remedied.

And for dessert, we review how to reload a Java class using a dynamic classloader. To get there we’ll see how objects, classes and classloaders are tied to each other and the process required to make changes. We begin with a bird’s eye view of the problem, explain the reloading process, and then proceed to a specific example to illustrate typical problems and solutions.

Enter java.lang.ClassLoader

Let’s dive into the beautiful world of classloader mechanics.

It’s important to realize that each classloader is itself an object–an instance of a class that extends java.lang.ClassLoader. Every class is loaded by one of those instances and developers are free to subclass java.lang.ClassLoader to extend the manner in which the JVM loads classes.

There might be a little confusion: if a classloader has a class and every class is loaded by a classloader, then what comes first? We need an understanding of the mechanics of a classloader (by proxy of examining its API contract) and the JVM classloader hierarchy.

First, here is the API, with some less relevant parts omitted:

package java.lang;

public abstract class ClassLoader {

  public Class loadClass(String name);
  protected Class defineClass(byte[] b);

  public URL getResource(String name);
  public Enumeration getResources(String name);
  
  public ClassLoader getParent()
}

By far, the most important method of java.lang.ClassLoader is the loadClass method, which takes the fully qualified name of the class to be loaded and returns an object of class Class.

The defineClass method is used to materialize a class for the JVM. The byte array parameter ofdefineClass is the actual class byte code loaded from disk or any other location.

What if you no longer had to redeploy your Java code to see changes? The choice is yours. In just a few clicks you can Say Goodbye to Java Redeploys forever.

getResource and getResources return URLs to actually existing resources when given a name or a path to an expected resource. They are an important part of the classloader contract and have to handle delegation the same way as loadClass – delegating to the parent first and then trying to find the resource locally. We can even view loadClass as being roughly equivalent todefineClass(getResource(name).getBytes()).

The getParent method returns the parent classloader. We’ll have a more detailed look at what that means in the next section.

The lazy nature of Java has an effect on how do classloaders work – everything should be done at the last possible moment. A class will be loaded only when it is referenced somehow – by calling a constructor, a static method or field.

Now let’s get our hands dirty with some real code. Consider the following example: class A instantiates class B.

	public class A {
	  public void doSomething() {
  	 B b = new B();
   	 b.doSomethingElse();
	  }
	}

The statement B b = new B() is semantically equivalent to B b = A.class.getClassLoader().loadClass(“B”).newInstance()

As we see, every object in Java is associated with its class (A.class) and every class is associated with classloader (A.class.getClassLoader()) that was used to load the class.

When we instantiate a ClassLoader, we can specify a parent classloader as a constructor argument. If the parent classloader isn’t specified explicitly, the virtual machine’s system classloader will be assigned as a default parent. And with this note, let’s examine the classloader hierarchy of a JVM more closely.


출처 - http://zeroturnaround.com/rebellabs/rebel-labs-tutorial-do-you-really-get-classloaders/

'Language > JAVA' 카테고리의 다른 글

자바 JVM 정리(Java Virtual Machine)  (0) 2016.08.05
GC  (0) 2016.08.05
JVM ClassLoader의 작동원리  (0) 2016.08.04
클래스로더 1, 동적인 클래스 로딩과 클래스로더  (3) 2016.08.04
java class loader #2  (0) 2016.08.04
:

JVM ClassLoader의 작동원리

Language/JAVA 2016. 8. 4. 15:48

개발을 하거나 WAS를 기동하면 ClassNotFoundException 을 많이 만나게 됩니다.


왜 클래스를 찾을 수 없는지 원리를 알아보기 위해 정리를 해보았습니다. 


ClassLoader의 작동원리를 살펴보기 전에 먼저 알아야 할 용어를 정리하면.


Class - Java 프로그램을 컴파일하여 생성된 .class 

Class 객체 - 일반객체를 만들기 위해 JVM 메모리에 로드된 .class에 대한 객체 

일반 객체 - Class 객체로 생성한 객체 


ClassLoader는 작성한 Java Byte Code를 JVM메모리상에 올려주는 역할을 하는 것이고, 프로그래머는 Class 객체를 이용하여 일반객체를 생성할 수 있습니다. 즉 메모리 내에서 Class 객체를 매개로 하여 일반객체를 생성합니다.


ClassLoader는 기본적으로 Unload 기능을 제공하지 않습니다.. GC에 의하여 자동 Unload되며, Static으로 선언된 경우에는 GC에서 Unload하지 않기 때문에 프로그래머는 항상 Static으로 선언된 클래스를 사용할 수 있습니다.





Java ClassLoader의 구조(Delegation Model, 위임구조)


Bootstrap ClassLoader - 모든 ClassLoader의 부모 ClassLoader 

Extensions ClassLoader - Bootstrap ClassLoader를 상속하며, lib/ext 디렉토리에 있는 클래스를 로드 

System-Classpath ClassLoader - Extensions ClassLoader를 상속하며, CLASSPATH에 잡혀 있는 모든 클래스, java.class.path 시스템 프라퍼티, -cp 혹은 -classpath에 잡혀 있는 모든 클래스를 로드 


ps.구조도는 첨부하였습니다.



클래스 로더의 delegation model에서 클래스를 찾을 경우 

Cache -> Parent -> Self 순서로 찾습니다. 


클래스 생성을 요청받으면 먼저 Cache를 검색하여 이전에 요청이 있었는지 파악하며, 없을경우 상위 클래스 로더에서 찾습니다. 

최상위 클래스로더인 Bootstrap ClassLoader 에도 없을 경우 작업한 소스의 경로에서 클래스를 찾아서 생성합니다.



출처 - http://open.egovframe.org/nforges/information/share/4082/.do


'Language > JAVA' 카테고리의 다른 글

GC  (0) 2016.08.05
Do You Really Get Classloaders?  (0) 2016.08.04
클래스로더 1, 동적인 클래스 로딩과 클래스로더  (3) 2016.08.04
java class loader #2  (0) 2016.08.04
java class loader #1  (1) 2016.08.04
:

클래스로더 1, 동적인 클래스 로딩과 클래스로더

Language/JAVA 2016. 8. 4. 15:35

동적인 클래스 로딩

자바는 동적으로 클래스를 읽어온다. 즉, 런타임에 모든 코드가 JVM에 링크된다. 모든 클래스는 그 클래스가 참조되는 순간에 동적으로 JVM에 링크되며, 메모리에 로딩된다. 자바의 런타임 라이브러리([JDK 설치 디렉토리]/jre/lib/rt.jar) 역시 예외가 아니다. 이러한 동적인 클래스 로딩은 자바의 클래스로더 시스템을 통해서 이루어지며, 자바가 기본적으로 제공하는 클래스로더는 java.lang.ClassLoader를 통해서 표현된다. JVM이 시작되면, 부트스트랩(bootstrap) 클래스로더를 생성하고, 그 다음에 가장 첫번째 클래스인 Object를 시스템에 읽어온다.

런타임에 동적으로 클래스를 로딩하다는 것은 JVM이 클래스에 대한 정보를 갖고 있지 않다는 것을 의미한다. 즉, JVM은 클래스의 메소드, 필드, 상속관계 등에 대한 정보를 알지 못한다. 따라서, 클래스로더는 클래스를 로딩할 때 필요한 정보를 구하고, 그 클래스가 올바른지를 검사할 수 있어야 한다. 만약 이것을 할 수 없다면, JVM은 .class 파일의 버전이 일치하지 않을 수 있으며, 또한 타입 검사를 하는 것이 불가능할 것이다. JVM은 내부적으로 클래스를 분석할 수 있는 기능을 갖고 있으며, JDK 1.1부터는 개발자들이 리플렉션(Reflection)을 통해서 이러한 클래스의 분석을 할 수 있도록 하고 있다.

로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading)

클래스를 로딩하는 방식에는 로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(run-time dynamic loading)이 있다. 먼저 로드타임 동적 로딩에 대해서 알아보기 위해 다음과 코드를 살펴보자.

  public class HelloWorld {
     public static void main(String[] args) {
        System.out.println("안녕하세요!");
     }
  }


HelloWorld 클래스를 실행하였다고 가정해보자. 아마도, 명령행에서 다음과 같이 입력할 것이다.

  $ java HelloWorld


이 경우, JVM이 시작되고, 앞에서 말했듯이 부트스트랩 클래스로더가 생성된 후에, 모든 클래스가 상속받고 있는 Object 클래스를 읽어온다. 그 이후에, 클래스로더는 명령행에서 지정한 HelloWorld 클래스를 로딩하기 위해, HelloWorld.class 파일을 읽는다. HelloWorld 클래스를 로딩하는 과정에서 필요한 클래스가 존재한다. 바로 java.lang.String과 java.lang.System이다. 이 두 클래스는 HelloWorld 클래스를 읽어오는 과정에서, 즉 로드타임에 로딩된다. 이 처럼, 하나의 클래스를 로딩하는 과정에서 동적으로 클래스를 로딩하는 것을 로드타임 동적 로딩이라고 한다.

이제, 런타임 동적 로딩에 대해서 알아보자. 우선, 다음의 코드를 보자.

  public class HelloWorld1 implements Runnable {
     public void run() {
        System.out.println("안녕하세요, 1");
     }
  }
  public class HelloWorld2 implements Runnable {
     public void run() {
        System.out.println("안녕하세요, 2");
     }
  }


이 두 클래스를 Runnable 인터페이스를 구현한 간단한 클래스이다. 이제 실제로 런타임 동적 로딩이 일어나는 클래스를 만들어보자.

  public class RuntimeLoading {
     public static void main(String[] args) {
        try {
           if (args.length < 1) {
              System.out.println("사용법: java RuntimeLoading [클래스 이름]");
              System.exit(1);
           }
           Class klass = Class.forName(args[0]);
           Object obj = klass.newInstance();
           Runnable r = (Runnable) obj;
           r.run();
        } catch(Exception ex) {
           ex.printStackTrace();
        }
     }
  }


위 코드에서, Class.forName(className)은 파리미터로 받은 className에 해당하는 클래스를 로딩한 후에, 그 클래스에 해당하는 Class 인스턴스(로딩한 클래스의 인스턴스가 아니다!)를 리턴한다. Class 클래스의 newInstance() 메소드는 Class가 나타내는 클래스의 인스턴스를 생성한다. 예를 들어, 다음과 같이 한다면 java.lang.String 클래스의 객체가 생성된다.

  Class klass = Class.forName("java.lang.String");
  Object obj = klass.newInstance();


따라서, Class.forName() 메소드가 실행되기 전까지는 RuntimeLoading 클래스에서 어떤 클래스를 참조하는 지 알수 없다. 다시 말해서, RuntimeLoading 클래스를 로딩할 때는 어떤 클래스도 읽어오지 않고, RuntimeLoading 클래스의 main() 메소드가 실행되고 Class.forName(args[0])를 호출하는 순간에 비로서 args[0]에 해당하는 클래스를 읽어온다. 이처럼 클래스를 로딩할 때가 아닌 코드를 실행하는 순간에 클래스를 로딩하는 것을 런타임 동적 로딩이라고 한다.

다음은 RuntimeLoading 클래스를 명령행에서 실행한 결과를 보여주고 있다.

  $ java RuntimeLoading HelloWorld1
  안녕하세요, 1


Class.newInstance() 메소드와 관련해서 한 가지 알아둘 점은 해당하는 클래스의 기본생성자(즉, 파라미터가 없는)를 호출한다는 점이다. 자바는 실제로 기본생성자가 코드에 포함되어 있지 않더라도 코드를 컴파일할 때 자동적으로 기본생성자를 생성해준다. 이러한 기본생성자는 단순히 다음과 같이 구성되어 있을 것이다.

  public ClassName() {
     super();
  }


ClassLoader

자바는 클래스로더를 사용하고, 클래스를 어떻게 언제 JVM으로 로딩하고, 언로딩하는지에 대한 특정한 규칙을 갖고 있다. 이러한 규칙을 이해해야, 클래스로더를 좀 더 유용하게 사용할 수 있으며 개발자가 직접 자신만의 커스텀 클래스로더를 작성할 수 있게 된다.

클래스로더의 사용

이 글을 읽는 사람들은 거의 대부분은 클래스로더를 프로그래밍에서 직접적으로 사용해본 경험이 없을 것이다. 클래스로더를 사용하는 것은 어렵지 않으며, 보통의 자바 클래스를 사용하는 것과 완전히 동일하다. 다시 말해서, 클래스로더에 해당하는 클래스의 객체를 생성하고, 그 객체의 특정 메소드를 호출하기만 하면 된다. 간단하지 않은가? 다음의 코드를 보자.

  ClassLoader cl = . . . // ClassLoader의 객체를 생성한다.
  Class klass = null;
  try {
     klass = cl.loadClass("java.util.Date");
  } catch(ClassNotFoundException ex) {
     // 클래스를 발견할 수 없을 경우에 발생한다.
     ex.printStackTrace();
  }


일단 클래스로더를 통해서 필요한 클래스를 로딩하면, 앞의 예제와 마찬가지로 Class 클래스의 newInstance() 메소드를 사용하여 해당하는 클래스의 인스턴스를 생성할 수 있게 된다. 형태는 다음과 같다.

  try {
     Object obj = klass.newInstance();
  } catch(InstantiationException ex) {
     ....
  } catch(IllegalAccessException ex) {
     ....
  } catch(SecurityException ex) {
     ....
  } catch(ExceptionIninitializerError error) {
     ...
  }


위 코드를 보면, Class.newInstance()를 호출할 때 몇개의 예외와 에러가 발생하는 것을 알 수 있다. 이것들에 대한 내용은 Java API를 참고하기 바란다.

자바 2의 클래스로더

자바 2 플랫폼에서 클래스로더의 인터페이스와 세만틱(semantic)은 개발자들이 자바 클래스로딩 메커니즘을 빠르고 쉽게 확장할 수 있도록 하기 위해 몇몇 부분을 재정의되었다. 그 결과로, 1.1이나 1.0에 맞게 작성된 (커스텀 클래스로더를 포함한) 클래스로더는 자바 2 플랫폼에서는 제기능을 하지 못할 수도 있으며, 클래스로더 사용하기 위해 작성했던 코드를 재작성하는 것이 그렇게 간단하지만은 않다.

자바 1.x와 자바 2에서 클래스로더에 있어서 가장 큰 차이점은 자바 2의 클래스로더는 부모 클래스로더(상위 클래스가 아니다!)를 갖고 있다는 점이다. 자바 1.x의 클래스로더와는 달리, 자바 2의 클래스로더는 부모 클래스로더가 먼저 클래스를 로딩하도록 한다. 이를 클래스로더 딜리게이션 모델(ClassLoader Delegation Model)이라고 하며, 이것이 바로 이전 버전의 클래스로더와 가장 큰 차이점이다.

자바 2의 클래스로더 딜리게이션 모델에 대해 구체적으로 알아보기 위해 로컬파일시스템과 네트워크로부터 클래스를 읽어와야 할 필요가 있다고 가정해보자. 이 경우, 쉽게 로컬파일시스템의 jar 파일로부터 클래스를 읽어오는 클래스로더와 네트워크로부터 클래스를 읽어오는 클래스로더가 필요하다는 것을 생각할 수 있다. 이 두 클래스로더를 각각 JarFileClassLoader와 NetworkClassLoader라고 하자.

JDK 1.1에서, 커스텀 클래스로더를 만들기 위해서는 ClassLoader 클래스를 상속받은 후에 loadClass() 메소드를 오버라이딩하고, loadClass() 메소드에서 바이트코드를 읽어온 후, defineClass() 메소드를 호출하면 된다. 여기서 defineClass() 메소드는 읽어온 바이트코드로부터 실제 Class 인스턴스를 생성해서 리턴한다. 예를 들어, JarFileClassLoader는 다음과 같은 형태를 지닐 것이다.

  public class JarFileClassLoader extends ClassLoader {
     ...
     private byte[] loadClassFromJarFile(String className) {
        // 지정한 jar 파일로부터 className에 해당하는 클래스의
        // 바이트코드를 byte[] 배열로 읽어온다.
        ....
        return byteArr;
     }
     
     public synchronized class loadClass(String className, boolean resolveIt)
        throws ClassNotFoundException {
        
        Class klass = null;
        
        // 클래스를 로드할 때, 캐시를 사용할 수 있다.
        klass = (Class) cache.get(className);
        
        if (klass != null) return klass;
        
        // 캐시에 없을 경우, 시스템 클래스로더로부터
        // 지정한 클래스가 있는 지 알아본다.
        try {
           klass = super.findSystemClass(className);
           return klass;
        } catch(ClassNotFoundException ex) {
           // do nothing
        }
        
        // Jar 파일로부터 className이 나타내는 클래스를 읽어온다.
        byte[] byteArray = loadClassFromJarFile(className);
        klass = defineClass(byteArray, 0, byteArray.length);
        if (resolve)
           resolveClass(klass);
        cache.put(className, klass); // 캐시에 추가
        return klass;
     }
  }


위의 개략적인 코드를 보면, 시스템 클래스로더에게 이름이 className인 클래스가 존재하는 지 요청한다. (여기서 시스템 클래스로더 또는 primordial 시스템 클래스로더는 부트스트랩 클래스로더이다). 그런 후에, 시스템 클래스로더로부터 클래스를 읽어올 수 없는 경우 Jar 파일로부터 읽어온다. 이 때, className은 완전한 클래스 이름(qualified class name; 즉, 패키지이름을 포함한)이다. NetworkClassLoader 클래스 역시 이 클래스와 비슷한 형태로 이루어져 있을 것이다. 이 때, 시스템 클래스로더와 그 외의 다른 클래스로더와의 관계는 다음 그림과 같다.



위 그림을 보면, 각각의 클래스로더는 오직 시스템 클래스로더와 관계를 맺고 있다. 다시 말해서, JarFileClassLoader는 NetworkClassLoader나 AppletClassLoader와는 관계를 맺고 있지 않다. 이제, A라는 클래스가 내부적으로 B라는 클래스를 사용한다고 가정해보자. 이 때, 만약 A 클래스는 네트워크를 통해서 읽어오고, B라는 클래스는 Jar 파일을 통해서 읽어와야 한다면? 이 경우에 어떻게 해야 하는가? 쉽사리 해결책이 떠오르지 않을 것이다. 이러한 문제는 JarFileClassLoader와 NetworkClassLoader 간에 유기적인 결합을 할 수 없기 때문에 발생한다.

자바 2에서는 이러한 문제를 클래스로더 딜리게이션 모델을 통해서 해결하고 있다. 즉, 특정 클래스로더 클래스를 읽어온 클래스로더(이를 부모 클래스로더라고 한다)에게 클래스 로딩을 요청하는 것이다. 다음의 그림을 보자.


이 그림은 자바 2에서 클래스로더간의 관계를 보여주고 있다. 이 경우, NetworkClassLoader 클래스는 JarFileClassLoader가 로딩하고, JarFileClassLoader 클래스는 AppClassLoader가 로딩하였음을 보여준다. 즉, JarFileClassLoader는 NetworkClassLoader의 부모 클래스로더가 되고, AppClassLoader는 JarFileClassLoader의 부모 클래스로더가 되는 것이다.

이 경우, 앞에서 발생했던 문제가 모두 해결된다. A 클래스가 필요하면, 가장 먼저 NetworkClassLoader에 클래스로딩을 요청한다. 그럼, NetworkClassLoader는 네트워크로부터 A 클래스를 로딩할 수 있으므로, A 클래스를 로딩한다. 그런 후, A 클래스는 B 클래스를 필요로 한다. B 클래스를 로딩하기 위해 NetworkClassLoader는 JarFileClassLoader에 클래스 로딩을 위임(delegation)한다. JarFileClassLoader는 Jar 파일로부터 B 클래스를 읽어온 후 NetworkClassLoader에게 리턴할 것이며, 따라서 NetworkClassLoader는 Jar 파일에 있는 B 클래스를 사용할 수 있게 된다. 앞의 JDK 1.1에서의 클래스로더 사이의 관계에 비해 훨씬 발전적인 구조라는 것을 알 수 있다.

앞에서 말했듯이, 자바 2에서는 몇몇 클래스로더 메커니즘을 재정의하였다. 이 때문에, JDK 1.1에서의 클래스로더에 관한 몇몇개의 규칙이 깨졌다. 먼저, loadClass() 메소드를 더 이상 오버라이딩(overriding) 하지 않고, 대신 findClass()를 오버라이딩한다. loadClass() 메소드는 public에서 protected로 변경되었으며, 실제 JDK1.3의 ClassLoader 클래스의 소크 코드를 보면 다음과 같이 정의되어 있다.

  // src/java/lang/ClassLoader.java
  public abstract class ClassLoader {
      /*
       * The parent class loader for delegation.
       */
      private ClassLoader parent;
      
      protected synchronized Class loadClass(String name, boolean resolve)
      throws ClassNotFoundException
      {
          // First, check if the class has already been loaded
          Class c = findLoadedClass(name);
          if (c == null) {
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClass0(name);
                  }
              } catch (ClassNotFoundException e) {
                  // If still not found, then call findClass in order
                  // to find the class.
                  c = findClass(name);
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
      ....
  }


위 코드를 보면 부모 클래스로더로부터 먼저 클래스 로딩을 요청하고, 그것이 실패할 경우(즉, catch 블럭)에 비로소 직접 클래스를 로딩한다. 여기서 그렇다면 부모 클래스는 어떻게 결정되는 지 살펴보자. 먼저 JDK 1.3의 ClassLoader 클래스는 다음과 같은 두 개의 생성자를 갖고 있다.

  protected ClassLoader(ClassLoader parent) {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = parent;
      initialized = true;
  }
  protected ClassLoader() {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = getSystemClassLoader();
      initialized = true;
  }


이 두 코드를 살펴보면, 부모 클래스로더를 지정하지 않을 경우, 시스템 클래스로더를 부모 클래스로더로 지정하는 것을 알 수 있다. 따라서 커스텀 클래스로더에서 부모 클래스로더를 지정하기 위해서는 다음과 같이 하면 된다.

  public class JarFileClassLoader extends ClassLoader {
     public JarFileClassLoader () {
        super(JarFileClassLoader.class.getClassLoader());
        // 다른 초기화 관련 사항
     }
     ....
     public Class findClass(String name) {
        // 지정한 클래스를 찾는다.
     }
  }


모든 클래스는 그 클래스에 해당하는 Class 인스턴스를 갖고 있다. 그 Class 인스턴스의 getClassLoader() 메소드를 통해서 그 클래스를 로딩한 클래스로더를 구할 수 있다. 즉, 위 코드는 JarFileClassLoader 클래스를 로딩한 클래스로더를 JarFileClassLoader 클래스로더의 부모 클래스로더로 지정하는 것이다. (실제로 커스텀 클래스로더를 구현하는 것에 대한 내용은 이 Article의 시리중에서 3번째에 알아보기로 한다).

JVM에서 부모 클래스로더를 갖지 않은 유일한 클래스로더는 부트스트랩 클래스로더이다. 부트스트랩 클래스로더는 자바 런타임 라이브러리에 있는 클래스를 로딩하는 역할을 맡고 있으며, 항상 클래스로더 체인의 가장 첫번째에 해당한다. 기본적으로 자바 런타임 라이브러리에 있는 모든 클래스는 JRE/lib 디렉토리에 있는 rt.jar 파일에 포함되어 있다.

결론

이번 Article에서는 자바에서 클래스 로딩이 동적으로 이루어지면, 클래스 로딩 방식에서는 로드타임 로딩과 런타임 로딩의 두 가지 방식이 있다는 것을 배웠다. 그리고 자바 2에서의 클래스로딩이 클래스로더 딜리게이션 모델(Classloader Delegation Model)을 통해서 이루어진다는 점과 이 모델에 자바 1.x에서의 클래스로딩 메커니즘과 어떻게 다르며, 어떤 장점이 있는 지 알아보았다. 다음 Article에서는 자바 2에서 기본적으로 제공하는 클래스로더에 대해서 알아보기로 한다.

'Language > JAVA' 카테고리의 다른 글

Do You Really Get Classloaders?  (0) 2016.08.04
JVM ClassLoader의 작동원리  (0) 2016.08.04
java class loader #2  (0) 2016.08.04
java class loader #1  (1) 2016.08.04
[Java] ClassLoader API  (0) 2016.08.04
:

java class loader #2

Language/JAVA 2016. 8. 4. 15:07

ClassLoader 2

 

  // src/java/lang/ClassLoader.java
  public abstract class ClassLoader {
      /*
       * The parent class loader for delegation.
       */
      private ClassLoader parent;
      
      protected synchronized Class loadClass(String name, boolean resolve)
      throws ClassNotFoundException
      {
          // First, check if the class has already been loaded
          Class c = findLoadedClass(name);
          if (c == null) {
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClass0(name);
                  }
              } catch (ClassNotFoundException e) {
                  // If still not found, then call findClass in order
                  // to find the class.
                  c = findClass(name);
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
      ....
  }

코드 4.


코드 4 JDK1.3 ClassLoader 클래스로서 부모 클래스로더로부터 먼저 클래스 로딩을 요청하고그것이 실패할 경우에 직접 클래스를 로딩합니다여기서 부모 클래스가 어떻게 결정되는 지를 보면 JDK 1.3 ClassLoader 클래스는 다음과 같은  개의 생성자를 갖고 있습니다.

  protected ClassLoader(ClassLoader parent) {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = parent;
      initialized = true;
  }
  protected ClassLoader() {
      SecurityManager security = System.getSecurityManager();
      if (security != null) {
          security.checkCreateClassLoader();
      }
      this.parent = getSystemClassLoader();
      initialized = true;
  }

코드 5.

코드 5 보면부모 클래스로더를 지정하지 않을 경우에는 시스템 클래스로더를 부모 클래스로더로 지정하고 있습니다커스텀 클래스로더에서 부모 클래스로더를 지정하기 위해서는 코드 6 같이 하면 됩니다.

  public class JarFileClassLoader extends ClassLoader {
     public JarFileClassLoader () {
        super(JarFileClassLoader.class.getClassLoader());
        // 
다른 초기화 관련 사항
     }
     ....
     public Class findClass(String name) {
        // 
지정한 클래스를 찾는다.
     }
  }

코드 6.


모든 클래스는  클래스에 해당하는 Class 인스턴스를 가지고 있습니다 Class 인스턴스의 getClassLoader() 메소드를 통해서  클래스를 로딩한 클래스로더를 구할  있습니다코드 6에서는 JarFileClassLoader 클래스를 로딩한 클래스로더를 JarFileClassLoader 클래스로더의 부모 클래스로더로 지정하는 것입니다. 
JVM
에서 부모 클래스로더를 갖지 않은 유일한 클래스로더는 부트스트랩 클래스로더로서 부트스트랩 클래스로더는 자바 런타임 라이브러리에 있는 클래스를 로딩하는 역할을 맡고 있으며항상 클래스로더 체인의   번째에 해당합니다. Java에서는 같은 클래스일지라도다른 클래스로더에 의해서 로딩되었다면 다른 클래스로 구분이 됩니다.

 

Example


  public class ToolsJarLoader extends URLClassLoader {

private static ToolsJarLoader mInstance;

public static ToolsJarLoader getInstance() {

if(mInstance == null) {

mInstance = new                ToolsJarLoader(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs());

}

return mInstance;

}


    public ToolsJarLoader(URL[] urls) {

        super(urls);

    }

    

    @Override

    public void addURL(URL url) {

        super.addURL(url);

    }

    

    public boolean isLoaded(URL url) {

     URL[] urls = getURLs();

     for(URL temp : urls) {

     if(temp.equals(url)) {

     return true;

     }

     }

     return false;

    }

}

코드 7.

코드 7 원하는 클래스  jar 런타임에 동적으로 로드하기 위한 클래스 로더이다. addURL 통해 jar  클래스를 로딩한다.


URL jarUrl = new File(toolsJarPath).toURI().toURL();

if(!ToolsJarLoader.getInstance().isLoaded(jarUrl)) {

ToolsJarLoader.getInstance().addURL(jarUrl);

}

Class cls = ToolsJarLoader.getInstance().loadClass("package.ClassName");

Object obj = cls.getConstructor(String.class).newInstance(arg1);

Method method = obj.getClass().getMethod("methodName");

method.invoke(obj);

코드 8.

코드 8 특정 위치에 있는 jar  클래스를 코드 7 있는 클래스로더를 이용하여 런타임에 동적으로 로딩하도록 한다로딩  해당 클래스를 만든다.

 

 

References

 

[1] http://javacan.tistory.com/3

[2] http://blog.naver.com/PostView.nhn?blogId=choigohot&logNo=40192701035

[3] http://javacan.tistory.com/entry/2

:

java class loader #1

Language/JAVA 2016. 8. 4. 15:06

Java 동적으로 클래스를 로딩하기 때문에 런타임에 모든 코드가 JVM 링크됩니다모든 클래스는 해당 클래스가 참조되는 순간에 동적으로 JVM 링크되며메모리에 로딩됩니다자바의 런타임 라이브러리([jdk path]/jre/lib/rt.jar) 역시 예외가 아닙니다이러한 동적인클래스 로딩은 자바의 클래스로더 시스템을 통해서 이루어지며자바가 기본적으로 제공하는 클래스로더는 java.lang.ClassLoader 통해서 표현됩니다. JVM 시작되면부트스트랩(bootstrap) 클래스로더를 생성하고 다음에 가장 첫번째 클래스인 Object 시스템에 로딩합니다.
런타임에 동적으로 클래스를 로딩한다는 것은 JVM 클래스에 대한 정보를 갖고 있지 않다는 것을 의미함으로서 JVM 클래스에 대한정보를 알지 못합니다따라서클래스로더는 클래스를 로딩할  필요한 정보를 구하고 클래스가 올바른지를 검사해야 합니다.


Load-time dynamic loading and Run-time dynamic loading

클래스를 로딩하는 방식에는 로드타임 동적 로딩(load-time dynamic loading) 런타임 동적 로딩(run-time dynamic loading) 있습니다. 

  public class HelloWorld {
     public static void main(String[] args) {
        System.out.println("
안녕하세요!");
     }
  }

코드 1.


로드타임 동적 로딩은 하나의 클래스를 로딩하는 과정에서 동적으로 클래스를 로딩하는 것을 말합니다코드 1 HelloWorld 클래스를 실행하면, JVM 시작되고앞에서 말했듯이 부트스트랩 클래스로더가 생성된 후에모든 클래스가 상속받고 있는 Object 클래스를 읽어옵니다 이후에클래스로더는 명령행에서 지정한 HelloWorld 클래스를 로딩하기 위해 HelloWorld.class 파일을 읽습니다그리고HelloWorld 클래스를 로딩하는 과정에서 필요한 클래스 java.lang.String java.lang.System 읽어옵니다  클래스는 HelloWorld 클래스를 읽어오는 로드타임에 동적으로 로딩됩니다.

 

class HelloWorld1 implements Runnable {
     public void run() {
        System.out.println("
안녕하세요, 1");
     }
  }
  class HelloWorld2 implements Runnable {
     public void run() {
        System.out.println("
안녕하세요, 2");
     }
  }

  public class RuntimeLoading {
     public static void main(String[] args) {
        try {
           if (args.length < 1) {
              System.out.println("
사용법: java RuntimeLoading [클래스 이름]");
              System.exit(1);
           }
           Class klass = Class.forName(args[0]);
           Object obj = klass.newInstance();
           Runnable r = (Runnable) obj;
           r.run();
        } catch(Exception ex) {
           ex.printStackTrace();
        }
     }
  }

코드 2.

 

런타임 동적 로딩은 클래스를 로딩할 때가 아닌 코드를 실행하는 순간에 클래스를 로딩하는 것을 말합니다.


코드 2에서 Class.forName(className) 파리미터로 받은 className 해당하는 클래스를 로딩한 후에  클래스에 해당하는 로딩한 클래스의 인스턴스가 아닌 Class 인스턴스를 리턴합니다그리고 Class 클래스의 newInstance() 메소드는 Class 나타내는 클래스의 인스턴스를 생성합니다예를 들어코드 3 java.lang.String 클래스의 객체를 생성합니다.

  Class klass = Class.forName("java.lang.String");
  Object obj = klass.newInstance();

코드 3.


따라서, Class.forName() 메소드를 실행하는 클래스에서는 Class.forName() 실행되기 전까지는 어떤 클래스를 참조하는    없습니다.


ClassLoader

클래스로더를 사용하기 위해서는 보통의 자바 클래스처럼 해당하는 클래스의 객체를 생성하고 객체의 특정 메소드를 호출하면 됩니다또한상속을 받아 클래스로드를 커스터마이징  수도 있습니다.

자바의 클래스로더는 클래스로더 딜리게이션 모델(ClassLoader Delegation Model)으로서 부모 클래스로더가 먼저 클래스를 로딩하도록합니다특정 클래스로더 클래스를 읽어온 클래스로더(부모 클래스로더)에게 클래스 로딩을 요청하는 것입니다


그림 1. ClassLoader Delegation Model[1]


그림 1 클래스로더간의 관계를 보여주고 있습니다 경우, NetworkClassLoader(가정) JarFileClassLoader(가정) 로딩하고, JarFileClassLoader 클래스는 AppClassLoader 로딩하였음을 보여줍니다, JarFileClassLoader NetworkClassLoader 부모 클래스로더가 되고, AppClassLoader JarFileClassLoader 부모 클래스로더가 되는 것입니다.

<자바에서 기본으로 제공하는 부트스트랩클래스로더 등에 관한 설명은 [2-3] 보면   있습니다.>


References

 

[1] http://javacan.tistory.com/3

[2] http://blog.naver.com/PostView.nhn?blogId=choigohot&logNo=40192701035

[3] http://javacan.tistory.com/entry/2

 

:

[Java] ClassLoader API

Language/JAVA 2016. 8. 4. 14:59

http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html


클래스를 로딩하는 책임을 지니고 있는 추상 클래스. 

기본전략: 바이너리 이름(String)을 받아서 파일 이름으로 바꾸고 파일 시스템에서 해당하는 이름의 클래스 파일을 읽어들인다.

위임 모델(delegation model)을 사용하여 클래스와 리소스를 찾는다. 각각의 ClassLoader 인스턴스는 연관된 상위(parent) 클래스 로더를 가지고 있다. 자신이 찾아 보기전에 상위 클래스 로더에 요청하여 먼저 찾아본다. VM 내장 클래스 로더인 "부트스트랩 클래스 로더"는 상위 클래스 로더가 없고 자신이 다른 ClassLoader 인스턴스의 상위가 된다.

보통 JVM은 플랫폼-독립적인 방식으로 로컬 파일 시스템에서 클래스를 읽어들인다. 예를 들어 유닉스 시스템에서 VM은 CLASSPATH 환경 변수에 정의되어 있는 디렉토리에서 클래스를 로딩한다.

하지만 어떤 클래스들은 파일에서 읽어오지 않고 네트워크에서 가져오거나 애플리케이션이 동작하면서 만들어지는 것도 있다. defineClass 메서드는 바이트 배열을 Class 클래스 인스턴스로 변환한다. Class.newInstance를 사용하여 그렇게 새로 정의된 클래스 인스턴스를 만들 수 있다.

클래스로더에 의해 만들어지는 객체의 메소드와 생성자는 다른 클래스를 참조할 수도 있다. 그렇게 참조하는 클래스들을 판단하기 위해 VM은 원래 클래스를 생성한 클래스 로더의 loadClass 메서드를 호출한다.

예를 들어 네트워크 클래스 로더를 만들어 다른 서버에서 클래스 파일을 다운로드 할 수도 있다. 다음은 예제 코드다.

ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();

네트워크 클래스 로더는 반드시 findClass와 네트워크에서 클래스를 읽어올 loadClassData를 정의해야한다. 바이트코드를 다운로드 한다음 defineClass를 사용하여 class 인스턴스를 만들어야 한다. 다음은 예제 구현체다.

     class NetworkClassLoader extends ClassLoader {
         String host;
         int port;

         public Class findClass(String name) {
             byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadClassData(String name) {
             // load the class data from the connection
              . . .
         }
     }
 

바이너리 이름

클래스로더에 전달되는 문자열로 표현한 클래스 이름은 다음에 정의된 자바 언어 표준을 따라야 한다.

예)
   "java.lang.String"
   "javax.swing.JSpinner$DefaultEditor"
   "java.security.KeyStore$Builder$FileBuilder$1"
   "java.net.URLClassLoader$3$1"

defineClass

- protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

바이트를 Class 클래스의 인스턴스로 변환한다. Class는 resolve를 한 다음에 사용해야 한다. 

loadClass

- public Class<?> loadClass(String name) throws ClassNotFountException

loadClass(naem, false); 호출

- public Class<?> loadClass(String name, boolean resolve) throws ClassNotFountException

기본 동작은 다음과 같다.
1. findLoadedClass(String)을 호출하여 클래스가 이미 로딩되었는지 확인한다.
2. 상위 클래스 로더의 loadClass 메서드를 호출한다. 만약 상위 클래스 로더가 null이면 VM 내장 클래스 로더를 사용한다.
3. findClass(String) 메서드를 사용하여 클래스를 찾는다.

만약에 위 과정을 통해 클래스를 찾은 뒤에 resolve 플래그가 true면 반환받은 Class 객체를 사용하여resolveClass(Class) 메서드를 호출한다.

클래스로더의 하위 클래스들은 이 메서드가 아니라 findClass(String)을 재정의 할 것을 권한다.
(이런걸 지켜야 리스코프 원칙을 지켰다고 하던가...)

findLoadedClass

- protected final Class<?> findLoadedClass(String name)

만약 이 클래스로더가 JVM에 initiating 로더로 기록되어 있다면 name에 해당하는 클래스를 반환한다. 그렇지 않으면 null을 반환한다.

findClass

- protected Class<?> findClass(String name) throws ClassNotFoundException

기본 구현체는 ClassNotFoundException을 던진다. 따라서 클래스 로더를 확장하는 클래스가 이것을 구현하여 delegation 모델을 따르는 구현체를 만드는데 사용해야 한다. loadClass 메서드에서 상위 클래스 로더를 확인 한 뒤에 호출된다.
(그냥 클래스만 찾으면 되지 꼭 delegation 모델을 따라야 하는건가... 사실 loadClass가 public이라 그것도 재정의하면 그만인것을. 강요하려면 하고 말려면 말지 어중간한거 아닌가..)

resolveClass

- protected final void resolveClass(Class<?> c)

해당 클래스를 링크한다. 이 메서드는 클래스 로더가 클래스를 링크할 때 사용된다. 만약 클래스 c가 이미 링크되어 있다면 이 메서드는 그냥 끝난다. 그렇지 않은 경우라면 자바 언어 스팩의 "Execution" 챕터에 기술되어 있는대로 클래스를 링크한다.

링크


링크하기(Linking)란 클래스 또는 인터페이스 타입의 바이너리를 가져와서 JVM의 런타임에 연결하여 실행 할 수 있는 상태로 만드는 것이다. 

링크 과정은 세 가지 세부 활동으로 구성된다: verification, preparation, resolution

링크 활동 구현내용은 달라질 수 있다. 예를 들어 클래스가 사용되는 순간에 개별적으로 클래스나 인터페이스에 있는 심볼릭 레퍼런스를 확정하거나(lazy or late resolution), 검증하고나서 바로 확정할 수도 있다.(static). 즉 어떤 구현체에서는 클래스나 인터페이스를 초기화 한 이후에도 확정(resolution) 프로세스가 계속 될 수 있다는 것이다.

Verification, Preparation, Resolution


Verification: 클래스나 인터페이스의 바이너리가 구조적으로 올바른지 확인한다. 검증에 실패하면LinkageError의 하위 클래스 중 하나인 VerifyError가 발생한다.

Preparation: 클래스나 인터페이스의 static 필드를 만들고 그런 필드들을 기본 값으로 초기화 하는 과정이 포함된다. 이 과정 중에 JVM 코드를 실행할 필요가 없다. 명시적인 static 필드 initializer는 이 과정이 아니라 initialization 과정에서 실행된다.

Resolution: 심볼릭 레퍼런스는 resolution단계를 지나야지 사용될 수 있다. 심볼릭 레퍼런스가 유효하고 반복적으로 사용되면 다이렉트 레퍼런스로 교체되어 보다 효율적으로 처리된다.

만약 이 단계를 지나다가 IncompatibleClassChangeError를 포함한 이 하위 에러들이 발생할 수 있다. IllegalAccessErrorInstantiationErrorNoSuchFieldErrorNoSuchMethodError

추가적으로 구현체를 찾을 수 없는 native 메서드를 선언한 클래스에서는 UnsatisfiedLinkError가 발생할 수 있다. 


출처 - http://whiteship.tistory.com/2560

: