Александр Завьялов, Евгений Мокеев
Просмотрщик домашних заданий
function getTasks(category) {
return github.getRepos('urfu-2015')
.then(tasks => {
return filterTasks(tasks, category)
})
.then(getTasksInfo);
};
Promise
.all([
getTasks('javascript'),
getTasks('verstka'),
getTasks('webdev')
])
.then(results => {
res.render(...);
});
Promise
.all([
getTasks('javascript'),
getTasks('verstka'),
getTasks('webdev')
]);
function getTasks(category) {
return github.getRepos('urfu-2015')
.then(...)
.then(...);
};
require('debug-http')();
GET https://api.github.com/orgs/urfu-2015/repos 891ms
GET https://api.github.com/orgs/urfu-2015/repos 889ms
GET https://api.github.com/orgs/urfu-2015/repos 902ms
GET https://api.github.com/orgs/urfu-2015/repos 933ms
GET https://api.github.com/orgs/urfu-2015/repos 953ms
GET https://api.github.com/orgs/urfu-2015/repos 1,003ms
GET https://api.github.com/orgs/urfu-2015/repos 1,018ms
GET https://api.github.com/orgs/urfu-2015/repos 1,067ms
GET https://api.github.com/orgs/urfu-2015/repos 1,072ms
Много одинаковых запросов
let reposQuery;
function getReposBatch() {
if (!reposQuery) {
reposQuery = github.getRepos('urfu-2015')
.then(res => {
reposQuery = null;
return res;
});
}
return reposQuery;
}
function getTasks(category) {
return getReposBatch()
.then(tasks => {
return filterTasks(tasks, category)
})
.then(getTasksInfo);
};
Уменьшили количество запросов
Быстрее отвечаем некоторым пользователям
Если запрос завершился ошибкой, то для всех
Данные ближе к месту использования
Хранение результатов вычислений
Оптимизация скорости получения данных
Низкая производительность
Избыточность запросов
Высокая нагрузка на внешний источник
Узкий сетевой канал
Высокие сетевые задержки
Количество попаданий
Hit Rate = Попадание в кэш / Количество запросов
Скорость получения данных
“Разогретый” кэш
Данные устарели (“протухли”) и их нужно убрать из кэша
Данные удаляются вручную
Данные заменяются новыми
Данные вытесняются автоматически по алгоритму
Одна из самых сложных задач в программировании
Алгортим вытеснения из кэша
Time period - Вытеснение по времени
LFU - Вытеснение редко используемых
LRU - Вытеснение давно неиспользуемых
Segmented LRU - Многоуровневый LRU
От алгортима зависит быстродействие кэша
Сохранение результата выполнения функции
const cache = {};
function memoize(key, fn) {
if (!cache.hasOwnProperty(key)) {
cache[key] = fn();
}
return cache[key];
}
const LRU = require('lru-cache');
const cache = new LRU();
cache.set(key, value, maxAge);
cache.get(key);
cache.del(key);
cache.has(key);
class Cache {
constructor() {
this._cache = new LRU();
}
memoize(key, maxAge, fn) {
// ...
}
}
memoize(key, maxAge, fn) {
const cache = this._cache;
const value = cache.get(key);
if (value) {
return Promise.resolve(value);
}
return Promise.resolve()
.then(fn)
.then(results => {
cache.set(key, result, maxAge * 1000);
return result;
});
}
function getTasks(category) {
return getReposBatch()
.then(tasks => {
return filterTasks(tasks, category)
})
.then(getTasksInfo);
};
const cache = new Cache();
function getTasksCached(category) {
const cacheKey = `tasks.${category}`;
return cache.memoize(cacheKey, 5 * 60, () => {
return getTasks(category);
});
}
GET https://api.github.com/orgs/urfu-2015/repos 990ms
GET / 200 1249.771 ms
GET / 200 1254.626 ms
GET / 200 1236.532 ms
GET / 200 11.003 ms
GET / 200 18.849 ms
GET / 200 25.639 ms
Процесс увеличения производительности и отказоустойчивости системы
Распределение нагрузки между несколькими процессами и машинами
Увеличивает доступность
Увеличивает производительность
Увеличивает отказоустойчивость
Увеличивает сложность
Сильная. Система “легко” масштабируется
Слабая. Очень тяжело масштабируется
Масштабируемость ~ Монолитность
Вертикальное. Добавление мощностей
Горизонтальное. Разбиение приложения на несколько частей
ось X: Клонирование приложения
ось Y: Декомпозиция приложения
ось Z: Разделение в зависимости от данных
The Art of Scalability. Martin L. Abbott and Michael T. Fisher
Round-robin алгоритм балансировки
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const cpus = os.cpus().length;
for (let i = 0; i < cpus; i++) {
cluster.fork();
}
} else {
require('./app.js');
}
Распараллелена нагрузка
При ошибке приложение не доступно
siege -t20S http://localhost:8080
Availability: 10.89 %
Successful transactions: 61
Failed transactions: 499
if (cluster.isMaster) {
// ...
cluster.on('exit', (worker, code) => {
if (code !== 0 && !worker.suicide) {
console.log('Worker crashed');
cluster.fork();
}
});
}
Availability: 87.10 %
Successful transactions: 243
Failed transactions: 36
Высокая доступность
Отказоустойчивость
Изолированная память
Shared cache
Хорошая документация
Данные в памяти
Транзакции
Пакетная обработка команд
Механизм pub/sub из коробки
Поддержка LRU алгортима
Встроенный мониторинг команд
redis-cli
telnet localhost 6379
Установка значения:
SET tasks.javascript "[{ name: ... }]"
Проверка существования ключа:
EXISTS tasks.javascript
Получение ключа:
GET tasks.javascript
Удаление ключа:
DEL tasks.javascript
Установка времени жизни ключа:
EXPIRE tasks.javascript 30
Получение времени жизни ключа:
TTL tasks.javascript
Установка значения вместе с временем жизни:
SETEX tasks.javascript 30 "[{ name: ... }]"
const Redis = require("ioredis");
class Cache {
constructor() {
this._cache = new Redis(6379, '127.0.0.1');
}
}
memoize(key, maxAge, fn) {
const cache = this._cache;
const value = cache.get(key)
if (value) {
return Promise.resolve(value)
}
return Promise.resolve()
.then(fn)
.then(results => {
cache.set(key, result, maxAge * 1000)
return result;
}
}
memoize(key, maxAge, fn) {
const cache = this._cache;
return cache.get(key)
.then(value => {
if (value) {
return JSON.parse(value);
}
return Promise.resolve()
.then(fn)
.then(result => {
cache.setex(
key, maxAge,
JSON.stringify(result));
return result;
});
});
}
Объединение запросов
Кэширование
Масштабирование
Всегда нужно отталкиваться от задачи