node js와 redis를 사용한 구조화된 데이터
DESCRIPTION
Node Js와 Redis를 사용한 구조화된 데이터TRANSCRIPT
제대로 배우는 Node Js 9 장 Node 와 Redis 를 사용한 구조화된 데이터
박진호
목차
Node 및 Redis 시작 게임 순위표 만들기 메시지 큐 만들기 Express 애플리케이션에 Stats 미들웨어
추가
Redis, Memcached, Cassan-dra
메모리 내의 키 / 값 저장소로 유명 Node 는 Redis, Memcached, Cassan-
dra 모두 지원
Memcached
빠른 액세스를 위해 데이터 쿼리를 메모리 상에 캐시하기 위해 사용
분산 컴퓨팅에도 적합 복잡한 데이터에 대해서는 제한된 지원만을
제공 대량의 쿼리를 처리하는 애플리케이션에는
유용하지만 , 데이터를 읽고 쓰는 것이 많은 애플리케이션에서는 유용성이 떨어짐
http://memcached.org/
Cassandra
MemCached 처럼 클러스터 지원 MemCached 와 마찬가지로 지원하는
데이터 구조가 제한 Redis 에 적합하지 않은 임시 쿼리를
처리하는데 알맞음 http://cassandra.apache.org/
Redis
데이터를 읽고 쓰는 것이 많은 곳에 사용 영속적으로 저장 가능 다양한 유형의 데이터를 지원 .
(Memcached 에 비해 더 많은 유연성 제공 )
Cassandra 에 비해 빠름 .
단일 머신에서만 구동 . Http://redis.io/
Redis 설치
https://github.com/dmajkic/redis/downloads
Redis 클라이언트 접속
게임 순위표 만들기
데이터베이스를 이용하면 데이터를 영속적으로 관리할 수 있지만 , 입출력에 다소 시간이 걸리기 때문에 실시간 서비스에서는 더 적합한 저장소를 사용할 필요
Redis 는 메모리 기반의 저장소이기 때문에 필요한 정보를 빠르게 저장하고 가져올 수 있는 실시간 서비스에 적합한 저장소
멤버 id, 플레이어 이름 , 게임이름 , 마지막 플레이한 날짜 , 점수 , 나머지 관련 정보 저장 .
https://github.com/dmajkic/redis/downloads
게임 순위표 만들기 – 기본 모듈
Redis 를 사용하여 구현 . npm install redis
hiredis 라는 공식적인 hiredis C 라이브러리를 바인딩하여 Non-Blocking 의 빠른 모듈 npm install hiredis redis
Async 설치 (series 기능 사용하기 위함 . 호출이 순서대로 호출되고 데이터 역시 순서대로 반환 보장 ) npm install async
게임 순위표 만들기 – 기본 모듈 Redis 모듈 포함
Var redis = require(‘redis’);
Redis 클라이언트 생성 Var client = redis.createClient(); 3 개의 선택적인 파라미터▪ Port : 6379▪ Host : 127.0.0.1▪ 옵션 : parser, return_buffers, detect_buffers, socket_nodelay,
no_ready_check 연결 종료
Client.quit(); 강제 종료
Client.end();
게임 순위표 만들기 – Redis 모듈
Redis 모듈 포함 Var redis = require(‘redis’);
Redis 클라이언트 생성 Var client = redis.createClient(); 3 개의 선택적인 파라미터▪ Port : 6379▪ Host : 127.0.0.1▪ 옵션 : parser, return_buffers, detect_buffers, socket_nodelay,
no_ready_check 연결 종료
Client.quit(); 강제 종료
Client.end();
게임 순위표 만들기 – Redis 동작
Redis 해시 속성 설정Client.hset(“hashid”, “propname”, “propvalue”, function(err, reply){
오류 혹은 응답에 대해 무엇인가를 수행} );
성공 확인 응답 Rredis.print▪ 에러나 응답을 콘솔에 출력 후 반환
Client.hset (“hashid”, “propname”, “propvalue”, redis.print);
게임 순위표 만들기
게임 순위표 만들기 – 서버var net = require('net');var redis = require('redis');
var server = net.createServer(function(conn) { console.log('connected');
// create Redis client var client = redis.createClient();
client.on('error', function(err) { console.log('Error ' + err); });
// fifth database is game score database
client.select(5);
conn.on('data', function(data) { console.log(data + ' from ' + conn.re-moteAddress + ' ' + conn.remotePort); try { var obj = JSON.parse(data);
// add or overwrite score client.hset(obj.member, "first_name", obj.first_name, redis.print); client.hset(obj.member, "last_name", obj.last_name, redis.print); client.hset(obj.member, "score", obj.s-core, redis.print); client.hset(obj.member, "date", obj.-date, redis.print);
// add to scores for Zowie! client.zadd("Zowie!", parseInt(obj.score), obj.member); } catch(err) { console.log(err); } }); conn.on('close', function() { console.log('client closed connection'); client.quit(); });
}).listen(8124);
console.log('listening on port 8124');
게임 순위표 만들기 – 클라이언트 var net = require('net');
var client = new net.Socket();client.setEncoding('utf8');
// connect to TCP serverclient.connect ('8124','examples.burningbird.net', function () { console.log('connected to server');});
// prepare for input from terminalprocess.stdin.resume();
// when receive data, send to serverprocess.stdin.on('data', function (data) { client.write(data);});
// when receive data back, print to consoleclient.on('data',function(data) { console.log(data);});
// when server closedclient.on('close',function() { console.log('connection is closed');});
게임 순위표 만들기 – Redis 동작
두개의 다른 데이터 저장소가 업데이트 됨 .
개별적인 점수 정보 ( 이름 , 점수 , 날짜를 포함 ) 은 hash 에 저장 Client.hset ( obj.member, “first_name”,
obj.first_name, redis.print );
멤버 id 와 점수는 sorted set 에 저장 Client.zadd( “Zowie!”, parseInt(obj.score),
obj.member );
게임 순위표 만들기 – score.jade
Score.jade Doctype 5 doctype html
게임 순위표 만들기 – 상위득점 서버
게임 순위표 만들기 – Score.jade
doctype htmlhtml(lang="en") head title Zowie! Top Scores meta(charset="utf-8") | <style type="text/css"> include main.css | </style> body table caption Zowie! Top Scorers! tr th Score th Name th Date if scores.length each score in scores if score tr td #{score.score} td #{score.first_name} #{score.last_name} td #{score.date}
게임 순위표 만들기 – main.css
body { margin: 50px;}table { width: 90%; border-collapse: collapse;}table,td,th,caption { border: 1px solid #000;}td { padding: 20px;}
caption { font-size: larger; background-color: #ff0; padding: 10px;}h1 { font: 1.5em Georgia, serif;}ul { list-style-type: none;}form { margin: 20px; padding: 20px;}
게임 순위표 만들기 – topScore-Server9-3 var http = require('http');
var async = require('async');var redis = require('redis');var jade = require('jade');
// set up Jade templatevar layout = require('fs').readFileSync(__dirname + '/score.jade', 'utf8');var fn = jade.compile(layout, {file-name: __dirname + '/score.jade'});
// start Redis clientvar client = redis.createClient();
// select fifth databaseclient.select(5);
// helper functionfunction makeCallbackFunc(member) { return function(callback) { client.hgetall(member, function(err, obj) { callback(err,obj); }); };}
http.createServer(function(req,res) {
// first filter out icon request if (req.url === '/favicon.ico') { res.writeHead(200, {'Content-Type': 'image/x-icon'} ); res.end(); return; }
// get scores, reverse order, top five only client.zrevrange('Zowie!',0,4, function(err,result) { var scores; if (err) { console.log(err); res.end('Top scores not currently available, please check back'); return; }
게임 순위표 만들기 – topScore-Server9-3 // create array of callback func-
tions for Async.series call var callFunctions = new Ar-ray();
// process results with make-CallbackFunc, push newly re-turned // callback into array for (var i = 0; i < re-sult.length; i++) { callFunctions.push(makeCallbackFunc(result[i])); }
// using Async series to process each callback in turn and return // end result as array of objects async.series( callFunctions, function (err, result) { if (err) { console.log(err); res.end('Scores not available'); return; }
// pass object array to template en-gine var str = fn({scores : result}); res.end(str); }); });}).listen(3000);
console.log('Server running on 3000/');
메시지 큐 만들기
메시지 큐 특정한 통신 형식을 입력으로 받아 큐에 저장하는
애플리케이션
메시지는 메시지 수신자가 가져갈 때 까지 저장되었다가 해당 시점에 큐에서 뽑혀져서 수신자에게 전송 ( 한번에 하나씩 혹은 대량 ))
통신은 비동기로 이루어짐
메시지 큐 만들기
메시지 큐를 보여주기 위해 여러 개의 다양한 하위 도메인에 대한 웹 로그 파일에 접근하는 애플리케이션
메시지 큐 애플리케이션이 수행하는 것은 3000번에서 메시지를 수신대기하다가 전송된 항목을 Re-dis 데이터 저장소로 저장
로그 항목을 받은 후 로그 데이터에서 이미지 리소스(jpg, gif 등 ) 가 접근되었는지를 찾아보는 정규식 검사를 수행 . 일치하는 패턴이 발견되면 리소스 URL을 메시지 큐 애플리케이션에 전송
Redis 클라이언트 생성 시점 Redis 클라이언트를 만들어서 애플리케이션이 종료될 때 까지
지속 ? Redis 클라이언트를 만든 후 Redis 명령어를 실행하자 마자
바로 해제 ?
영구적이 좋은가 ? 즉시 해제하는 것이 좋은가 ? 클라이언트 연결유지가 더 빠를것이라는 예상 맞음 . 연결을 유지하는
경우를 테스트 하는 도중 애플리케이션이 잠시 동안 상당히 느려졌다가 상대적으로 빠른 속도를 회복
Redis 데이터베이스에 대해 대기 중인 요청들이 큐가 해제도리 때 까지 Node 애플리케이션을 일시적으로 차단시켰기 때문 . 매번 요청할 때마다 연결을 열고 닫는 경우에는 동일한 상황을 겪지 않았는데 , 열고 닫는 과정에 들어가는 추가 오버헤드가 애플리케이션의 성능을 저하시켜서 동시 사용자 상한선에 도달하지 않았기 때문
Express 애플리케이션에 Stats 미들웨어 추가 이전 장들에서 만들어본 위젯 애플리케이션에
통계를 추가하기 위해 Redis 를 사용
통계는 위젯 애플리케이션의 페이지에 접근하는 모든 ip 주소들의 집합과 각 리소스가 접근된 횟수라는 두개로 제한
두 개의 분리된 데이터 컬렉션을 한번에 가져오기 위해 redis 트랜잭션을 제어하는 multi 사용
Express 애플리케이션에 Stats 미들웨어 추가 1. Redis 데이터 베이스에 접근 정보를
기록하기 위한 새로운 미들웨어 추가 Ip 주소를 추가▪ Client.sadd(‘ip’, req.socket.remoteAddress);
리소스 카운트 증가▪ Client.hincrby(‘myurls’, req.url, 1);
Express 애플리케이션에 Stats 미들웨어 추가 2. routes/index.js
통계 애플리케이션에서 새로운 컨트롤러 코드를 가진 라우팅 색인 파일 통계 인터페이스는 최상위 도메인에서 접근되므로 routes 폴더에 추가 .
Express 4에서 에러나는 부분들… . 좀 더 찾아봐야… app.use(express.favicon()); app.use(express.logger('dev'));
app.use(express.staticCache({maxObjects: 100, maxLength: 512}));
app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.directory(__dirname + '/public')); app.use(function(req, res, next){ throw new Error(req.url + ' not
found'); }); app.use(function(err, req, res, next) { console.log(err); res.send(err.message); });
감사합니다