Работа с асинхронным поведением NodeJS
Использование NodeJS с MongoDB + Mongoose.
Прежде всего, я знаю преимущества асинхронного неблокирующего кода. Поэтому я занимаюсь обратными вызовами. Но, наконец, я столкнулся со следующей проблемой.
Допустим, у меня есть функция, которая может быть вызвана пользователем в любое время. И возможно, что супер "молниеносный" пользователь вызывает его дважды почти одновременно.
function do_something_with_user(user_id){
User.findOne({_id:user_id}).exec(function(err,user){ // FIND QUERY
// Do a lot of different stuff with user
// I just cannot update user with a single query
// I might need here to execute any other MongoDB queries
// So this code is a set of queries-callbacks
user.save() // SAVE QUERY
})
}
Конечно, он выполняется следующим образом: НАЙТИ ЗАПРОС, НАЙДИТЕ ВОПРОС, СОХРАНЯЙТЕ ВОПРОС, СОХРАНЯЙТЕ ВОПРОС
Это полностью нарушает логику приложения (если НАЙТИ ВОПРОС, СОХРАНЯЙТЕ ВОПРОС, НАЙДИТЕ ВОПРОС, СОХРАНЯЙТЕ ВОПРОС). Поэтому я решил предотвратить асинхронное поведение, "заблокировав" всю функцию для конкретного пользователя (поэтому внутри кода функции все равно async).
var lock_function_for_user = {}
function do_something_with_user(user_id){
if(!lock_function_for_user[user_id]){
lock_function_for_user[user_id] = true
User.findOne({_id:user_id}).exec(function(err,user){
// Same code as above
user.save(function(){
lock_function_for_user[user_id] = false
})
})
} else {
setTimeout(function(){
do_something_with_user(user_id)
},100) // assuming that average function execution time is 100ms in average
}
}
Итак, мой вопрос: это хорошая практика, хороший взлом или плохой взлом? Если это плохой взлом, пожалуйста, предоставьте другое решение. В частности, я сомневаюсь, что это решение будет работать, когда мы масштабируем и запускаем несколько процессов NodeJS.
Это очень плохая практика, вы никогда не должны использовать таймеры для управления потоком кода.
Проблема здесь называется атомарностью. Если вам нужно найти find-save, find-save, вам нужно как-то упаковать эти операции (транзакцию). Это зависит от используемого вами программного обеспечения. В redis у вас есть команды multi и exec. В mongodb вы найдетеAndModify(). Другое решение - использовать индекс. Когда вы пытаетесь сохранить одно и то же поле дважды, вы получаете сообщение об ошибке. Используйте атрибуты "index: true" и "unique: true" в schemaType в mongoose:
var schema = mongoose.Schema ({
myField: { type: String, index: true, unique: true, required: true },
});
Это то, что вам нужно: Mongodb - Изолировать последовательность операций - выполнить двухфазные фиксации. Но учтите, что mongodb не может быть лучшим выбором, если вам нужно совершить много транзакций.
Вы не хотите тратить ОЗУ, поэтому замените
lock_function_for_user[user_id] = false
с
delete lock_function_for_user[user_id]
Кроме того: вы можете просто быть оптимистом и повторять попытку, если произойдет конфликт. Просто оставьте блокировку и убедитесь, что DB замечает, когда что-то идет не так (и повторите попытку в этом случае). Конечно, какой способ лучше зависит от того, насколько часто такие конфликты действительно происходят.
- Вопросы
- Asynchronous
- Работа с асинхронным поведением NodeJS