|
JavaScript / HTML Создание динамической разметки страниц |
01.09.2013, 16:45
|
#1
|
Unity/C# кодер
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений (для 5,323 пользователей)
|
Node.JS + MongoDB
Посоветуйте пожалуйста чтиво по этому поводу.
Из основных вопросов: Пока не понял как правильно работать с открытием соединения к базе, хочу использовать модуль mongodb и думаю никакие mongoose не надо. Цель - создать REST API для начала.
|
(Offline)
|
|
01.09.2013, 22:00
|
#2
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
Не нужно книжек и чтивы. Если работал с mongo то там всё проактически идентично официальным докам (в случае с mongoose это не так).
Я юзал mongoose и потом перешёл на mongo-native от самих 10gen - и не жалею вовсе. Мне эти schemas - нафиг не сдались. Суть mongo - в её динамике, и нефиг пытаться прикручивать правила и структуру к collection'ам подобно это SQL бд.
Офф дока - очень полезна, т.к. как уже говорил почти идентична к mongo-native в использовании.
По факту - коммандная строка для mongo тоже на JavaScript'е
Про соединение, сперва тебе нужно создать объект куда коннектиться:
var mongodb = require('mongodb'); var access = new mongodb.Server('127.0.0.1', 27017, { auto_reconnect: true });
new mongodb.Db('database', access, { safe: true }).open((function (err, db) { if (err) throw err; // db - хендл для бд (client) });
Далее тебе нужно будет получить сами collection'ы, я это делаю один раз при коннекте в самом каллбеке (точнее я создаю евент и слущаю его, таким образом разные модули получают свои collection'ы:
var users = db.collection('users');
Как только у тебя есть collection можешь уже работать с данными:
users.insert({ name: 'test' });
users.find({ }).toArray(function(err, data) { if (err) throw err;
console.log(data); });
Вот тут исчерпывающие примеры:
https://github.com/mongodb/node-mong...aster/examples
Есть пару тонкостей: - Реконнект при обрыве с бд, я не тестировал, но может даже collection хэндлы нужно будет переполучать (а может и нет). В общем тестируй если такие сценарии бывают.
- _id - по стандарту mongo держит ObjectID - это своего рода просто string и в консоль по факту будет выводиться просто как string что пиздецки сбивает порой. Так что не забывай что каждый раз если имеешь ID например с REST запроса, нужно создать ObjectID объект из string'и.
- Если хочешь просто номер для _id (не рекомендуется если предполагается шардинг или репликация бд). То у меня был очень простой выход: заводим collection 'ids' и имеем _id - имя collection и inc - число инкремента. Затем если хотим создать объект в бд делаем:
ids.findAndModify({ _id: 'users' }, { }, { $inc: { inc: 1 } }, { new: true }, function(err, id) { if (err) throw err;
// id == твой новый id });
- find получает список, и в mongo-native модуле это не callback (это единственный метод с таким исключением). Так что юзаем спец модуль "toArray":
users.find({ }).toArray(function(err, data) { });
- Для REST советую использовать express.js т.к. роутинг будет очень простым и удобным.
- Валидация - важно. Типы данных бывают разные (если это POST).
- Middleware - в express есть отличная тема с middeware, так можно написать модуль с парсингом пагинации и юзать его везде. Пример: заводим файл pagination.js и его содержимое:
exports.middleware = function(data) { return function(req, res, next) { // возвращаем middleware функцию var limit = data.limit ? data.limit : 0; // limit по стандарту 0 var skip = 0; // skip тоже нуль
if (req.query.limit) { // если в запросе есть limit var tmp = parseInt(req.query.limit); // парсим if (tmp != NaN && tmp > 0) { // если хорошее число limit = tmp; } } if (req.query.skip) { // если в запросе есть skip var tmp = parseInt(req.query.skip); // парсим if (tmp != NaN && tmp > 0) { // если хоршее число skip = tmp; } }
if (data.max) { // если указан максимальное число на страницу limit = Math.min(limit, data.max); // ограничиваем limit } if (data.min) { // если указано минимальное число на страницу limit = Math.max(limit, data.min); // ограничиваем limit }
req.paginator = { // создаём объект с limit и skip данными в объекте запроса limit: limit, skip: skip }; next(); // продолжаем цепочку middleware } };
Далее в коде:
var paginator = require('./paginator.js').middleware; // получаем handle middleware пагинатора app.get('/users', paginator({ limit: 8, min: 4, max: 64 }), function(req, res, next) { // устанавливаем get запрос с пагинатором users.find({ }, { skip: req.paginator.skip, limit: req.paginator.limit }).toArray(function(err, data) { // ищем в бд с данными из пагинатора if (err) next(err); // если ошибка, бежим в express хэндлер ошибок res.json(data); // или шлём данные с бд джонсоном }); });
Если есть конкретные вопросы - задавай.
Я пишу по работе RESTful API с mongo-native и express'ом ну и другими всякими плюшками.
|
(Offline)
|
|
Эти 3 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
|
|
01.09.2013, 22:49
|
#3
|
Unity/C# кодер
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений (для 5,323 пользователей)
|
Ответ: Node.JS + MongoDB
Правильно ли я понимаю, что для каждого REST запроса надо создавать свое подключение? Мне сначала подумалось создать одно подключение к базе на старте приложения и потом его использовать. Потом я посмотрел некоторые рест библиотеки для монго и там для каждого запроса создается свой экземпляр Db. Такой подход верный?
Еще один вопрос - есть у nodejs возможность запускаться в многопроцессном режиме, это не стоит использовать для REST API?
|
(Offline)
|
|
01.09.2013, 23:44
|
#4
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
Не нужно для каждого запроса свой db хэндлер - это оверкил.
Там блокировка на уровне бд будет а не на уровне node, следственно иметь более одного хэндла - смысла нету, только замедляет процесс.
Возможно mongo-native кеширует коннекты и будет выдавать уже подконекченный - но это снова лишние телодвижения.
Относительно мультипоточности - ты говоришь о cluster?
Мы используем - 4 worker'а. Всё работает как полагается, естественно если у тебя есть сессия, то её нужно хранить вне процесса, мы используем connect-mongo, тем самым не важно в какой worker идёт запрос.
По опыту пришёл к такому выводу - если у тебя есть CMS и write'ы очень редкие, и обслуживаются определённой группой людей, следственно таскать с собой функционал для CMS в основном API не оправдано.
Для этого я сделал отдельные запросы для PUSH'ей, это немного идёт против логики REST'а, т.к. /url будет отличаться (я сделал субдомейн для этого, а руты были те же), но зато когда нужно обновить основной API, не нужно было перегружать CMS и т.п.
Но это зависит от ситуации.
cluster'ы - реально помогают, их количество обычно параллельно количеству ядер. Мы на ec2 xlarge (4 CPU) 4 worker'а запускаем - и всё ок.
|
(Offline)
|
|
Сообщение было полезно следующим пользователям:
|
|
03.09.2013, 10:46
|
#5
|
Unity/C# кодер
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений (для 5,323 пользователей)
|
Ответ: Node.JS + MongoDB
Еще пара вопросов:
1. Как правильно обрабатывать ошибки, чтобы при их происхождении не падал весь процесс. Можно конкретно для express и mongodb.
2. Я так понимаю что кластер можно использовать как защиту от падения процесса, т.е. кластер при падении одного воркера может запустить нового воркера? И если надо будет перезапустить сервер, то просто необходимо будет перезапустить кластер да?
3. Какой модуль лучше использовать для логирования? Особенно если используется кластер и несколько воркеров.
|
(Offline)
|
|
03.09.2013, 13:53
|
#6
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
Сообщение от pax
1. Как правильно обрабатывать ошибки, чтобы при их происхождении не падал весь процесс. Можно конкретно для express и mongodb.
|
В примерах я делал 'throw', обычно я так не делаю.
Важно понимать что ошибки - это важно, и их не должно быть.
Если в тестировании ошибок в запросах к бд небыло, а потом есть, значит твой query слишком динамичен и нужно его немного нормализировать (это 90% случаев).
Я у себя в коде так делаю:
Ранее при настройке express'а:
app.configure(function() { app.use(express.errorHandler()); ... });
app.get('/users', ..., function(req, res, next) { users.find({ }, { skip: req.paginator.skip, limit: req.paginator.limit }).toArray(function(err, data) { if (!err) { res.json(data); } else { next(err); } }); });
Далее где-то в коде самый последний middleware будет для ловли ошибок:
app.use(function(err, req, res, next) { var obj = { error: true, message: err.message }; if (development) { obj.stack= err.stack; } res.json(obj); });
Сообщение от pax
2. Я так понимаю что кластер можно использовать как защиту от падения процесса, т.е. кластер при падении одного воркера может запустить нового воркера? И если надо будет перезапустить сервер, то просто необходимо будет перезапустить кластер да?
|
Да. Но нужно следить точно когда что-то падает или нет.
Вообще есть "защито" от падения процесса. Есть event который ловит не словленные exception'ы. Но естественно если что-то жесть как пошло не так, то нужно и реагировать соответственно, а не тупо игнорировать.
Но всё равно:
process.on('uncaughtException', function(err) { console.log('Caught exception: ' + err, err.stack); });
Сообщение от pax
3. Какой модуль лучше использовать для логирования? Особенно если используется кластер и несколько воркеров.
|
Я пока настраивал node.js как сервис в linux, тупо выводил консоль в файл, естественно это не супер решение. И исследовал этот вопрос. Лидер тут конкретный: winston https://github.com/flatiron/winston , отлично поддерживается.
Суть у него в том что он имеет основу для логгирования, но логит в транспорты, которые могут быть что угодно: консоль, файл, бд, MQ, redis, socket, amazon alerts, email, и т.п.
Есть стандартный набор транспортов которых обычно хватает с головой.
Я лично юзать буду файл + бд (с индексом просрочивания неделю), и ZeroMQ в мелкий процесс который через socket.io будет слать логи от разных процессов мне на мелкую страничку мониторинга. Таким образом если что-то случиться, я сразу могу вычислить с кем, где и т.п.
|
(Offline)
|
|
Эти 2 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
|
|
03.09.2013, 15:10
|
#7
|
Unity/C# кодер
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений (для 5,323 пользователей)
|
Ответ: Node.JS + MongoDB
Хочу часть логики сделать на хранимом js в MongoDB, это лучше чем делать логику в NodeJS?
Upd: кстати как правильно вызывать хранимые функции?
Upd2: Получилось примерно так:
db.getUser = function (user_id, callback) { db.eval(new Code('return user_get(user_id);', {'user_id': user_id}), {nolock: true}, callback); }
сама функция
Это нормально или я что-то не так делаю?
|
(Offline)
|
|
03.09.2013, 16:58
|
#8
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
Я предпочитаю ничего не хранить в DB. Можно лишь мелкие туулзы или функции утилит, но не относящиеся к логике твоего приложения - это ответственность твоего API.
Да и сразу по твоей функции - ты доверяешь назначение user_id кому? БД, или логике приложения?
Я пытался сделать подобное - вставлять если нету, но потом пришёл к выводу - нефиг! Есть куча сценариев где тебе нужно получить юзверя - но он может и не быть там, следственно и вставлять его не нужно.
Так что лучше разделить эту логику.
Да и если ты 100% вставляешь, то не используй 'save', а используй 'insert'.
save - весьма опасен, тем что если будешь обновлять что-то (ожидая update), а документа такого не будет, а обновление будет partial (только пару полей), то вставиться "не полный" документ, что твоя логика приложения не будет ожидать.
И мелкая заметка, в node, для даты проще писать так:
Date.now() - получает timestamp.
|
(Offline)
|
|
Эти 2 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
|
|
03.09.2013, 18:21
|
#9
|
Unity/C# кодер
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений (для 5,323 пользователей)
|
Ответ: Node.JS + MongoDB
У меня это авторегистрация такая. id это id пользователя в соц сети. На счет insert спасибо, буду иметь ввиду. И я уже решил делать логику в ноде, а не в БД, а то получается какая-то фигня (не нравится мне такой код) с вызовами.
|
(Offline)
|
|
03.09.2013, 19:27
|
#10
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
Я тут щас aggregation и mapReduce изучаю. Заодно открыл для себя $where.
$where - позволяет стрингой передать сроку кода, например:
Пример player'а:
{ _id: 42, prizes: [ { created: 123, type: 'foo' }, { created: 234, type: 'bar' } ] }
И вот такой query:
players.count({ $where: 'this.prizes.length >= 20' }, function(err, count) { if(!err) { // count - сколько игроков имеет призов больше чем 20 } else { ... } });
Если в бд вбить:
db.players.find({ $where: 'this.prizes.length >= 20' }).explain()
То выдаст факт того что тут ничего естественно не индексируется, индекс тоже игнорируется.
Следственно например для collection'а с 2800 записями, у меня затрачивается 72мс (на простом Mac'е), что ужасно долго для таких запросов.
Следственно в реальном мире лучше кешировать длину массива рядом с массивом.
Но всё же, если у тебя например запрос на 32 записи (например), то такой код - вполне приемлем. Нужно исследовать как там с блоком всего процесса (блокирует ли другие запросы и т.п.).
aggregation - это интерестный монстрик, по получению всяких данные и более сложных манипуляций над документами.
Например у меня есть записи игроков, и у каждого игрока есть любимая команда. Мне нужно получить список всех команд и сколько игроков её указали как любимую:
players.collection().aggregate([ { $match: { favourite: { $exists: true } // ищем всех игроков у которых указан 'favourite' } }, { $group: { // групируем все записи _id: "$favourite", // по favourite полю total: { $sum: 1 } // и прибавляем 1 к total } }, { $sort: { // потом сортируем по убыванию total total: -1 } } ], function(err, data) { if (!err) { // } else { ... } });
favourite - это id команды простое число.
Всего там 20 команд.
В результате получу массив с до 20 записями такого вида:
{ _id : 685, total : 813 }
Для сборов статистики - самое то. Естетсвенно это не супер шустро такие статистически сборы делать. Но в aggregate есть не мало разных операторов, которые используют индексацию, что ускоряет весьма не плохо. Для не больших запросов (до 64 записей), это весьма приемлемый подход. Естественно всегда можно закешить результаты.
mapReduce - это ещё следующий шаг. Когда aggregation предоставляет операторы, и там важна их поочерёдность и т.п., то mapReduce принимает лишь 2 функции и query фильтр.
Первая функция обрабатывает каждый документ (удовлетворяющий query) и если нужно вызывает emit, что передаёт уже обработанные данные (можно и не обработанные) в reduce функцию, где уже дальше мы имеем дело с групированными данными.
Примеров много можно привезти, в моём случае, нужно было узнать сколько призов в среднем выйграл каждый игрок. Есть 2800 записей. И нужно получить общее арифметическое колличества призов:
players.collection().mapReduce(function() { emit('stats', this.prizes.length); // map - для всех запускаем reduce с групированием (_id будет 'stats') }, function(key, values) { // reduce var total = 0; // всего призов var i = values.length; while(i--) { // для каждой длины массива призов total += values[i]; // складываем } return total / values.length; // и делим на количество значений }, { query: { confirmed: true }, // только игрокам кто подтвердил свой аккаунт out: { inline: 1 } }, function(err, data) { if (!err) { // data[0].value - общее арифметическое длин массива призов у каждого игрока } else { ... } });
На самом деле это можно было сделать и aggregator'ом, но я хотел попробовать mapReduce.
Снова, для тысячей записей - это не шустро, но для статистики - самое то.
|
(Offline)
|
|
Эти 2 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
|
|
04.09.2013, 12:21
|
#11
|
Unity/C# кодер
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений (для 5,323 пользователей)
|
Ответ: Node.JS + MongoDB
Заметил что таймштамп в ноде в миллисекундах, а не в секундах как это в php. Поделил все на 1000)
|
(Offline)
|
|
04.09.2013, 16:34
|
#12
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
Сообщение от pax
Заметил что таймштамп в ноде в миллисекундах, а не в секундах как это в php. Поделил все на 1000)
|
Дык держи в миллисекундах, их и так достаточно, и не вижу бенефита в секундах
Тем более что если захочешь перевезти timestamp в дату то:
var date = new Date(timestamp);
Иначе прийдётся ещё и на 1000 умножать..
|
(Offline)
|
|
04.09.2013, 18:08
|
#13
|
Unity/C# кодер
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений (для 5,323 пользователей)
|
Ответ: Node.JS + MongoDB
Бенефит: в базе либо Int32 если секунды, либо double если миллисекунды.
PS: начинаю втягиваться, на первый взгляд сложнее чем на php, потому что надо писать асинхронный код, с другой стороны вроде как и интереснее получается.
|
(Offline)
|
|
04.09.2013, 18:32
|
#14
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
Сообщение от pax
Бенефит: в базе либо Int32 если секунды, либо double если миллисекунды.
|
Исходя из того как бд хранит данные и создаёт файлы с зазорами - это не даст практически никакой разницы, если конечно у тебя документы не просто _id и timestamp.
http://stackoverflow.com/questions/6...tes-in-mongodb
Сообщение от pax
PS: начинаю втягиваться, на первый взгляд сложнее чем на php, потому что надо писать асинхронный код, с другой стороны вроде как и интереснее получается.
|
На самом деле как привыкнешь, потом будет даже не сложнее.
Правда появяться некоторые "трудности", например запустить сразу 3 асинхронных функции, и потом финальную функцию, или сделать очередь асинхронных функций. Есть promise и async.js, но я как обычно юзаю свой вариант решения простой задачи:
function jobsQueue(jobs) { if (jobs && jobs instanceof Array && jobs.length > 0) { var count = jobs.length; var i = -1; var next = function() { if (++i < count) { jobs[i](next); } } next(); } }
function jobsQueueAsync(jobs, complete) { if (jobs && jobs instanceof Array && jobs.length > 0) { var count = jobs.length; var done = function() { if (--count == 0 && complete) { complete(); } } for(var i = 0, len = jobs.length; i < len; ++i) { jobs[i](done) } } }
Первая запускает поочерёдно функции.
Вторая запускает все асинхронно и финальную когда все закончили.
Пользоваться так:
jobsQueue([ function(next) { ... next(); }, function() { ... } ]);
jobsQueueAsync([ function(next) { ... next(); }, function(next) { ... next(); } ], function() { ... done });
Ну и тесты:
jobsQueue([ function(next) { console.log('1'); next(); }, function(next) { console.log('2a') setTimeout(function() { console.log('2b'); next(); }, 400); }, function(next) { console.log('3a'); setTimeout(function() { console.log('3b'); next(); }, 100); }, function() { console.log('4'); } ]);
Выдаст: 1 2a 2b 3a 3b 4
Как видишь он даже не начинает выполнение следующей функции до выполнения прошлой.
jobsQueueAsync([ function(next) { console.log('1'); next(); }, function(next) { console.log('2a') setTimeout(function() { console.log('2b'); next(); }, 400); }, function(next) { console.log('3a'); setTimeout(function() { console.log('3b'); next(); }, 100); } ], function() { console.log('4'); } );
Выдаст: 1 2a 3a 3b 2b 4
И тут видно что он запускает все сразу, но финальную только когда каждая функция запустит next() по завершению (асинхронно или нет).
|
(Offline)
|
|
Эти 2 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
|
|
04.09.2013, 18:36
|
#15
|
.
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений (для 6,863 пользователей)
|
Ответ: Node.JS + MongoDB
В тему асинхронного кода, юмор:
var result = fs.readFile(‘data.json’, function () {})
|
(Offline)
|
|
Эти 2 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
|
|
Ваши права в разделе
|
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения
HTML код Выкл.
|
|
|
Часовой пояс GMT +4, время: 14:08.
|