2013년 5월 30일 목요일

[Java] Json는 Gson으로...(서버부터 안드로이드까지)

Android App을 개발하면서 정말 많이 쓰는 기술 가운데 하나가 Json 처리 입니다. 예전에는 XML을 많이 사용 하였는데 요세는 XML보다는 가벼운 Json을 많이 사용 하는거 같습니다. 저는 주로 Android에서만 작업을 하기 때문에 Json 처리를 별도의 라이브러리를 사용하지 않고 JsonObeject 와 JsonArray를 사용하여 개발해 왔었습니다. 그러던 중에 간단한 테스트를 하려고 제 데스크탑에 서버 설정을 하면서 Gson을 처음 사용해 봤는데 정말 편하고 좋았습니다. 다만 서버개발자와 서로 맞춰야 될게 몇가지가 있습니다.

먼저 Gson을 사용 하려면 라이브러리를 받아서 등록해야 됩니다. 서버든 Android든 라이브러리 등록 방법은 따로 설명드리지 않겠습니다. 서버에서 Gson을 사용하여 Json형태로 만드는 방법과  Android에서 Gson을 사용하여 Json 을 처리하는 과정을 순차적으로 포스팅 하겠습니다.

Gson 라이브러리 다운로드
Gson User Guide

서버 Gson
public class Intro {
 private int nIndex;
 private String nFirstName;
 private String nSecondName;
 private String nThirdName;
 
 public Intro(int nIndex, String nFirstName, String nSecondName,
   String nThirdName) {
  this.nIndex = nIndex;
  this.nFirstName = nFirstName;
  this.nSecondName = nSecondName;
  this.nThirdName = nThirdName;
 }
}
public class Intros {

 public ArrayList names = new ArrayList();

}
  String json = null;
  String[] nName = {"ㄱ","ㄴ","ㄷ","ㄹ","ㅁ","ㅂ","ㅅ","ㅇ","ㅈ","ㅊ","ㅋ","ㅌ","ㅍ","ㅎ"};
  Gson gson = new GsonBuilder().setPrettyPrinting().create();
//  Gson gson = new Gson();
  Intros model = new Intros();
  
  for (int i = 0; i < nName.length; i++) {
   model.names.add(new Intro(i, nName[i], nName[i], nName[i]));
  }
  json = gson.toJson(model);
  System.out.println(json);

대략적인 소스 설명을 하면 Intro bean 객체에 데이터를 넣어서 names라는 ArrayList<intro>에 넣습니다. Gson에서는 데이터를 만들때는 toJson(object) 메서드를 사용합니다. 이러면 Json포맷이 만들어 졌습니다. 정말 간단하게 Json을 만들 수 있는걸 볼 수 있습니다. Gson의 생성 방법은 2가지가 있습니다. new GsonBuilder().setPrettyPrinting().create();와 같이 생성하면 Json양식이 갈끔하게 보이게 되고 new Gson();으로 생성하게 되면 보통의 Json 처럼 직렬로 보이게 됩니다.

GsonBuilder().setPrettyPrinting().create() 로 생성
{
  "names": [
    {
      "nIndex": 0,
      "nFirstName": "ㄱ",
      "nSecondName": "ㄱ",
      "nThirdName": "ㄱ"
    },
    {
      "nIndex": 1,
      "nFirstName": "ㄴ",
      "nSecondName": "ㄴ",
      "nThirdName": "ㄴ"
    },
    {
      "nIndex": 2,
      "nFirstName": "ㄷ",
      "nSecondName": "ㄷ",
      "nThirdName": "ㄷ"
    },
    ......... 이하 생략 .........
  ]
}

new Gson()로 생성
{"names":[{"nIndex":0,"nFirstName":"ㄱ","nSecondName":"ㄱ","nThirdName":"ㄱ"},{"nIndex":1,"nFirstName":"ㄴ","nSecondName":"ㄴ","nThirdName":"ㄴ"},{"nIndex":2,"nFirstName":"ㄷ","nSecondName":"ㄷ","nThirdName":"ㄷ"},    

......... 이하 생략 .........

{"nIndex":13,"nFirstName":"ㅎ","nSecondName":"ㅎ","nThirdName":"ㅎ"}]}


Gson 라이브러리를 이용하여 Json 포맷을 만드는 코드를 살펴 보았으니 이제 Android에서 Gson 라이브러리를 이용하여 Json 포맷의 데이터를 어떻게 사용하는지 포스팅하겠습니다.
Gson을 사용할때 한가지 주의 사항은 위에서도 언급했듯이 서버와 클라이언트가 서로 맞추어야 되는 부분이 있습니다. 바로 Namming입니다. ArrayList<Name>를 만들때는 서버에서 사용하고 있는 변수명인 names로 맞춰주고 Bean 파일의 변수명도 서버와 동일하게 맞춰 줍니다. ArrayList<Name>의 변수명이 서버와 다른 경우 데이터가 ArrayList<Name>에 들어가지 않습니다.

Android Gson
public class Name { 
 private int nIndex;
 private String nFirstName;
 private String nSecondName;
 private String nThirdName;
 
 public int getnIndex() {
  return nIndex;
 }
 public void setnIndex(int nIndex) {
  this.nIndex = nIndex;
 }
 public String getnFirstName() {
  return nFirstName;
 }
 public void setnFirstName(String nFirstName) {
  this.nFirstName = nFirstName;
 }
 public String getnSecondName() {
  return nSecondName;
 }
 public void setnSecondName(String nSecondName) {
  this.nSecondName = nSecondName;
 }
 public String getnThirdName() {
  return nThirdName;
 }
 public void setnThirdName(String nThirdName) {
  this.nThirdName = nThirdName;
 }
}
public class Names {
 private ArrayList names = new ArrayList();
 public ArrayList getnList() {
  return names;
 }
 public void setnList(ArrayList names) {
  this.names = names;
 }
}
 String introJson = HttpConnecter.getJson(url주소);
 FLLog.e(TAG, "introJson : " + introJson);
   
 this.mNames = new Gson().fromJson(introJson, Names.class);
 FLLog.e(TAG, "mNames : " + mNames.getnList().size()); 
Bean파일을 보면 변수명은 똑같은걸 확인 할 수 있습니다. 다만 서버에서는 데이터를 넣기만 하면 되기때문에 생성자에서 세팅되게 하였으며 Android에서는 가져다가 써야 하므로 Getter and Setter를 만들어준 차이 밖에 없습니다. HttpConnecter.getJson(url주소)를 이용하여 Json 데이터를 가지고 옵니다. 소스에서는 보이지 않지만 당연히 Thread를 이용하여 데이터를 가지고 오고 있습니다. 가지고온 데이터는 introJson 이라는 String 변수에 저장을 했습니다. Gson으로 생성된 Json 포맷을 받을때는 formJson(data, format)으로 받으면 됩니다. JsonObject를 사용 할 때 처럼 하나하나 받아주지 않고 이런식으로 한번에 통으로 list에 넣을 수가 있습니다.

2013년 5월 20일 월요일

[Android] ActionBar Tabs

Android UI를 구성할때 많이 사용하는 것이 Tab기능입니다. 이런 Tab기능을 ActionBar에서 사용 할 수 있습니다. 대표적으로 Google Play 나 YouTube을 보시면 확인 할 수 있습니다.
ActionBar에서 Tab 기능을 사용하면 ActionBar의 여유공간에 따라 Tabs의 위치가 달라질 수 있습니다.


ActionBar에 Tab 추가

  1. ActionBar에 Tab을 추가하기 위해서는 getActionBar()을 이용하여 ActionBar를 가지고 온다.
  2. bar.addTab(bar.newTab().setText("Tab 1").setTabListener(this));를 사용하여 Tab을 추가한다
  3. bar.setNavigationMode()를 설정해 준다.(ActionBar.NAVIGATION_MODE_TABS로 설정해 주시면 Tabs 영역이 나타나게 된다.)
  4. setTabListener를 사용하여 TabListener를 등록 해준다.








 @Override
 protected void onCreate(Bundle savedInstanceState) {
  // TODO Auto-generated method stub
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_navigator);
  
  findViewById(R.id.btnNavi01).setOnClickListener(this);
  final ActionBar bar = getActionBar();

  bar.addTab(bar.newTab().setText("Tab 1").setTabListener(this));
  bar.addTab(bar.newTab().setText("Tab 2").setTabListener(this));
  bar.addTab(bar.newTab().setText("Tab 3").setTabListener(this));
 }

 @Override
 public void onClick(View v) {
  // TODO Auto-generated method stub
  final ActionBar bar = getActionBar();
  int flags = 0;
  switch (v.getId()) {
  case R.id.btnNavi01:
   bar.setNavigationMode(bar.getNavigationMode() == ActionBar.NAVIGATION_MODE_STANDARD ? ActionBar.NAVIGATION_MODE_TABS : ActionBar.NAVIGATION_MODE_STANDARD);
   return;
  }
  int change = bar.getDisplayOptions() ^ flags;
  bar.setDisplayOptions(change, flags); 
 }

TabListener를 implements 받아서 처리 하면 아래와 같은 메서드를 Override하게 됩니다.
 @Override
 public void onTabReselected(Tab tab, FragmentTransaction ft) {
  // TODO Auto-generated method stub
  Log.d(TAG, "onTabReselected " + tab.getText());
 }

 @Override
 public void onTabSelected(Tab tab, FragmentTransaction ft) {
  // TODO Auto-generated method stub
  Log.d(TAG, "onTabSelected " + tab.getText());
 }

 @Override
 public void onTabUnselected(Tab tab, FragmentTransaction ft) {
  // TODO Auto-generated method stub
  Log.d(TAG, "onTabUnselected " + tab.getText());
 }
ActionBar에서 Tab을 추가하는 것을 간단하게 포스팅 하였습니다. 실제 App을 개발할때는 Fragment와 결합하여 사용하게 되며 ViewPager와 연계하여 Swipe기능을 사용하여 개발 할 수도 있습니다.


참조

2013년 5월 16일 목요일

[Flume] Flume 설치부터 예제까지...

Flume OG는 구버전이니 Flume NG를 사용하여 설치부터 사용 예제까지 포스팅 하겠습니다. 여러 머신에서 사용하려면 각각의 머신에 동일하게 Flume를 설치해야 되며 Hadoop이 설치되어있다면 Flume의 라이브러리를 Hadoop 라이브러리에 복사해야 됩니다.

설치
  • http://flume.apache.org/download.html 에서 Apache Flume binary(tar.gz)를 다운로드 받습니다.
  • tar -xvfz apache-flume-1.3.1-bin.tar.gz 
  • ln -s apache-flume-1.3.1-bin.tar.gz flume

환경변수 설정 
export FLUME_HOME=/apps/apache-flume-1.3.1-bin.tar.gz
PATH=$PATH:$HOME/bin:${FLUME_HOME}/bin
export PATH

환경설정
  • cd flume
  • cp flume-conf.properties.template flume.conf
  • vi flume.conf

# The configuration file needs to define the sources, 
# the channels and the sinks.
# Sources, channels and sinks are defined per agent, 
# in this case called 'agent'

agent.sources = seqGenSrc
agent.channels = memoryChannel
agent.sinks = loggerSink

# For each one of the sources, the type is defined
agent.sources.seqGenSrc.type = seq //source 타입

# The channel can be defined as follows.
agent.sources.seqGenSrc.channels = memoryChannel  // source를 channel과 연결

# Each sink's type must be defined
agent.sinks.loggerSink.type = logger  // sink 타입

#Specify the channel the sink should use
agent.sinks.loggerSink.channel = memoryChannel  // sink를 channel과 연결

# Each channel's type is defined.
agent.channels.memoryChannel.type = memory //channel 타입

# Other config values specific to each type of channel(sink or source)
# can be defined as well
# In this case, it specifies the capacity of the memory channel
agent.channels.memoryChannel.capacity = 100 // channel의 용량

이 파일에서 Source, Sink, Channel를 어떻게 설정하냐에 따라 여러 Flow을 구성 하실 수가 있습니다.

환경설정 샘플
제가 구축한 설정은 서버1, 서버2, 서버3에다가 각각 Flume을 설치한 후 서버1에서는 서버2와 서버3에서 보내는 데이터를 파일로 저장합니다. 서버2와 서버3은 로그파일을 읽어 서버 1로 보내줍니다.

서버1 flume.conf
agent01.sources = avroGenSrc
agent01.channels = memoryChannel
agent01.sinks = fileSink

# For each one of the sources, the type is defined
agent01.sources.avroGenSrc.type = avro
agent01.sources.avroGenSrc.bind = localhost
agent01.sources.avroGenSrc.port = 3333

# The channel can be defined as follows.
agent01.sources.avroGenSrc.channels = memoryChannel

# Each sink's type must be defined
agent01.sinks.fileSink.type = file_roll
agent01.sinks.fileSink.sink.directory = /home/aaaaa/flume/data
agent01.sinks.fileSink.sink.rollInterval = 30
agent01.sinks.fileSink.sink.batchSize = 100


#Specify the channel the sink should use
agent01.sinks.fileSink.channel = memoryChannel

# Each channel's type is defined.
agent01.channels.memoryChannel.type = memory

# Other config values specific to each type of channel(sink or source)
# can be defined as well
# In this case, it specifies the capacity of the memory channel
agent01.channels.memoryChannel.capacity = 100000
agent01.channels.memoryChannel.transactionCapacity = 10000


서버2, 서버3 flume.conf
서버2와 서버3의 환경설정은 똑같습니다. 단지 agent 명만 다릅니다.
agent02.sources = execGenSrc
agent02.channels = memoryChannel
agent02.sinks = avroSink

# For each one of the sources, the type is defined
agent02.sources.execGenSrc.type = exec
agent02.sources.execGenSrc.command = tail -F /home/aaaaa/hadoop/logs/logsample.log
agent02.sources.execGenSrc.batchSize = 10

# The channel can be defined as follows.
agent02.sources.execGenSrc.channels = memoryChannel

# Each sink's type must be defined
agent02.sinks.avroSink.type = avro
agent02.sinks.avroSink.hostname = 데이타가 보내져야될 호스트 주소
agent02.sinks.avroSink.port = 3333
agent02.sinks.avroSink.batch-size = 10


#Specify the channel the sink should use
agent02.sinks.avroSink.channel = memoryChannel

# Each channel's type is defined.
agent02.channels.memoryChannel.type = memory

# Other config values specific to each type of channel(sink or source)
# can be defined as well
# In this case, it specifies the capacity of the memory channel
agent02.channels.memoryChannel.capacity = 100000
agent02.channels.memoryChannel.transactionCapacity = 10000

실행

  • ./bin/flume-ng agent --conf-file ./conf/flume.conf --name agent01
  • ./bin/flume-ng agent --conf-file ./conf/flume.conf --name agent02
  • ./bin/flume-ng agent --conf-file ./conf/flume.conf --name agent03
데이터가 이동되는 것을 볼 수 있습니다. 한가지 이슈는 exec는 파일을 읽을때 버퍼링을 좀 있다고 합니다. 파일을 수정해보면 파일 내용이 잘 전달되어 오는것을 확인 하실 수 있습니다.

참고

[Flume] 내가 바로 Flume 이다.

최근 IT업계에서 가장 핫 이슈는 빅데이터가 아닐까 생각합니다.
여러 관련 프로그램 중에서 Flume을 설정 할 일이 있어 알아 본 내용을 포스팅 하겠습니다.

About Flume

일단 Flume의 뜻은 용수로 라는 뜻입니다. 여러 머신에 있는 데이타를 한 곳에 저장시켜주는 역할을 하기때문에 Flume 이란 의미와 잘 맞습니다. Flume은 0.9.x 버전과 1.x버전으로 나눠 볼 수가 있습니다. 0.9.x는 Flume OG라고 불리며 1.x는 Flume NG라고 불려집니다. Flume이 1.x로 버전이 바뀌면서 단순 기능 추가가 아닌 아키텍쳐 자체가 바뀌어 버렸기 때문에 설정하는 부분이 완전 틀려졌습니다. Flume 검색해 보면 Flume NG보다 Flume OG의 예제가 더 많이 나오나 저는 Flume NG로 구성하였습니다.(사실 Flume OG로 설정하다가 원하는데로 구현이 잘 안되서 Flume NG로 구현했습니다.) Flume NG가 나온 이유는 설정이 다소 번거로운 Flume OG를 개선하기 위해서 나왔습니다. Flume OG와 Flume NG 둘 다 설정해 본 결과 확실해 Flume NG의 개념이 확장하거나 설정하기가 훨씬 간편해 진걸 느낄 수 있었습니다.



Flume OG 와 Flume NG의 차이점

가장 큰 변화는 아키텍쳐 자체가 변했다는 점입니다. Flume OG의 아키텍쳐를 보면 Agent, Collector, Master로 나눠져 있습니다.(HDFS는 Hadoop 저장 장치를 의미합니다.) Flume은 하나의 머신에서 논리적으로 나눠서 구성할 수도 있고 각각의 머신으로 물리적으로 구성 할 수도 있습니다. 저는 쉽게 설명드리기 위해 물리적 구성으로 설명 드리겠습니다. Flume OG는 각각의 머신에 있는 Agent가 모은 데이터를 Collector라는 애가 있는 머신으로 보냅니다. Collector라는 애는 각각의 머신에 있는 Agent에게서 받은 데이터를 최종 저장 장치에 보내게 됩니다. 이런 데이터 흐름을 관리하는 곳이 Master 입니다. 간단한 예를 들어 보자면 각각의 배(Agent)에서 잡은 생선들은 경매장(Collector)에 모이고 경매된 생선들은 다시 노량진 수산시장(HDFS같은 최종 저장장치) 같은 최종 소비자가 구매 할 수 있는 곳으로 모이게 되져. Flume OG의 개념이 이와 같습니다. Flume OG는 각각의 역할이 정해져 있어서 설정할 때도 각각의 역할에 맞게 명시적으로 설정을 해줘야 되는데 한번 구성하고 땡이면 상관없는데 수평적으로 Agent를 확장 할 수도 있고 Collector를 확장 할 수 있는 구조에서 확장 될때 마다 Master를 다시 설정해야 되는 구조가 불편했습니다. 사실 Collector는 Agent입니다. Master에서 어떻게 설정 하느냐에 따라 Agent가 Collector가 되는 것입니다. 이런 불편함을 개선한게 Flume NG입니다.


Flume NG에서는 Master가 사라졌습니다. Collector 개념도 없어졌습니다. Collector는 위에서 설명했듯이 Master에서 Collector라고 이름만 바꿔준 Agent 입니다. 그래서 Master가 없어졌으니 당연히 Collector도 없어진 것입니다.(이래서 줄을 잘서야 됩니다.) 아키텍쳐를 처음 보시면 Flume OG와 Flume NG의 시점이 달라서 눈에 확 안들어 올 수 있지만 잘 보시면 둘다 데이터의 Flow를 나타내고 있다는 걸 아실 수 있습니다.  Flume NG의 아키텍쳐는  Agent안에서의 데이터 흐름만 나타내고 있는데 Flume NG 에서는 Agent만 알면 되고 Flume OG는 Agent, Collector, Master 를 알아야지만 데이터의 Flow를 설명 할 수 있기 때문입니다. 또 설명 드리지만 Flume NG에서는 Master와 Collector 개념이 빠졌습니다. Flume OG의 아키텍쳐에서 Master와 Collector를 빼보면 Flume NG의 아키텍쳐와 똑같다는 걸 알 수 있습니다.
Flume NG의 아키텍쳐를 보면 Agent안에 Source, Sink, Channel이 있는 것을 확인 할 수 있습니다. Source, Sink는 Flume OG에서도 Agent 안에 있던 아이들 입니다. Source는 데이터가 Agent로 들어오는 곳이고 Sink는 Agent가 데이터를 내보낼때 사용 하는곳 입니다.사람을 Agent라고 하면 Source는 입이고 Sink는...똥......꼬?가 되겠네요. 음식물(데이터)이 입(Source)을 통해 몸(Channel)에 들어오면 똥꼬(Sink)를 통해 똥(데이터)으로 나오고 그 똥들은 정화조(HDFS)에 모입니다. Channel은 일종의 큐라고 생각하면 됩니다. Source로 부터 데이터가 들어오면 Channel에 쌓이고 Sink는 Channel에 있는 데이터를 꺼내와 내보내게 됩니다. 이렇게 함으로써 Flume OG에서는 전체 Flow를 관리하던 Master가 있어야 됐지만 Flume NG는 Agent 하나하나의 설정만 알고 있으면 되게 되었습니다.

Flume NG Flow 구성 종류

Setting multi-agent flow



Consolidation



Multiplexing the flow





Flume NG로 구성 할 수 있는 Flow 구성도 입니다. 가운데 있는 Consolidation Flow가 Flume OG의 아키텍쳐와 비슷 하다는걸 알 수 있습니다. Flume NG는 각각의 Agent만 알고 있으면 된다고 했습니다. 그 의미는 Source의 Type과 Sink의 Type만 결정하여 설정해 놓으면 Agent가 수평적으로 확장이 되어도 다른 Agent는 신경 쓸 필요가 없어집니다. Consolidation Flow를 보면 각각의 Agent가 하나의 Agent로 데이터를 보내고 있는것을 알 수 있습니다. Flume OG의 용어로 말하자면 각각의 Agent에서 Collector로 데이터를 보내는 것입니다. 하지만 Flume NG에서는 Collector가 아닌 Agent에서 Agent로 데이터를 이동하는 것 뿐입니다. Multiplexing Flow를 보면 Channel과 Sink를 여러개 만들어 각각의 저장소에 저장하거나 다른 Agent로 보낼 수도 있음을 확인 할 수 있습니다. 굉장히 심플해 지면서 유연성있게 변경된 걸 확인 할 수 있습니다.

Agent Type
모든 Type들을 살펴본 것은 아니며 몇가지 테스트한 Type에 대해서만 설명 드리도록 하겠습니다.

Source Type

  • Avro Source : Agent 와 Agent를 연결해 주는 Type입니다. Agent끼리 연결 할 경우는 Source와 Sink의 Type를 avro로 해주면 됩니다.
  • Exec Source : 파일을 읽을때 사용 하는 Type으로서 Command 속성을 이용하여 tail 명령어로 데이터를 읽어 올 수 있습니다.
  • NetCat Type : 터미널에서 데이터를 입력 받을 때 사용 합니다.
  • Syslog Source : SyslogTCP와 SyslogUDP로 나뉘며 SyslogTCP는 로그 한줄 한줄이 새로운 Event이이나 SyslogUDP는 전체로그를 한번의 Event로 보냅니다.

이밖에도 Scribe Source, Json Source, Custom Source 등 여러 Type이 있습니다.

Sink Type

  • Avor Sink : Agent 와 Agent를 연결해 주는 Type입니다. Agent끼리 연결 할 경우는 Source와 Sink의 Type를 avro로 해주시면 됩니다.
  • HDFS Sink : Hadoop 저장장치에 저장할때 사용 하는 Type 입니다.
  • File_Roll Sink : 원하는 저장장치에 파일로 저장한다.
  • HBase Sink : HBase에 형태로 저장한다.

이밖에도 Logger Sink, Custom Sink등 여러 Type이 있습니다.

Channel Type

  • Memory Channel : Source에서 받은 Event를 Memory에 가지고 있는 방식으로 빠르고 간편하지만 Event를 잃어버릴 수도 있습니다.
  • JDBC Channel : JDBC 형태로 저장한다.
  • File Channel : File 형태로 저장한다.

이밖에도 Custom Channel도 있습니다.

모든 Type를 다 테스트하기에는 어려운 점이 있어 제가 알아본 Type만 작성 하였습니다. Flume User Guide를 참고하시면 여러 Type에 관해 알 수 있습니다.

참고

[Flume] Flume 설치부터 예제까지...