UNPKG

@63pokupki/nodejs-common

Version:
493 lines (491 loc) 19.4 kB
"use strict"; 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