블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2013. 9. 23. 11:40 NodeJS/Concept

Node.js 위에 기본적으로 Express.js를 많이 사용하지만 좀 더 추상화 되고 MVC 다운 프레임워크를 사용해 보고자. Express.js 및 Socket.io 와 다양한 데이터베이스를 추상화해서 사용할 수 있는 기능을 갖춘 Sails.js Framework을 사용해 보도록 한다. 



1. 사용 배경

  - Enterprise 급 MVC Framework : Express.js + Socket.io 기본 설정되어 사용

  - Waterline 이라는 adapter를 통하여 다양한 Database의 호환성 보장 

    + 흐름 : Model 객체 <-> Waterline <-> Databases 

    + 지원 데이터베이스 : MySql, PostgreSql, MongoDB, etc

    + 데이터베이스 연결을 하지 않으면 파일 또는 디스크에 백만개 밑으로 key:value 저장하는 node-dirty의 어뎁터를 사용한다

  - 손쉬운 설치 및 RESTful Web Services 개발

  - socket.io 및 redis 통합 

  - SPA (Single Page Application) 개발을 위한 client framework (backbone, ember, angular)의 adapter 제공 

    + Yeoman generator 제공 : Generator Sails AngularJS



2. 설치 및 사용

  - Yeoman을 기본 설치한다 : Yeoman 이해와 설치하기

  - "generator-sails-angular" 설치 후 "yo" 명령수행 : 빨간색의 Sails-angular 선택 (주의, sails 0.9.4 버전과 호환안됨)

// generator 설치

$ sudo npm install -g generator-sails-angular generator-angular


// yeoman 명령 수행

$ yo

[?] What would you like to do? (Use arrow keys)

 ❯ Run the Angular generator (0.3.1)

   Run the Backbone generator (0.1.7)

   Run the Bootstrap generator (0.1.3)

   Run the Express generator (0.1.0)

   Run the Generator generator (0.2.0)

   Run the Mocha generator (0.1.1)

   Run the Sails-angular generator (0.0.1)

   Update your generators

   Install a generator

   Find some help

   Get me out of here!


// 선택을 하면 Server에 Sails 환경과 Client에 AngularJS 환경이 동시에 설정된다 

// Client는 기본 Twitter bootstrap을 사용하고 Client 추가 Component는 "bower install <Component명칭>" 을 사용하여 설치한다 

// Server는 "npm install <module명칭>"을 사용하여 설치한다


  - "sails lift" 수행 (2013.09.23현재 0.9.4 버전으로 sails upgrade : sudo npm update sails -g )

$ sails lift

info:

info:

info:    Sails.js           <|

info:    v0.9.4              |\

info:                       /|.\

info:                      / || \

info:                    ,'  |'  \

info:                 .-'.-==|/_--'

info:                 `--'-------'

info:    __---___--___---___--___---___--___

info:  ____---___--___---___--___---___--___-__

info:

info: Server lifted in `/Users/prototyping/sails/test`

info: To see your app, visit http://localhost:1337

info: To shut down Sails, press <CTRL> + C at any time.


debug: --------------------------------------------------------

debug: :: Mon Sep 23 2013 10:05:04 GMT+0900 (KST)

debug:

debug: Environment : development

debug: Port : 1337

debug: --------------------------------------------------------



3. Sails MVC 이해하기 

  - Model 

    + Waterline 이라는 Sails Adapter를 통하여 다양한 데이터베이스의 객체로 사용되어 진다 

    + 단, Waterline의 형식(JSON 포멧)에 맞추어서 Model을 정의한다 : 모든 데이터베이스에 추상화된 정의이다 

    + /config/adapters.js 파일에 Database 환경 설정을 한다 또한 관련 데이터 베이스 adapter를 설치해야 함 

       예) MongoDB 설치 

$ npm install sails-mongo --save

npm http 200 https://registry.npmjs.org/sails-mongo

npm http GET https://registry.npmjs.org/sails-mongo/-/sails-mongo-0.9.5.tgz

.. 중략 ..

    + /api/models/ 디렉토리 밑에 <Model>.js 파일이 위치한다

// model 생성  

$ sails generate model Person


// 생성 내역 : Model의 맨앞자는 자동으로 대문자로 변경됨 

$ /api/models> cat Person.js

/*---------------------

:: Person

-> model

---------------------*/

module.exports = {

attributes : {

// Simple attribute:

// name: 'STRING',


// Or for more flexibility:

// phoneNumber: {

// type: 'STRING',

// defaultValue: '555-555-5555'

// }

}

};/


// CRUD : 기본 Deferred Promised 지원 

생성 : Person.create

조회 : Person.findOne/find

갱신 : Person.update

제거 : Person.destroy



  - Controller

    + view와 model 사이의 제어 역할을 한다

    + /api/controllers/ 디렉토리에 위치함 

// comment controller 만들기 : 최종 명령 tag뒤에 스페이스가 있으면 안된다. 

// controller 뒤 첫번째 인자 : comment가 controller의 명칭

// controller 두번째 인자이후 : comment의 메소드 명칭들 열거  

$ sails generate controller comment create like tag 


// 결과 

$ /api/controllers> cat CommentController.js

/*---------------------

:: Comment

-> controller

---------------------*/

var CommentController = {

// To trigger this action locally, visit: `http://localhost:port/comment/create`

create: function (req,res) {

// This will render the view: /views/comment/create.ejs

res.view();

},


// To trigger this action locally, visit: `http://localhost:port/comment/like`

like: function (req,res) {

// This will render the view: /views/comment/like.ejs

res.view();

},


// To trigger this action locally, visit: `http://localhost:port/comment/tag`

tag: function (req,res) {

// This will render the view:/views/comment/tag.ejs

res.view();

}


};

module.exports = CommentController; 


// 브라우져 호출 

http://localhost:1337/comment/create (like, tag) 


  - Route

    + RESTful 호출을 uri를 제어하고 싶다면 Route를 지정한다 

    + /config/routes.js 파일안에 정의한다 

    + controller와 action을 정의한다 

    + home의 경우 

       1) /config/routes.js 에 하기와 같이 home 컨트롤러 정의 

       2) /api/controllers/HomeController.js 존재 : index액션(펑션) 존재

       3) /views/home/index.ejs 파일 존재 : 서버에서 rendering되어 클라이언트 응답 

// Routes

// *********************

// 

// This table routes urls to controllers/actions.

//

// If the URL is not specified here, the default route for a URL is:  /:controller/:action/:id

// where :controller, :action, and the :id request parameter are derived from the url

//

// If :action is not specified, Sails will redirect to the appropriate action 

// based on the HTTP verb: (using REST/Backbone conventions)

//

// action을 정의하지 않으면 다음의 기본 형식을 따른다 

// GET:     /:controller/read/:id

// POST:   /:controller/create

// PUT:      /:controller/update/:id

// DELETE:/:controller/destroy/:id

//

// If the requested controller/action doesn't exist:

//   - if a view exists ( /views/:controller/:action.ejs ), Sails will render that view

//   - if no view exists, but a model exists, Sails will automatically generate a 

//       JSON API for the model which matches :controller.

//   - if no view OR model exists, Sails will respond with a 404.

//

module.exports.routes = {

// To route the home page to the "index" action of the "home" controller:

'/' : {

controller : 'home'

}


// If you want to set up a route only for a particular HTTP method/verb 

// (GET, POST, PUT, DELETE) you can specify the verb before the path:

// 'post /signup': {

// controller : 'user',

// action : 'signup'

// }


// Keep in mind default routes exist for each of your controllers

// So if you have a UserController with an action called "juggle" 

// a route will be automatically exist mapping it to /user/juggle.

//

// Additionally, unless you override them, new controllers will have 

// create(), find(), findAll(), update(), and destroy() actions, 

// and routes will exist for them as follows:

/*


// Standard RESTful routing

// (if index is not defined, findAll will be used)

'get /user': {

controller : 'user',

action : 'index'

},

'get /user/:id': {

controller : 'user',

action : 'find'

},

'post /user': {

controller : 'user',

action : 'create'

},

'put /user/:id': {

controller : 'user',

action : 'update'

},

'delete /user/:id': {

controller : 'user',

action : 'destroy'

}

*/

};


  - View

    + Sails는 기본 ejs를 사용한다 

    + /views/ 밑에 위치한다 

    + Partial 파일을 include 할 수 있다 

    + 메인 파일 : layout.ejs 

   


  - Adapter

    + 데이터베이스를 바꾸고 싶다면 /config/adapters.js 에서 내용을 바꾼다 : MongoDB를 사용한다면 mongo adapter를 설치해야 한다

// Configure installed adapters

// If you define an attribute in your model definition, 

// it will override anything from this global config.

module.exports.adapters = {


// If you leave the adapter config unspecified 

// in a model definition, 'default' will be used.

'default': 'memory',

// In-memory adapter for DEVELOPMENT ONLY

// (data is NOT preserved when the server shuts down)

        // Sails-dirty는 Node-Dirty의 Adapter를 memory or disk에 JSON 을 저장하는 store이다. 백만개 밑으로 저장시 사용

memory: {

module: 'sails-dirty',

inMemory: true

},


// Persistent adapter for DEVELOPMENT ONLY

// (data IS preserved when the server shuts down)

// PLEASE NOTE: disk adapter not compatible with node v0.10.0 currently 

// because of limitations in node-dirty

// See https://github.com/felixge/node-dirty/issues/34

disk: {

module: 'sails-dirty',

filePath: './.tmp/dirty.db',

inMemory: false

},


// MySQL is the world's most popular relational database.

// Learn more: http://en.wikipedia.org/wiki/MySQL

mysql: {

module : 'sails-mysql',

host : 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS',

user : 'YOUR_MYSQL_USER',

password : 'YOUR_MYSQL_PASSWORD',

database : 'YOUR_MYSQL_DB'

}

};

   + generator-sails-angular 를 yeoman통하여 설치하였다면 SPA를 개발하는 것이므로 실제로 ejs 쓸 일이 거의 없고, Client 단에서 AngularJS 정의에 따라 Routing과 View Control이 발생한다. 따라서 Controller를 통하여 수행할 action에서 response.view() 하는 것이 아니라, response.json(<JSON Object>) 만을 응답으로 주면 될 것으로 보인다. 또한 업무적인 처리는 action에서 구현하면 된다   

   + response.send(형식)

res.send(); // 204

res.send(new Buffer('wahoo'));

res.send({ some: 'json' });

res.send('<p>some html</p>');

res.send('Sorry, cant find that', 404);

res.send('text', { 'Content-Type': 'text/plain' }, 201);

res.send(404);

   + response.json(형식)

res.json(null);

res.json({ user: 'tj' });

res.json('oh noes!', 500);

res.json('I dont have that', 404);


* Redis나 데이터베이스의 외부 인터페이스 라이브러리는 Custom Adapter 개발을 수행한다. (만드는 방법



3. MongoDB 연결하여 RESTful 호출 테스트 

  - mongodb 환경설정 : sails-mongo 어뎁터가 설치되어 있어야 함 

$ cd config && vi adapters.js  이동하여 mongodb 설정 입력


////////////////////////

// adapters.js 수정 내역 

module.exports.adapters = {

  // If you leave the adapter config unspecified 

  // in a model definition, 'default' will be used.

  'default': 'mongo',


  // In-memory adapter for DEVELOPMENT ONLY

  memory: {

    module: 'sails-memory'

  },


  // Persistent adapter for DEVELOPMENT ONLY

  // (data IS preserved when the server shuts down)

  disk: {

    module: 'sails-disk'

  },


  // sails v.0.9.0

  mongo: {

    module   : 'sails-mongo',

    host     : 'localhost',

    port     : 27017,

    user     : '',

    password : '',

    database : 'sailsdb'

  }

};

  

  - Person model 수정

// ProjectName/models/Person.js  수정 내역 

module.exports = {

  attributes: {

  name: 'string',

  phone: 'string',

  address: 'string',

  sex: 'string',

  etc: 'string' 

  }

};


  - PersonController의 create 수정 : Promise 구문 형식 사용가능  

// ProjectName/controllers/PersonController.js  수정 내역

module.exports = {

  create: function (req,res) {

    Person.create({ 

      name: req.param('name'),

      phone: req.param('phone'),

      address: req.param('address'),

      sex: req.param('sex'),

      etc: req.param('etc')

    }).done(function(err, person){

      if(err) throw err;

      res.json(person);

    });

  },

  .. 중략 ..

}


  - Postman을 통하여 호출 테스트 : Postman chrome extension을 설치한다. 


  - mongo shell 에서 person collection 조회 : model이 mongodb에서 collection과 맵핑된다  

///////////////////

// mongo shell 확인 

mongo

MongoDB shell version: 2.4.5

connecting to: test

show dbs

local 0.078125GB

sailsdb 0.203125GB

use sailsdb

switched to db sailsdb

show collections

person

system.indexes

> db.person.find();

{ "name" : "도원", "phone" : "1004", "address" : "seoul", "sex" : "man", "etc" : "developer", "createdAt" : ISODate("2013-09-23T02:28:51.281Z"), "updatedAt" : ISODate("2013-09-23T02:28:51.281Z"), "_id" : ObjectId("523fa76377e9f89562000001") }



4. MongoDB의 _id를 sequence number로 바꾸기 

  - "_id" : ObjectId("523fa76377e9f89562000001") 라고 나오는 것을 "_id" : 1 씩 증가하는 정수로 바꾸기 

  - 최대한 sails-mongo adapter의 api를 사용하여 구현한다 

  - sequence document 만들기 

> db.seq.insert(

 {

   _id: 'personid',

   seq: 0

 });

> db.seq.find();

{ "_id" : "personid", "seq" : 0 }


  - Sails Model을 정의한다 

$ sails generate model seq

$ cd models && vi Seq.js


// Seq.js 내역 

// _id를 통하여 여러 model의 sequence 값을 구분하기 위해 사용한다 

module.exports = {

  attributes: {

  _id: 'string',

  seq: 'integer'

  }

};


  - Person의 모델에서 명시적으로 _id 를 integer로 정의한다 

module.exports = {

  attributes: {

  _id: 'integer',

  name: 'string',

  phone: 'string',

  address: 'string',

  sex: 'string',

  etc: 'string'

  }

};


  - PersonController.js 내역 수정 : Gist 소스 파일

  /**

   * /person/create

   */ 

  create: function (req,res) {

      // seq 번호를 얻어온다 

      Seq.find({_id: 'personid'}).done(function(err, seqObj) {

        if(err) throw err;

        // 배열임을 주의  

        var seqNo = seqObj[0].seq;

        seqNo++;

        console.log('>>> seqNo is', seqNo);

        

        // insert시에 _id 값을 넣어준다 

        Person.create({ 

          _id: seqNo, 

          name: req.param('name'),

          phone: req.param('phone'),

          address: req.param('address'),

          sex: req.param('sex'),

          etc: req.param('etc')

        }).done(function(err, person){

          if(err) throw err;

          res.json(person);

        });


       // sails-mongo의 api에 한번에 select & update해주는 findAndModify 메소드가 없는 관계로 seqNo를 update해준다 

        Seq.update({_id: 'personid'}, {seq: seqNo}).done(function(err, seqObj){

          if(err) throw err;

        })

      });

  },


  - Postman으로 테스트 데이터를 입력한다 


  - mongo shell로 mongodb에 저장된 내역을 확인해 보자 

// postman통하여 데이터를 2개 넣었을 경우 

> db.seq.find();

{ "_id" : "personid", "seq" : 2, "updatedAt" : ISODate("2013-09-23T09:47:29.548Z") }


> db.person.find();

{ "_id" : 1, "name" : "윤도원-8", "phone" : "1004", "address" : "목동", "sex" : "사람", "etc" : "모비콘 블로깅", "createdAt" : ISODate("2013-09-23T09:45:36.391Z"), "updatedAt" : ISODate("2013-09-23T09:45:36.391Z") }

{ "_id" : 2, "name" : "윤도원-9", "phone" : "1004", "address" : "목동", "sex" : "사람", "etc" : "모비콘 블로깅", "createdAt" : ISODate("2013-09-23T09:47:29.547Z"), "updatedAt" : ISODate("2013-09-23T09:47:29.547Z") }

>

 

 

5. Sails를 통항 WebApp 만들기

  - SailsCast Site 참조

  - SailsCast GitHub 소스

  - 유튜브 동영상 25 강좌 보기


 

<참조>

  - Sails.js + AngularJS + MongoDB 데모

  - MongoDB auto increment Sequence Field 방법

  - Mongoose Sequence Table 통한 auto increment plugin  : express 와 mongoose 사용시 해당 plugin을 사용

posted by 윤영식
2013. 9. 14. 12:18 Languages/JavaScript

Mediator 패턴은 충돌을 해결하고 협력할 수 있도록 해주는 것이다. 즉 시스템에 많은 컴포넌트들이 있다면 서로의 커뮤니케이션을 중재하고 제어한다. 예로 공항 관제실을 생각해 보자. 비행기가 컴포넌트이고 이들의 이착륙을 관리하는 관제실이 Mediator가 된다. 




개념 이해하기 

  - Mediator

    Colleague 오븐젝트와 통신하기 위한 인터페이스를 정의한다 

  - ConcreteMediator

    Colleague 클래스를 알고 Colleague 오브젝트의 레퍼런스를 가지고 있는다. 

    Colleague 끼리 메세지를 전달하고 통신토록 해준다 

  - Colleague Classes

    Mediator 오브젝트에 레퍼런스를 유지한다. Mediator를 통하여 다른 Colleague와 통신한다 



PubSub 만들기 

  - Mediator Core

  - publish() 와 subscribe() 

var mediator = (function(){

    // Storage for topics that can be broadcast or listened to

    var topics = {};


    // Subscribe to a topic, supply a callback to be executed

    // when that topic is broadcast to

    var subscribe = function( topic, fn ){

        if ( !topics[topic] ){ 

          topics[topic] = [];

        }

        topics[topic].push( { context: this, callback: fn } );

        return this;

    };


    // Publish/broadcast an event to the rest of the application

    var publish = function( topic ){

        var args;

        if ( !topics[topic] ){

          return false;

        } 


        args = Array.prototype.slice.call( arguments, 1 );

        for ( var i = 0, l = topics[topic].length; i < l; i++ ) {

            var subscription = topics[topic][i];

            subscription.callback.apply( subscription.context, args );

        }

        return this;

    };


    return {

        publish: publish,

        subscribe: subscribe,

        installTo: function( obj ){

            obj.subscribe = subscribe;

            obj.publish = publish;

        }

    };


}());

  - Jack Lawson의 Mediator.js 를 가져다 쓰면 된다 

/*! * Mediator.js Library v0.9.5 * https://github.com/ajacksified/Mediator.js * * Copyright 2013, Jack Lawson * MIT Licensed (http://www.opensource.org/licenses/mit-license.php) * * For more information: http://thejacklawson.com/2011/06/mediators-for-modularized-asynchronous-programming-in-javascript/index.html * Project on GitHub: https://github.com/ajacksified/Mediator.js * * Last update: June 13 2013 */ (function(global, factory) { 'use strict';

// 다양한 환경에서 적용할 수 있도록 모듈에 대한 export if(typeof exports !== 'undefined') { // Node/CommonJS exports.Mediator = factory(); } else if(typeof define === 'function' && define.amd) { // AMD define('mediator-js', [], function() { global.Mediator = factory(); return global.Mediator(); }); } else { // Browser global global.Mediator = factory(); } }(this, function() { 'use strict'; // We'll generate guids for class instances for easy referencing later on. // Subscriber instances will have an id that can be refernced for quick // lookups.

// 글로벌 유니크 아이디를 생성하는 로직이다. 등록된 Colleague 오브젝트에 (즉, Subscriber Instances)

// 아이디를 부여해 준다 function guidGenerator() { var S4 = function() { return (((1+Math.random())*0x10000)|0).toString(16).substring(1); }; return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); } // Subscribers are instances of Mediator Channel registrations. We generate // an object instance so that it can be updated later on without having to // unregister and re-register. Subscribers are constructed with a function // to be called, options object, and context. // Colleague인 Subscriber 펑션 정의 function Subscriber(fn, options, context){ if(!(this instanceof Subscriber)) { return new Subscriber(fn, options, context); } this.id = guidGenerator(); this.fn = fn; this.options = options; this.context = context; this.channel = null; }

// Subscriber Prototype 객체 정의

// Subscriber에 등록한 fn, context, options를 변경할 수 있다 Subscriber.prototype = { // Mediator.update on a subscriber instance can update its function,context, // or options object. It takes in an object and looks for fn, context, or // options keys. update: function(options){ if(options){ this.fn = options.fn || this.fn; this.context = options.context || this.context; this.options = options.options || this.options; if(this.channel && this.options && this.options.priority !== undefined) { this.channel.setPriority(this.id, this.options.priority); } } } }; // 채널을 통하여 Subscriber 의 등록/제거 관리 

// Subscriber에 정보 publish function Channel(namespace, parent){ if(!(this instanceof Channel)) { return new Channel(namespace); } this.namespace = namespace || ""; this._subscribers = []; this._channels = []; this._parent = parent; this.stopped = false; } // A Mediator channel holds a list of sub-channels and subscribers to be fired // when Mediator.publish is called on the Mediator instance. It also contains // some methods to manipulate its lists of data; only setPriority and // StopPropagation are meant to be used. The other methods should be accessed // through the Mediator instance. // Mediator 채널이 sub-channel 목록을 가지고 있다 

// Channel Prototype 객체 정의 Channel.prototype = { addSubscriber: function(fn, options, context){ var subscriber = new Subscriber(fn, options, context); if(options && options.priority !== undefined){ // Cheap hack to either parse as an int or turn it into 0. Runs faster // in many browsers than parseInt with the benefit that it won't // return a NaN. options.priority = options.priority >> 0; if(options.priority < 0){ options.priority = 0; } if(options.priority >= this._subscribers.length){ options.priority = this._subscribers.length-1; } this._subscribers.splice(options.priority, 0, subscriber); }else{ this._subscribers.push(subscriber); } subscriber.channel = this; return subscriber; }, // The channel instance is passed as an argument to the mediator subscriber, // and further subscriber propagation can be called with // channel.StopPropagation().

// Mediator에 등록한 Subscriber에 아규먼트 전달 멈춤 stopPropagation: function(){ this.stopped = true; },


getSubscriber: function(identifier){ var x = 0, y = this._subscribers.length; for(x, y; x < y; x++){ if(this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier){ return this._subscribers[x]; } } }, // Channel.setPriority is useful in updating the order in which Subscribers // are called, and takes an identifier (subscriber id or named function) and // an array index. It will not search recursively through subchannels. // subchannel을 돌면서 우선순위를 업데이트 한다 setPriority: function(identifier, priority){ var oldIndex = 0, x = 0, sub, firstHalf, lastHalf, y; for(x = 0, y = this._subscribers.length; x < y; x++){ if(this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier){ break; } oldIndex ++; } sub = this._subscribers[oldIndex]; firstHalf = this._subscribers.slice(0, oldIndex); lastHalf = this._subscribers.slice(oldIndex+1); this._subscribers = firstHalf.concat(lastHalf); this._subscribers.splice(priority, 0, sub); },

// sub channel 등록/삭제 addChannel: function(channel){ this._channels[channel] = new Channel((this.namespace ? this.namespace + ':' : '') + channel, this); }, hasChannel: function(channel){ return this._channels.hasOwnProperty(channel); }, returnChannel: function(channel){ return this._channels[channel]; }, removeSubscriber: function(identifier){ var x = this._subscribers.length - 1; // If we don't pass in an id, we're clearing all if(!identifier){ this._subscribers = []; return; } // Going backwards makes splicing a whole lot easier. for(x; x >= 0; x--) { if(this._subscribers[x].fn === identifier || this._subscribers[x].id === identifier){ this._subscribers[x].channel = null; this._subscribers.splice(x,1); } } }, // This will publish arbitrary arguments to a subscriber and then to parent // channels. // 퍼블리싱 : subscriber에 아규먼트 전달 publish: function(data){ var x = 0, y = this._subscribers.length, called = false, subscriber, l, subsBefore,subsAfter; // Priority is preserved in the _subscribers index. for(x, y; x < y; x++) { if(!this.stopped){ subscriber = this._subscribers[x]; if(subscriber.options !== undefined && typeof subscriber.options.predicate === "function"){ if(subscriber.options.predicate.apply(subscriber.context, data)){ subscriber.fn.apply(subscriber.context, data); called = true; } }else{ subsBefore = this._subscribers.length; subscriber.fn.apply(subscriber.context, data); subsAfter = this._subscribers.length; y = subsAfter; if (subsAfter === subsBefore - 1){ x--; } called = true; } } if(called && subscriber.options && subscriber.options !== undefined){ subscriber.options.calls--; if(subscriber.options.calls < 1){ this.removeSubscriber(subscriber.id); y--; x--; } } } if(this._parent){ this._parent.publish(data); } this.stopped = false; } };

// Mediator 펑션 function Mediator() { if(!(this instanceof Mediator)) { return new Mediator(); } this._channels = new Channel(''); } // A Mediator instance is the interface through which events are registered // and removed from publish channels. Mediator.prototype = { // Returns a channel instance based on namespace, for example // application:chat:message:received // sub channel은 namespace 기반으로 만들어 진다 getChannel: function(namespace){ var channel = this._channels, namespaceHierarchy = namespace.split(':'), x = 0, y = namespaceHierarchy.length; if(namespace === ''){ return channel; } if(namespaceHierarchy.length > 0){ for(x, y; x < y; x++){ if(!channel.hasChannel(namespaceHierarchy[x])){ channel.addChannel(namespaceHierarchy[x]); } channel = channel.returnChannel(namespaceHierarchy[x]); } } return channel; }, // Pass in a channel namespace, function to be called, options, and context // to call the function in to Subscribe. It will create a channel if one // does not exist. Options can include a predicate to determine if it // should be called (based on the data published to it) and a priority // index. // sub channel에 namespace 기반으로 Subscriber 등록하기 subscribe: function(channelName, fn, options, context){ var channel = this.getChannel(channelName); options = options || {}; context = context || {}; return channel.addSubscriber(fn, options, context); }, // Pass in a channel namespace, function to be called, options, and context // to call the function in to Subscribe. It will create a channel if one // does not exist. Options can include a predicate to determine if it // should be called (based on the data published to it) and a priority // index. // 한번 호출하고 끝내기 once: function(channelName, fn, options, context){ options = options || {}; options.calls = 1; return this.subscribe(channelName, fn, options, context); }, // Returns a subscriber for a given subscriber id / named function and // channel namespace // 아이디와 명칭으로 Subscriber 얻어오기 (identifier === GUID) getSubscriber: function(identifier, channel){ return this.getChannel(channel || "").getSubscriber(identifier); }, // Remove a subscriber from a given channel namespace recursively based on // a passed-in subscriber id or named function. remove: function(channelName, identifier){ this.getChannel(channelName).removeSubscriber(identifier); }, // Publishes arbitrary data to a given channel namespace. Channels are // called recursively downwards; a post to application:chat will post to // application:chat:receive and application:chat:derp:test:beta:bananas. // Called using Mediator.publish("application:chat", [ args ]); // 퍼블리싱 publish: function(channelName){ var args = Array.prototype.slice.call(arguments, 1), channel = this.getChannel(channelName); args.push(channel); this.getChannel(channelName).publish(args); } };

// 일반적으로 사용하는 이름으로도 Aliasing // Alias some common names for easy interop Mediator.prototype.on = Mediator.prototype.subscribe; Mediator.prototype.bind = Mediator.prototype.subscribe; Mediator.prototype.emit = Mediator.prototype.publish; Mediator.prototype.trigger = Mediator.prototype.publish; Mediator.prototype.off = Mediator.prototype.remove; // Finally, expose it all. Mediator.Channel = Channel; Mediator.Subscriber = Subscriber; Mediator.version = "0.9.5"; return Mediator; })); 



사용해 보기 

  - Plunker에서 수행한 내역 보기 : http://plnkr.co/edit/k9dT9Ms7xzbx77GhVraA?p=preview

  - HTML

<!DOCTYPE html>

<html>


  <head>

    <script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>

    <link data-require="bootstrap-css@*" data-semver="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />

    <link data-require="bootstrap@*" data-semver="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />

    <script data-require="bootstrap@*" data-semver="3.0.0" src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>

    <link rel="stylesheet" href="style.css" />

    <script src="mediator.js"></script>

    <script src="script.js"></script>

  </head>


  <body>

    <h1>Chat</h1>

    <form id="chatForm">

      <label for="fromBox">Your Name:</label>

      <input id="fromBox" type="text" />

      <br />

      <label for="toBox">Send to:</label>

      <input id="toBox" type="text" />

      <br />

      <label for="chatBox">Message:</label>

      <input id="chatBox" type="text" />

      <button type="submit">Chat</button>

    </form>

    <div id="chatResult"></div>

  </body>


</html>


  - JavaScript

// create Mediator.js

var mediator = new Mediator();


$("#chatForm").submit(function(){

    // Collect the details of the chat from our UI

    var text = $( "#chatBox" ).val(),

        from = $( "#fromBox" ).val(),

        to = $( "#toBox" ).val();

        

    // newMessage 채널로 퍼블리싱 하기 

    // Publish data from the chat to the newMessage topic

    mediator.publish( "newMessage" , { message: text, from: from, to: to } );

    return false;

});


// Append new messages as they come through

function displayChat( data ) {

    var date = new Date(),

        msg = data.from + " said \"" + data.message + "\" to " + data.to;

  

    $( "#chatResult" ).prepend("<p>" + msg + " (" + date.toLocaleTimeString() + ")</p>");

}


// Log messages

function logChat( data ) {

    if ( window.console ) {

        console.log( data );

    }

}


// newMessage 명칭으로 Subscriber 등록하기 

// Subscribe to new chat messages being submitted

// via the mediator

mediator.subscribe( "newMessage", displayChat );

mediator.subscribe( "newMessage", logChat );


  - 수행내역



<참조>

  - 원문 : Mediator Pattern

  - OODesign : Mediator Pattern

  - http://thejacklawson.com/Mediator.js/

  - 오늘의 명언 : 내가 감동하면 남도 감동할 수 있다. 그것이 SNS정신이다 

posted by 윤영식
2013. 9. 13. 21:19 MongoDB/Prototyping

MongoDB에 구글의 도서검색 내역을 넣고, 여기서 도서의 Description을 하둡으로 분석하여 추천도서를 만들어 보자 



구글도서에서 Description을 MongoDB에 저장하기 

  - 이클립스에서 Maven Project를 하나 생성하고, pom.xml 을 다음과 같이 구성한다 

    자바에서 몽고디비를 사용하기 위한 드라이버와 구글 검색결과(JSON)을 파싱하기위한 JSON라이브러리를 추가한다 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mobiconsoft</groupId>

  <artifactId>booksearch</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

  <dependency>

  <groupId>org.mongodb</groupId>

  <artifactId>mongo-java-driver</artifactId>

  <version>2.11.2</version>

  </dependency>


  <dependency>

  <groupId>junit</groupId>

  <artifactId>junit</artifactId>

  <version>4.10</version>

  </dependency>

 

  <dependency>

  <groupId>org.json</groupId>

  <artifactId>json</artifactId>

  <version>20090211</version>

  </dependency>

  </dependencies>

</project>

  - BookSearcher.java 코딩 (편의상 import 구문 제외)

public class BookSearcher {


  // 도서검색 

  public String searchBooks(String keyword) {

    URL url = null;

    try {

      url = new URL("https://www.googleapis.com/books/v1/volumes?q=" + keyword);

    } catch (MalformedURLException e) {

      e.printStackTrace();

    }

    

    StringBuffer sb = new StringBuffer();

    String line;

    try {

      URLConnection urlConn = url.openConnection();

      BufferedReader br = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), "utf-8"));

      while((line = br.readLine()) != null) sb.append(line);

    } catch(IOException e) {

      e.printStackTrace();

    }

    

    return sb.toString();

  }

  

  // 도서 검색 결과에서  items찾아와 저장한다 

  public void saveBooks(String books) {

    Mongo mongo = null;

    try{

      mongo = new MongoClient("localhost", 27017);

    } catch(Exception e) {

      e.printStackTrace();

      throw new RuntimeException();

    }

    

   // 몽고디비 db는 books-db 이고 컬렉션은 books 로 만들어짐 

    mongo.setWriteConcern(new WriteConcern(1, 2000));

    DB bookDB = mongo.getDB("books-db");

    DBCollection bookColl = bookDB.getCollection("books");

    

    try{

      JSONObject json = new JSONObject(books);

      JSONArray items = json.getJSONArray("items");

      for( int i=0; i<items.length(); i++) {

        DBObject doc = new BasicDBObject();

        // search-book key로 value가 들어간다

        doc.put("search-book", (DBObject)JSON.parse(items.getJSONObject(i).toString()));

        bookColl.save(doc);

      }

    } catch(JSONException e) {

      e.printStackTrace();

    }

  }

}

  - 테스트 해보자 

    JUnit 테스트전에 mongodb를 기동한다 

// 몽고디비 

$ ../bin/mongod -dbpath=/Users/dowon/Documents/mongodb/database


// 테스트 

public class BookSearcherTest {

  

    private BookSearcher bookSearcher;

    

    @Before

    public void setUp() throws Exception {

      this.bookSearcher = new BookSearcher();

    }


    // search 결과 보기 

    @Test

    public void testSearchBooks() throws Exception {

      String result = this.bookSearcher.searchBooks("nosql");

      //assertNotNull(result);

      System.out.println(result);

    }

    

    // 데이터 저장하기 

    @Test

    public void testSaveBooks() throws Exception {

      String result = this.bookSearcher.searchBooks("nosql");

      this.bookSearcher.saveBooks(result);

    }

}


// 테스트 성공후 mongo 쉘을 통하여 확인 

> use books-db

switched to db books-db

> show collections

books

system.indexes

> db.books.find().length();

10

> db.books.find()

{ "_id" : ObjectId("5232e69cda06561b2e11306c"), "search-book" : { "saleInfo" : { "saleability" : "NOT_FOR_SALE", "isEbook" : false, "country" : "KR" }, "id" : "tv5iO9MnObUC", "searchInfo" : { "textSnippet" : "They provide examples, practical solutions, and expert education in new technologies, all designed to help programmers do a better job. wrox.com Programmer Forums Join our Programmer to Programmer forums to ask and answer programming ..." }, "etag" : "HX8hesQgrJM", "volumeInfo" : { "pageCount" : 408, "averageRating" : 3, "infoLink" : "http://books.google.co.kr/books?id=tv5iO9MnObUC&dq=nosql&hl=&source=gbs_api", "printType" : "BOOK", "publisher" : "John Wiley & Sons", "authors" : [  "Shashank Tiwari" ], "canonicalVolumeLink" : "http://books.google.co.kr/books/about/Professional_NoSQL.html?hl=&id=tv5iO9MnObUC", "title" : "Professional NoSQL", "previewLink ... 중략 ...



MongoDB Hadoop Connector 사용하기  

  - 몽고디비와 하둡을 연결하는 방법을 제공한다 

     https://github.com/mongodb/mongo-hadoop 에서 1.1.x 의 Core 다운로드 한다 (mongo-hadoop-core_1.1.2-1.1.0.jar)

  - Input-Output으로 몽고디비를 사용할 경우   

    

 - 분석을 위하여 Pig, MR을 할 경우

    

  - ETL처럼 처리후 별도의 저장소로 던져질 경우

    ETL from MongoDB

      

    ETL to MongoDB

        


  - Eclipse에 새로운 Book Search Mapper와 Reducer 프로젝트를 만들고 pom.xml 을 만든다 

    mongo-hadoop-core  파일을 maven에 등록되어 있지 않기때문에 수동으로 .m2/repository에 만들어 주어야 한다 

    예)

    > 레파지토리 : /Users/dowon/.m2/repository

    > 파일위치 : mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mobiconsoft</groupId>

  <artifactId>booksearch_mapreduce</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

    <dependency>

      <groupId>org.apache.hadoop</groupId>

      <artifactId>hadoop-core</artifactId>

      <version>1.1.2</version>

    </dependency>

    

    <dependency>

      <groupId>org.mongodb</groupId>

      <artifactId>mongo-java-driver</artifactId>

      <version>2.11.2</version>

    </dependency>

    

    <!-- 수동 설정 --> 

    <dependency>

      <groupId>mongo-hadoop-core</groupId>

      <artifactId>mongo-hadoop-core_1.1.2</artifactId>

      <version>1.1.0</version>

    </dependency>

  </dependencies>

  

  <build>

    <plugins>

      <plugin>

        <artifactId>maven-antrun-plugin</artifactId>

        <configuration>

          <tasks>

            <copy file="target/${project.artifactId}-${project.version}.jar"

              tofile="/Users/dowon/Documents/input/${project.artifactId}-${project.version}.jar" />

          </tasks>

        </configuration>

        <executions>

          <execution>

            <phase>install</phase>

            <goals>

              <goal>run</goal>

            </goals>

          </execution>

        </executions>

      </plugin>

    </plugins>

  </build>

  

</project>

  - Mapper와 Reducer 클래스를 코딩 

// Mapper

public class BookSearchMapper extends Mapper<Object, BSONObject, Text, IntWritable> {

  

  private final static IntWritable ONE = new IntWritable();

  private Text word = new Text();

  

  protected void map(Object key, BSONObject value, Context context) 

    throws IOException, InterruptedException {

    BasicDBObject anItem = (BasicDBObject)value.get("search-book");

    BasicDBObject volumeInfo = (BasicDBObject)anItem.get("volumeInfo");

    String description = volumeInfo.getString("description");

    if(description == null || description.trim().length() <= 0) return;

    

    StringTokenizer st = new StringTokenizer(description);

    while(st.hasMoreTokens()) {

      word.set(st.nextToken());

      context.write(word, ONE);

    }

  }

}


// Reducer

public class BookSearcherReducer extends

  Reducer<Text, IntWritable, Text, IntWritable> {

  

  protected void reduce(Text key, Iterable<IntWritable> values, Context context) 

    throws IOException, InterruptedException {

    int sum = 0;

    for(final IntWritable value : values) sum += value.get();

    context.write(key, new IntWritable(sum));

  }

}

  - Job을 만든다

public class MongoJob extends MongoTool {

  static {

    Configuration.addDefaultResource("mongo-default.xml");

    Configuration.addDefaultResource("mongo-book.xml");

  }

  

  public static void main(String[] args) throws Exception {

    Configuration conf = new Configuration();

    

    JobHelper.addJarForJob(conf, "/Users/dowon/.m2/repository/mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar:"

          + "/Users/dowon/.m2/repository/org/mongodb/mongo-java-driver/2.11.2/mongo-java-driver-2.11.2.jar");

    

    System.exit(ToolRunner.run(conf, new MongoJob(), args));

  }

}

  - 리소스 xml을 만든다 

    https://github.com/mongodb/mongo-hadoop/blob/master/examples/treasury_yield/src/main/resources/mongo-defaults.xml 

   에서 xml 정보를 copy 하여 mongo-book.xml 을 만든 후 하기 내용을 수정하여 입력해야 한다

<property>

    <!-- Class for the mapper -->

    <name>mongo.job.mapper</name>

    <value>booksearch_mapreduce.BookSearchMapper</value>

  </property>

  <property>

    <!-- Reducer class -->

    <name>mongo.job.reducer</name>

    <value>booksearch_mapreduce.BookSearcherReducer</value>

  </property>

  <property>

    <!-- InputFormat Class -->

    <name>mongo.job.input.format</name>

    <value>com.mongodb.hadoop.MongoInputFormat</value>

  </property>

  <property>

    <!-- OutputFormat Class -->

    <name>mongo.job.output.format</name>

    <value>com.mongodb.hadoop.MongoOutputFormat</value>

  </property>

  <property>

    <!-- Output key class for the output format -->

    <name>mongo.job.output.key</name>

    <value>org.apache.hadoop.io.Text</value>

  </property>

  <property>

    <!-- Output value class for the output format -->

    <name>mongo.job.output.value</name>

    <value>com.mongodb.hadoop.io.BSONWritable</value>

  </property>

  <property>

    <!-- Output key class for the mapper [optional] -->

    <name>mongo.job.mapper.output.key</name>

    <value>org.apache.hadoop.io.Text</value>

  </property>

  <property>

    <!-- Output value class for the mapper [optional] -->

    <name>mongo.job.mapper.output.value</name>

    <value>org.apache.hadoop.io.IntWritable</value>

  </property>

  <property>

    <!-- Class for the combiner [optional] -->

    <name>mongo.job.combiner</name>

    <value>booksearch_mapreduce.BookSearcherReducer</value>

  </property>

  - "Mave Build..." clean install 하여 .jar  파일을 만든다 (참조에 첨부파일)

 - 다음 하둡 runtime(start-all.sh) 을 수행한다 

  - 하둡 수행 쉘을 만든다 

//////////////////////

// mongodb.sh 내역 

#!/bin/sh


export REPO=/Users/dowon/.m2/repository

export MONGO_DRIVER=$REPO/org/mongodb/mongo-java-driver/2.11.2/mongo-java-driver-2.11.2.jar

export MONGO_HADOOP=$REPO/mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar

export HADOOP_CLASSPATH=$MONGO_DRIVER:$MONGO_HADOOP

export HADOOP_USER_CLASSPATH_FIRST=true


hadoop jar booksearch_mapreduce-0.0.1-SNAPSHOT.jar booksearch_mapreduce.MongoJob



/////////////

/// 수행하기 

$ mongodb.sh

2013-09-13 20:53:24.630 java[1431:1203] Unable to load realm info from SCDynamicStore

13/09/13 20:53:24 INFO util.MongoTool: Created a conf: 'Configuration: core-default.xml, core-site.xml, mongo-default.xml, mongo-book.xml, mapred-default.xml, mapred-site.xml' on {class booksearch_mapreduce.MongoJob} as job named '<unnamed MongoTool job>'

13/09/13 20:53:24 INFO util.MongoTool: Mapper Class: class booksearch_mapreduce.BookSearchMapper

13/09/13 20:53:24 INFO util.MongoTool: Setting up and running MapReduce job in foreground, will wait for results.  {Verbose? false}

13/09/13 20:53:25 INFO util.MongoSplitter: MongoSplitter calculating splits

13/09/13 20:53:25 INFO util.MongoSplitter: use range queries: false

.. 중략 ...

13/09/13 20:53:41 INFO mapred.JobClient:     Spilled Records=1428

13/09/13 20:53:41 INFO mapred.JobClient:     Map output bytes=14049

13/09/13 20:53:41 INFO mapred.JobClient:     Total committed heap usage (bytes)=269619200

13/09/13 20:53:41 INFO mapred.JobClient:     Combine input records=1299

13/09/13 20:53:41 INFO mapred.JobClient:     SPLIT_RAW_BYTES=195

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce input records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce input groups=714

13/09/13 20:53:41 INFO mapred.JobClient:     Combine output records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce output records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Map output records=1299



MongoDB에서 결과값 확인하기 

  - 브라우져에서 결과값을 확인하고 싶다면 몽고디비에 옵션으로 --rest 를 주면 28017 포트로 RESTful 하게 호출할 수 있다 

 $ ./bin/mongod -dbpath=/Users/dowon/Documents/mongodb/database --rest 

  - 결과 화면 

    결과값은 out 컬렉션에 생성이 된다


<참조>

  - 검색 책 정보 

books.json


  - 이클립스 project workspace

booksearch.tar


  - 하둡기동후 수행하는 쉘 

mongodb.sh


  - 반출한 booksearch mapreducer jar 파일 

booksearch_mapreduce-0.0.1-SNAPSHOT.jar






posted by 윤영식
2013. 9. 12. 20:45 Big Data

몽고디비를 하둡의 Input/Output의 Store를 사용하면 어떨까? 어차피 몽고디비는 Document Store 이며 Scale-Out을 위한 무한한 Sharding(RDB의 Partitioning) 환경을 제공하니 충분히 사용할 수 있을 것이다. Store에 저장된 데이터의 Batch Processing Engine으로 하둡을 사용하면 될 일이다. 




Mongo-Hadoop Connector 소개

  - Hadoop을 통하면 Mongo안에 있는 데이터를 전체 코어를 사용하면서 병렬로 처리할 수 있다 

  - 하둡포멧으로 Mongo를 BSON format을 파일로 저장하거나, MongoDB에 바로 저장할 수 있는 Java API존재

  - Pig + Hive를 사용할 수 있음 

  - AWS의 Amazon Elastic MapReduce 사용



Batch Processing Model 종류
  - 사실 MongoDB에서도 Aggregation Framework을 제공하여 MapReduce프로그래밍을 JavaScript로 개발 적용할 수 있다다
  - 시간단위 Batch Processing은 요렇게도 사용할 수 있겠다 


  - 데이터가 정말 Big 이면 하둡을 이용하여 Batch Processing을 해야겠다. 여기서 몽고디비를 "Raw Data Store" 와 "Result Data Store"로 사용한다 


  - MongoDB & Hadoop : Batch Processing Model 전체 내역을 보자 



<참조>

  - 결국 처리된 데이터는 표현되어야 한다 : Data Visualization Resources

  - MongoDB 넌 뭐니? NoSQL에 대한 이야기 (조대협)

posted by 윤영식
2013. 9. 11. 19:20 Big Data

Mapper & Reducer를 .jar로 배포하고 직접 하둡명령으로 수행하는 방법에 대하여 알아보자 



MapReduce 프로그램 

  - Writable Interface는 Value에서 사용한다

  - Mapper 인터페이스 

    Mapper<K1, V1, K2, V2>의 형태 : key는 WritableComparable를 구현해야 하며, value는 Writable를 구현해야 함.


  - Reducer 인터페이스 

    reducer는 여러가지 매퍼로부터 생성된 결과를 받고, key/value 쌍의 key에 대해 데이터를 정렬하고 동일한 key에 대한 모든 값을 그룹핑 함.


  - 이전의 WordCount에 대한 것을 직접 코딩하였는데, 맵퍼-TokenCountMapper-, 리듀서-LongSumReducer-를 사용해서 동일하게 만들 수 있다



hadoop 명령어로 .jar 직접 수행하기

  - pom.xml 에 MapReduce Jar파일을 만들어 특정위치로 복사하는 플러그인 설정을 넣는다 

<build>

  <plugins>

    <plugin>

      <artifactId>maven-antrun-plugin</artifactId>

      <configuration>

        <tasks>

          <copy file="target/${project.artifactId}-${project.version}.jar"

            tofile="/Users/dowon/Documents/hadoop-jobs/${project.artifactId}-${project.version}.jar" />

        </tasks>

      </configuration>

      <executions>

        <execution>

          <phase>install</phase>

          <goals>

            <goal>run</goal>

          </goals>

        </execution>

      </executions>

    </plugin>

  </plugins>

</build>

  - 기존 WordCount에 대한 WordCount3 복사본을 만들고 TokenCountMapper와 LongSumReducer로 변형한다

    즉 직접 코딩하지 말고 하둡에서 제공하는 클래스를 사용한다 

import java.io.IOException;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapred.FileInputFormat;

import org.apache.hadoop.mapred.FileOutputFormat;

import org.apache.hadoop.mapred.JobClient;

import org.apache.hadoop.mapred.JobConf;

import org.apache.hadoop.mapred.lib.LongSumReducer;

import org.apache.hadoop.mapred.lib.TokenCountMapper;


public class WordCount3 {


  public static void main(String[] args) throws IOException {

    // 1. configuration Mapper & Reducer of Hadoop

    JobConf conf = new JobConf(WordCount3.class);

    conf.setJobName("wordcount3");

    

    // 2. final output key type & value type

    conf.setOutputKeyClass(Text.class);

    conf.setOutputValueClass(LongWritable.class);

    

    // 3. in/output format 

    conf.setMapperClass(TokenCountMapper.class);

    conf.setCombinerClass(LongSumReducer.class);

    conf.setReducerClass(LongSumReducer.class);

    

    // 4. set the path of file for read files

    //    input path : args[0]

    //    output path : args[1]

    FileInputFormat.setInputPaths(conf, new Path(args[0]));

    FileOutputFormat.setOutputPath(conf, new Path(args[1]));

    

    // 5. run job

    JobClient client = new JobClient();

    client.setConf(conf);

    JobClient.runJob(conf);

  }

}

  - eclipse의 프로젝트를 선택하고 "Run As"에서 "Maven build..."를 선택하여 "clean install" 입력하고 "run"버튼을 클릭한다 


  - 결과로 배포가 성공으로 나오면 된다  : /Users/dowon/Documents/hadoop-jobs 디렉토리에 *.jar 파일 생성을 확인한다 


  - 하둡 데몬들을 수행하기 전 NameNode에 대해서 format을 하고 수행한다 

// name node 포멧

hadoop namenode -format


// .bash_profile 에 PATH 설정

set -o vi

export JAVA_HOME=/Library/Java/Home

export H_HOME=~/Documents/hadoop-1.2.1

export PATH=.:$PATH:$JAVA_HOME/bin:$H_HOME/bin:/usr/bin

alias ll='ls -alrt'

alias cdh='cd $H_HOME'


// 하둡 데몬 수행

// 50030 : job-tracker 접속 포트

// 50070 : NameNode 접속 포트

$ start-all.sh 

  - input 의 위치를 지정하여 준다 (만일, NameNode를 포멧하였다면)

// 위치가 하기와 같다면 

$ pwd

/Users/dowon/Documents/input

$ ls

total 16

-rw-r--r--   1 dowon  staff   22  9 11 19:26 file01

-rw-r--r--   1 dowon  staff   21  9 11 19:26 file02


// input 을 HDFS에 만든다 

$ hadoop fs -put . input


1) http://localhost:50070/으로 접속하여 "Browser the filesystem"을 클릭하면 볼 수 있다 

2) /user/dowon/input 경로로 만들어 졌음을 알 수 있다 

  - hadoop 명령어로 생성된 jar 파일을 수행해 보자 

// 경로가 다음과 같고, WordCount3.class가 들어있는 .jar 파일이 존재한다 

$ pwd

/Users/dowon/Documents/hadoop-jobs

$ ls

-rw-r--r--   1 dowon  staff  5391  9 11 19:45 MapReduce-1.0.0-SNAPSHOT.jar


// 명령어 수행 

$ hadoop jar *.jar WordCount3 /user/dowon/input /user/dowon/output3


1) 네임노드에 접속해서 /user/dowon에 들어가 보면 "output3"이 생성된 것을 볼 수있다

2) output3으로 들어가면 결과값을 지니 파일이 존재한다 

3) 만일 명령을 재수행하고 싶다면 output3 디렉토리리 삭제해야 한다 

    $ hadoop fs -rmr /user/dowon/output3


eclipse에서 수행하지 않고 반출된 .jar 파일을 가지고 hadoop명령으로 수행하는 방법을 알아보았다. 


<참조>

  없음 


posted by 윤영식