현재 참여하고 있는 라이브 프로젝트는 상당히 오래된 프로젝트로
노드6.x를 사용하며 패키지 관리를 package.json으로 하지 않고,
node_modules를 통째로 git에서 관리하고 있었다.
개발에 제약이 커 노드 버전을 8.x로 올렸으나, 여전히 갈 길은 멀다.
다른 누군가 이런 상황을 겪을 일은 없겠지만,
node를 사용하며 proxy를 처음 사용 해 보기도 해서, 기록을 겸하는 목적으로 작은 코드 하나 공유한다.
global에 변수를 박아놓고 쓰는 건 상상도 못했는데 상용 서비스에서 쓰이고 있어 놀랐다.
const Redis = require('ioredis');
const instances = {};
process.on('SIGINT', () => {
logger.log('[Redis] close all redis connection');
Object.keys(instances).forEach(k => {
try {
let conn = instances[k];
if (conn)
conn.close();
} catch (e) {
logger.error(`[Redis] release connection error - `, e);
}
});
process.exit(0);
});
function createProxy(redis) {
return new Proxy(redis,
{
get(target, propKey, _receiver) {
const origMethod = target[propKey];
if (typeof origMethod === 'function') {
return function (...args) {
// 마지막 인자가 콜백 함수인지 확인
let callback = args[args.length - 1];
if (typeof callback === 'function') {
logger.debug(`[Redis] execute : ${propKey}`, args.slice(0, -1));
// 원래 콜백을 감싸는 새로운 콜백
const loggingCallback = (err, result) => {
if (err) {
logger.error(`[Redis] error : ${propKey}`, err);
} else {
logger.debug(`[Redis] result : ${propKey}`, result);
}
// 원래 콜백 호출
callback(err, result);
};
// 원래 콜백 대신 로깅 콜백 사용
args[args.length - 1] = loggingCallback;
} else {
// promise 로 호출된 경우 콜백이 없다.
// 이 커맨드 객체의 상세 정보를 모두 남기는 로그인 관계로 trace로 남긴다.
logger.trace(`[Redis] command details: ${propKey}`, args);
}
return origMethod.apply(target, args);
};
} else {
return origMethod;
}
},
});
}
const getClient = (dbNumber) => {
if (!instances[dbNumber]) {
let options = loadOptions();
let config = {
host: options.host,
port: options.port,
username: options.username,
password: options.password,
commandTimeout: options.timeout,
connectTimeout: 10000,
keepAlive: 10000,
db: dbNumber,
}
let redisProxy = createProxy(new Redis(config)); // 프록시를 적용하여 로깅 활성화
redisProxy.on('connect', () => {
logger.debug(`[Redis] client connected - ${JSON.stringify(config)}`);
});
redisProxy.on('error', (error) => {
logger.error('[Redis] error - ', error);
});
redisProxy.on('close', () => {
logger.debug('[Redis] connection closed');
});
instances[dbNumber] = redisProxy; // 프록시 인스턴스 저장
}
return instances[dbNumber];
}
function close() {
Object.keys(instances).forEach(k => {
if (instances[k])
instances[k].quit((r) => logger.debug(`close redis[${k}] connection - `, r));
})
}
function loadOptions() {
return {
host: global.CONFIG ? global.CONFIG.REDIS_STORE_HOST || '127.0.0.1' : '127.0.0.1',
port: global.CONFIG ? global.CONFIG.REDIS_STORE_PORT || 6379 : 6379,
username: global.CONFIG ? global.CONFIG.REDIS_STORE_USERNAME || null : null,
password: global.CONFIG ? global.CONFIG.REDIS_STORE_PASSWORD || null : null,
timeout: global.CONFIG ? global.CONFIG.REDIS_STORE_TIMEOUT || 3000 : 3000
};
}
module.exports = {
getClient: getClient,
close: close
}
const _ = require('underscore');
const ioredis = require('ioredis');
const redis = require('./redis.pool');
class Client {
constructor(dbNumber) {
/** @type {ioredis.RedisCommander} */
this.client = redis.getClient(dbNumber || 0);
}
close() {
if (this.client) {
this.client.close();
}
}
/**
*
* @param {(err:object, instance: ioredis.RedisCommander)} callback
* @returns
*/
getInstance(callback) {
const CODE = require('../common').CODE;
if (!this.client) {
return callback({ code: CODE.REDIS_ERROR, message: `[Redis] invalid connection` }, null);
} else {
return callback(null, this.client);
}
}
command (...args) {
const command = args[0];
const callback = args[args.length - 1];
if (typeof command != 'string' || typeof callback != 'function') {
throw new Error(`[Redis] invalid usage - ${JSON.stringify(args)}`);
} else if (!this.client) {
const CODE = require('../common').CODE;
return callback({ code: CODE.REDIS_ERROR, message: `[Redis] invalid connection` }, null);
} else {
return this.client[command].apply(this, args.slice(1));
}
}
zpopmin (key, count = 1, callback) {
this.getInstance((err, instance) => {
if (err) return callback(err, null);
instance.zpopmin(key, count, (err, reply) => {
return callback(err, reply && reply.length > 0 ? _.object(_.chunk(reply, 2).map(p => [p[0], +p[1]])) : {});
});
});
}
}
module.exports = Client