@63pokupki/nodejs-common
Version:
Common nodejs functionality
493 lines (491 loc) • 19.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisSys = void 0;
const ioredis_1 = __importDefault(require("ioredis"));
const knex_1 = require("knex");
const lodash_1 = __importDefault(require("lodash"));
const uuid_1 = require("uuid");
const ip_1 = __importDefault(require("ip"));
/** Обертка над редисом которая понимает async/await */
class RedisSys {
/** init */
constructor(param) {
this.env = 'prod';
this.ixRedisDb = {};
this.ixRedisDbError = {};
this.ixRedisDbCommon = {};
this.ixRedisDbCommonError = {};
this.ipSrv = ip_1.default.address();
this.ipConnectUse = ip_1.default.address();
/** Интервал проверки актуальности соединения REDIS */
this.intervalRedis = setInterval(async () => {
let okMainConnect = await this.checkRedisDb(this.redisMaster);
if (!okMainConnect) {
this.workErrorDb(this.redisMaster);
}
/** Убрать проблемную БД из пула по IP */
for (const k in this.ixRedisDbError) {
const dbRedis = this.ixRedisDbError[k];
const okDbRedis = await this.checkRedisDb(dbRedis.redisMaster);
if (okDbRedis && this.ixRedisDbError[k]) {
this.ixRedisDb[k] = this.ixRedisDbError[k];
delete this.ixRedisDbError[k];
}
}
/** Убрать проблемную БД из пула Общего */
for (const k in this.ixRedisDbCommonError) {
const dbRedis = this.ixRedisDbCommonError[k];
const okDbRedis = await this.checkRedisDb(dbRedis.redisMaster);
if (okDbRedis && this.ixRedisDbCommonError[k]) {
this.ixRedisDbCommon[k] = this.ixRedisDbCommonError[k];
delete this.ixRedisDbCommonError[k];
}
}
if (this.env == 'dev') {
console.log('>>>INTERVAL REDIS CONNECT', Date.now(), okMainConnect);
console.log('>>>REDIS CONNECT>>>', 'ip:', Object.keys(this.ixRedisDb).length, 'ipError:', Object.keys(this.ixRedisDbError).length, 'common:', Object.keys(this.ixRedisDbCommon).length, 'commonError:', Object.keys(this.ixRedisDbCommonError).length);
}
}, 10000);
this.ixRedisDb = {};
if (param.env) {
this.env = param.env;
}
if (param.connect) {
// Общий пул соединений для записи и резервного чтения
const aConnectCommon = param.connect['*'];
for (let i = 0; i < aConnectCommon.length; i++) {
const vConnect = aConnectCommon[i];
this.ixRedisDbCommon[i] = {
redisMaster: new ioredis_1.default(vConnect.urlDbMaster),
redisScan: new ioredis_1.default(vConnect.urlDbScan)
};
}
// выделенный пул соединений для ip
const aConnectIP = param.connect[this.ipSrv];
if (aConnectIP) {
for (let i = 0; i < aConnectIP.length; i++) {
const vConnect = aConnectIP[i];
this.ixRedisDb[i] = {
redisMaster: new ioredis_1.default(vConnect.urlDbMaster),
redisScan: new ioredis_1.default(vConnect.urlDbScan)
};
}
}
else {
this.ixRedisDb = this.ixRedisDbCommon;
this.ipConnectUse = '*';
}
if (this.ixRedisDb[0]) {
this.redisMaster = this.ixRedisDb[0].redisMaster,
this.redisScan = this.ixRedisDb[0].redisScan;
}
}
else if (param.urlDbMaster && param.urlDbScan) {
this.redisMaster = new ioredis_1.default(param.urlDbMaster),
this.redisScan = new ioredis_1.default(param.urlDbScan);
this.ixRedisDb[0] = {
redisMaster: this.redisMaster,
redisScan: this.redisScan
};
// пул для записи
this.ixRedisDbCommon[0] = {
redisMaster: this.redisMaster,
redisScan: this.redisScan
};
}
else {
console.log('Неверно указаны параметры для кеша:');
console.log(`connect >> ${param.connect}`);
console.log(`urlDbMaster >> ${param.urlDbMaster}`);
console.log(`urlDbScan >> ${param.urlDbScan}`);
}
// Настройки sphinx
if (param.sphinxDb && param.sphinxIndex) {
this.sphinxDb = (0, knex_1.knex)(param.sphinxDb);
this.sphinxIndex = param.sphinxIndex;
}
else {
console.log('Параметры sphinx для кеша не указаны!!!');
console.log('sphinxDb>>>', param.sphinxDb);
console.log('sphinxIndex>>>', param.sphinxIndex);
}
}
/** Проверка БД редиса */
async checkRedisDb(dbRedis) {
let okConnect = false;
const sWorkConnect = dbRedis.options.host + ':' + dbRedis.options.port;
try {
const pong = await dbRedis.ping();
okConnect = pong == 'PONG' ? true : false;
}
catch (e) {
console.log('>>>ERROR ошибка проверки связи с редисом - ', sWorkConnect);
}
return okConnect;
}
/** Обработка БД с ошибками */
workErrorDb(dbRedis) {
const sCurrConnect = this.redisMaster.options.host + ':' + this.redisMaster.options.port;
const sWorkConnect = dbRedis.options.host + ':' + dbRedis.options.port;
// ============================================
/** Убрать проблемную БД из пула по IP */
for (const k in this.ixRedisDb) {
if (!this.ixRedisDb[k]) {
console.log('ERROR удаление пустой БД');
continue;
}
const vDBMaster = this.ixRedisDb[k].redisMaster;
const sIpConnect = vDBMaster.options.host + ':' + vDBMaster.options.port;
if (sWorkConnect == sIpConnect) {
console.log('>>>Убрать проблемную БД из пула по IP - ', sIpConnect);
this.ixRedisDbError[k] = this.ixRedisDb[k];
delete this.ixRedisDb[k];
}
}
/** Убрать проблемную БД из пула Общего */
for (const k in this.ixRedisDbCommon) {
if (!this.ixRedisDbCommon[k]) {
console.log('ERROR удаление пустой БД');
continue;
}
const vDBMaster = this.ixRedisDbCommon[k].redisMaster;
const sCommonConnect = vDBMaster.options.host + ':' + vDBMaster.options.port;
if (sWorkConnect == sCommonConnect) {
console.log('>>>Убрать проблемную БД из пула Общего - ', sCommonConnect);
this.ixRedisDbCommonError[k] = this.ixRedisDbCommon[k];
delete this.ixRedisDbCommon[k];
}
}
/** Если ошибка произошла по текущему соединению */
if (sCurrConnect == sWorkConnect) {
let bConnectFind = false;
if (!bConnectFind) {
for (const k in this.ixRedisDb) {
this.redisMaster = this.ixRedisDb[k].redisMaster;
this.redisScan = this.ixRedisDb[k].redisScan;
bConnectFind = true;
break;
}
}
if (!bConnectFind) {
for (const k in this.ixRedisDbCommon) {
this.redisMaster = this.ixRedisDbCommon[k].redisMaster;
this.redisScan = this.ixRedisDbCommon[k].redisScan;
bConnectFind = true;
break;
}
}
if (!bConnectFind) {
console.log('>>>ERROR REDIS CONNECT>>> СОЕДИНЕНИЯ ОТСУТСТВУЮТ ');
}
}
console.log('>>>REDIS CONNECT>>>', 'ip:', Object.keys(this.ixRedisDb).length, 'ipError:', Object.keys(this.ixRedisDbError).length, 'common:', Object.keys(this.ixRedisDbCommon).length, 'commonError:', Object.keys(this.ixRedisDbCommonError).length);
}
/**
* Получить значение из редиса
* @param key
*/
async get(key) {
// Пробуем получить ключ из кеша
let kCaсheKey = await this.redisScan.get(key);
// console.log('get-redis-scan',kCaсheKey);
if (!kCaсheKey && this.sphinxDb) {
kCaсheKey = await this.getFromSphinx(key);
if (kCaсheKey) { // Если ключ в sphinx все таки есть записываем его в редис scan
const aPromiseSet = [];
for (const k in this.ixRedisDbCommon) {
aPromiseSet.push((async () => {
const vRedisDb = this.ixRedisDbCommon[k];
try {
await vRedisDb.redisScan.set(key, kCaсheKey, 'EX', 30 * 24 * 3600);
}
catch (e) {
this.workErrorDb(vRedisDb.redisScan);
console.log('>>>ERROR REDIS GET>>>', e);
}
})());
}
await Promise.all(aPromiseSet);
}
}
let vData = null;
if (kCaсheKey) { // Если ключ есть - можем запросить данные
vData = await this.redisMaster.get(kCaсheKey);
}
return vData;
}
/**
* Получить ключи по шаблону - медленный способ
* @param sKeyPattern
*/
async keys(sKeyPattern) {
let aKeys = [];
if (this.sphinxDb) {
aKeys = await this.scanFromSphinx(sKeyPattern);
}
else {
aKeys = await this.redisScan.keys(sKeyPattern);
}
return aKeys;
}
/**
* Получить ключи по шаблону сканированием
* @param keys
*/
async scan(sKeyPattern) {
let aKeys = [];
if (this.sphinxDb) {
aKeys = await this.scanFromSphinx(sKeyPattern);
}
else {
const iRedisSize = await this.redisScan.dbsize();
aKeys = (await this.redisScan.scan(0, 'MATCH', sKeyPattern, 'COUNT', iRedisSize))[1];
}
return aKeys;
}
/**
* Получить количество ключей базы данных
* @param keys
*/
async dbsize() {
return await this.redisScan.dbsize();
}
/**
* Поместить значение в редис
* @param key
* @param val
* @param time
*/
async set(key, val, time = 3600) {
let kCaсheKey = await this.redisScan.get(key);
// Записываем ключ если его нет при настройках sphinx
if (!kCaсheKey && this.sphinxDb) {
kCaсheKey = await this.setInSphinx(key);
if (kCaсheKey) { // Записываем только если смогли сделать ключ
const aPromiseSet = [];
for (const k in this.ixRedisDbCommon) {
// console.log('vRedisDb', vRedisDb)
aPromiseSet.push((async () => {
const vRedisDb = this.ixRedisDbCommon[k];
try {
await vRedisDb.redisScan.set(key, kCaсheKey, 'EX', 30 * 24 * 3600);
}
catch (e) {
this.workErrorDb(vRedisDb.redisScan);
console.log('>>>ERROR REDIS SET>>>', e);
}
})());
}
await Promise.all(aPromiseSet);
}
}
// Записываем ключ если настроек сфинкс нет
if (!kCaсheKey && !this.sphinxDb) {
kCaсheKey = (0, uuid_1.v4)();
// Кешируем на 1 день
const aPromiseSet = [];
for (const k in this.ixRedisDbCommon) {
aPromiseSet.push((async () => {
const vRedisDb = this.ixRedisDbCommon[k];
try {
await vRedisDb.redisScan.set(key, kCaсheKey, 'EX', 30 * 24 * 3600);
}
catch (e) {
this.workErrorDb(vRedisDb.redisScan);
console.log('>>>ERROR REDIS SET>>>', e);
}
})());
}
await Promise.all(aPromiseSet);
}
if (kCaсheKey) {
const aPromiseSet = [];
for (const k in this.ixRedisDbCommon) {
aPromiseSet.push((async () => {
const vRedisDb = this.ixRedisDbCommon[k];
try {
await vRedisDb.redisMaster.set(kCaсheKey, String(val), 'EX', time);
}
catch (e) {
this.workErrorDb(vRedisDb.redisMaster);
console.log('>>>ERROR REDIS SET>>>', e);
}
})());
}
await Promise.all(aPromiseSet);
}
return kCaсheKey;
}
/**
* Найти и удалить ключи по шаблону
* @param sMatch
* @param iCount
*/
async clear(sMatch) {
const aKeys = await this.keys(sMatch);
// console.log('=========================');
// console.log('aKeys for match', sMatch);
// console.log('aKeys for del', aKeys);
// console.log('=========================');
await this.del(aKeys);
return aKeys;
}
/**
* Удалить ключи по ID
* @param keys
*/
async del(keys) {
if (keys.length > 0) {
const aPromiseDel = [];
const aaKeys = lodash_1.default.chunk(keys, 1000);
for (let i = 0; i < aaKeys.length; i++) {
const aKeys = aaKeys[i];
for (const k in this.ixRedisDbCommon) {
aPromiseDel.push((async () => {
const vRedisDb = this.ixRedisDbCommon[k];
try {
await vRedisDb.redisMaster.del(aKeys);
}
catch (e) {
this.workErrorDb(vRedisDb.redisMaster);
console.log('>>>ERROR REDIS DEL>>>', e);
}
})());
}
}
await Promise.all(aPromiseDel);
}
}
// ============================================
// SPHINX операции с ключами
// ============================================
/**
* Получить значение из редиса
* @param sKey
*/
async getFromSphinx(sKey) {
sKey = this.clearKeyForMatch(sKey);
sKey = String(sKey);
const sql = `
SELECT id FROM ${this.sphinxIndex}
WHERE
k = :key
AND
end_at > :end_at
LIMIT 1
;
`;
const param = {
key: sKey,
end_at: (new Date().getTime() / 1000),
};
// console.log('>>>getFromSphinx>>>', this.sphinxDb.raw(sql, param).toString());
let v = null;
try {
v = (await this.sphinxDb.raw(sql, param))[0][0];
}
catch (e) {
console.log('>>>ERROR MYSQL.REDISKEY>>>', e);
}
if (v) {
v = v.id;
}
// console.log( v);
return v;
}
/**
* Получить ключи по шаблону сканированием
* @param keys
*/
async scanFromSphinx(sKey) {
sKey = this.clearKeyForMatch(sKey);
sKey = sKey.replace(/[*]/g, '%');
// let aKeyNew:string[] = [];
// for (let i = 0; i < aKey.length; i++) {
// const vKey = aKey[i];
// if(vKey){
// aKeyNew.push('"*'+String(vKey)+'*"');
// }
// }
// aKey = aKeyNew;
// sKey = aKey.join('<<');
const sql = `
SELECT id FROM ${this.sphinxIndex}
WHERE
k LIKE :key
AND
end_at > :end_at
LIMIT 50000
;
`;
const param = {
key: sKey,
end_at: (new Date().getTime() / 1000),
};
// console.log('>>>scanFromSphinx>>>', this.sphinxDb.raw(sql, param).toString());
let a = null;
try {
a = (await this.sphinxDb.raw(sql, param))[0];
}
catch (e) {
console.log('>>>ERROR MYSQL.REDISKEY>>>', e);
}
if (a) {
a = a.map((v) => String(v.id));
}
return a;
}
/**
* Поместить значение в редис
* @param sKey
* @param val
* @param time
*/
async setInSphinx(sKey) {
let out = ''; // Ответ
const vData = {
k: sKey,
created_at: (new Date().getTime() / 1000),
end_at: (new Date().getTime() / 1000) + (30 * 24 * 3600),
};
try {
const idKey = (await this.sphinxDb(this.sphinxIndex)
.insert(vData))[0];
if (idKey) {
out = String(idKey);
}
}
catch (e) {
out = ''; // Если произошла ошибка возвращаем пустую строку
console.log('>>>ERROR MYSQL.REDISKEY>>>', e);
}
return out;
}
// Очистка ключа для поиска
clearKeyForMatch(sKey) {
sKey = sKey.replace(/["]/g, '');
return sKey;
}
/** Ждет выполнения запросов и закрывает соединение */
quit() {
for (const k in this.ixRedisDbCommon) {
const vRedisDb = this.ixRedisDbCommon[k];
vRedisDb.redisMaster.quit();
vRedisDb.redisScan.quit();
}
if (this.ipConnectUse != '*') {
for (const k in this.ixRedisDb) {
const vRedisDb = this.ixRedisDb[k];
vRedisDb.redisMaster.quit();
vRedisDb.redisScan.quit();
}
}
this.redisMaster.quit();
this.redisScan.quit();
}
}
exports.RedisSys = RedisSys;
//# sourceMappingURL=RedisSys.js.map