Доброго дня всем. Ситуация: На сервер приходят сообщения (до 1000 в секунду) по посту. Я их сохраняю вот так: Код (Text): $xml_post = file_get_contents('php://input'); // If we receive data, save it. . if ($xml_post) { $number = 0; $xml_file = 'pusherFiles2/received_xml_' . date('Y_m_d-H-i-s') .'_'.$number. '.xml'; while(file_exists($xml_file)){ $number++; $xml_file = 'pusherFiles2/received_xml_' . date('Y_m_d-H-i-s') .'_'.$number. '.xml'; } $fh = fopen($xml_file, 'w') or die(); fwrite($fh, $xml_post); fclose($fh);} Далее мне нужно обработать эти файлы (рассортировать). Так вот я делаю так: Код (Text): $fileList = array_slice(scandir('pusherFiles/'),2,100); foreach($fileList as $file){ if(file_exists($oldfolder.$file))rename($oldfolder.$file,$newfolder.$file); } И иногда получаю ворнинг ПХП Warning: rename(pusherFiles/received_xml_2017_03_30-21-10-21_2.xml,o_temp2/received_xml_2017_03_30-21-10-21_2.xml): No such file or directory in ... Что-то не пойму я, ведь стоит проверка перед операцией if(file_exists($oldfolder.$file)). Откуда тогда ворнинг? Подобное происходит и с unlink-ом, когда я удаляю Warning: unlink(o_temp3/received_xml_2017_03_30-21-09-28_6.xml) вот код: Код (Text): if(file_exists($filename)) unlink($filename); Общая картинка такая: Код (Text): $xml_post = file_get_contents('php://input'); // If we receive data, save it. . if ($xml_post) { $number = 0; $xml_file = 'pusherFiles2/received_xml_' . date('Y_m_d-H-i-s') .'_'.$number. '.xml'; while(file_exists($xml_file)){ $number++; $xml_file = 'pusherFiles2/received_xml_' . date('Y_m_d-H-i-s') .'_'.$number. '.xml'; } $fh = fopen($xml_file, 'w') or die(); fwrite($fh, $xml_post); fclose($fh); $oDecoder = new Controller_Decoder(); $i=0; if(count(array_slice(scandir('pusherFiles2/'),2)) > 500){ shell_exec("mv pusherFiles2/* pusherFiles"); } while(file_exists('oldFiles/process'.$i.'.txt') && $i<4){ $i++; } if($i<4){ $oDecoder->openProcess($i); } return $xml_file; } //functoins from class Decoder function openProcess($i){ $tempdir = 'oldFiles/'; $tempFile = 'process'.$i.'.txt'; $oldfolder = 'pusherFiles/'; $filename = $tempdir.$tempFile; $post = 'process'.$i; if(!file_exists($filename)){ $fh = fopen($filename, 'w') or die(); fwrite($fh, $post); fclose($fh); $fileList = array_slice(scandir('pusherFiles/'),2,100); if(count($fileList) >0){ $newfolder = 'o_temp'.$i.'/'; if (!file_exists($newfolder)) { mkdir($newfolder, 0777, true); } foreach($fileList as $file){ if(file_exists($oldfolder.$file))rename($oldfolder.$file,$newfolder.$file); } $listDecode = array_slice(scandir($newfolder),2); $res = $this->action_getFilecim($listDecode,$newfolder); } unlink($filename); } }
В какой строке указан? Почему забирается именно как file_get_contents('php://input'), а не просто читается текст из соответствующей POST-переменной? Отладку пробовали делать?
Да ворнинг указывает именно на те строки, которые я указал. Забирается по //input, - так указано было в API и неизвестно имя POST . Данные приходят в json виде. Все, что приходит - в файл, и потом уже разбираю. Проблемы начинаются, когда более 100 (иногда до 1000) пакетов в секунду приходят по 1-4 Кб. Я потом эти пакеты в БД запихиваю. Меня удивляет сам факт того, что после проверки выскакивают эти сообщения. Может кто сталкивался с таким? Сейчас временное решение нашел с помощью ассинхронного вызова скрипта + создал буферную папку, куда перекидывает по 100 файлов и потом оттуда растягивается скриптом. И таких ошибок нет. Какова вероятность, что в момент проверки файл есть, а после проверки его уже нет?
https://en.wikipedia.org/wiki/Race_condition --- Добавлено --- - Доктор, когда я вот так вот делаю, мне вот тут вот больно. - Не делайте так. Решение тут обычно такое: не писать код, в котором возможен рейс кондишн. Ну в твоём случае очевидное решение в лоб - не писать в файл, а сразу разбирать и сохранять под правильным названием. Но наверное тебе жутко страшно, что в случае ошибки что-то потеряется. Наверное поэтому ты их в файлы пихаешь. Вообще, мне честно говоря непонятно, почему ты не работаешь с какой-нить mongodb. Но видимо тебе либо лень всё это настраивать, либо ещё чего. Поэтому вот тебе ещё один костыль: Пишешь первичные файлы в одну папку, а разобранные - в другую. Всё. Победа. А потом кроном раз в час из первой папки стираешь те, что старше суток, например.
Значит что я пробовал и что получилось. 0. Во всем виноват оказался Рейс Кондишн. 1. Абзац пришел минут через 5, когда проц разошелся на 500% и просто стал рубить все. 2. Посмотрел на MongoDb. Оказался очень интересный инструмент. Правда я раньше с ним не работал никогда. Но установил, настроил и все, вроде бы, закрутилось. 3. В виду нехватки времени реализовал костыль: Файлы пишутся в Монгу, оттуда скриптом выбирается json и пишется в базу ΜySql а в Монге удаляются по ИД. 4. О Монге знаю мало, поэтому буду тестировать ее на выборку и апдейт данных. Посмотрим, как она себя поведет по времени. Но точно понятно, что на запись она быстрее, чем МайСиКУеЛ. Даже просто запись в файл давала сбой и были битые пакеты (нарушена json кодировка). Как и ожидалось, самое узкое место - операции с БД. Большое спасибо за направления. Пойду совершенствоваться. Спасибо всем!
если тебе не надо всё-всё гарантированно на 100% сохранять, то можно заюзать кеши типа memcached или apc или redis. Но они могут переполниться, если не будешь успевать разбирать, а сбрасывать на диск они не будут. Редис умеет, но мемкеш или апц это чисто кеш в оперативке больше похоже, что тебе надо с одного сервера пихать в редис отдельно стоящий, допустим. А с третьего - разбирать то, что накидалось в редис.
@sphinks я бы на твоем месте давал файлам имена из uniqid(). Ведь всё портит, насколько я понял, $number.
Господа, в общем все портит MySql ибо работает очень медленно. Если кто-то знает про MongoDb Driver и желает помочь, я тут тему открыл https://php.ru/forum/threads/mnogourovnevaja-vyborka-s-mongodb-driver.62897/.
ну медленно понятие относительное. расскажи, как именно ты его дрючишь. Может ты это неправильно делаешь. --- Добавлено --- а попробуй-ка вместо INSERT писать INSERT DELAYED
В случае рэйс кондишен всегда будет портить самое медленное звено. Теперь вместо MySQL будет что-то другое, но реже.
Медленно - это 2500 строк за 3 минуты. У меня задача такая: 1. Приходящие пакеты (1-8 КБ) в формате json от провайдера инфы помещать в базу, которую использует сайт. 2. Потом я Ajax-ом на сайте обновляю инфу. Но, чтоб поместить их в базу, нужно их сортировать. json вида Код (Text): { "Header": { "ModuleId": 3, "GeneratedTime": "2017-03-31T17:24:29.5822999Z", "MsgGuid": "bfd5d9d6-a6fa-414f-903b-e09300d433a9", "MsgId": 592 }, "Peer": { "Id": 2274581, "Events": { "Event": { "Id": 6, "Name": "Birthday", "Guests": [ { "Name": "Ivan", "SecondName": "Kolomiec", "Phone": "+30xxxxx", "Status": "Active" }, { "Name": "Petro", "SecondName": "Volovec", "Phone": "+30xxxxx", "Status": "Active" }, { "Name": "Maxim", "SecondName": "Kopernic", "Phone": "+30xxxxx", "Status": "Active" }] } } } } Должен быть отсортирован по ИД Пира. (таблица в MySQL Peers) peer_id, peer_info, peer_timeinsert, по Event для Peer ( таблица Events) event_id, event_name. И т.к. у меня нет ИД для Guests, то я создал таблицу Guests, где сохраняю гостей с ИД. (guest_id [autoincr], guest_name, guest_event_id . И таблица GEvents, где хранятся все guests для каждого Event : gevent_id (autoinc), gevent_peer_id, gevent_event_id, gevent_name, gevent_guest_id, gevent_guest_name, gevent_guest_status Обработка пакета происходит так: 1. Смотрю Header -> ModuleId. (Значения 1-10) 2. Если Header -> ModuleId=1 -> Там будет информация только о Peer-e. Беру Peer Id. Запрос в таб Peers. Если нет такого Id, то записываю, если есть - обновляю. 2. Если Header -> ModuleId=2. Тогда Информация о Event и Guests. 2.1 Запрос в БД в Peers, SELECT peer_id FROM peers WHERE peer_id = [peer_id] Если нет результата, - закончили процедуру. 2.2 Если есть, запрос SELECT * FROM events WHERE event_id = [event_id]. Если нет -> вставляем Event 2.3 Если есть, запрос в БД SELECT guest_id, guest_name FROM guests WHERE guest_event_id = [event_id] и запоминаю в массиве. Благо тут максимум к одному Event прикреплено не больше 30 guests. Но Event ов около 300. 2.4 Начинаю цикл foreach($guest as $guest): 2.4.0 Нахожу guest_id из массива, где есть совпадения с $guest[name]. Если нет совпадений, вставляю Guest в таблицу Guests и выбираю ИД вставленного 2.4.1 Смотрю в GEvents, есть ли у нас такая запись: SELECT gevent_id FROM gevents WHERE gevent_peer_id =[peer_id] AND gevent_event_id = [event_id] AND gevent_guest_name =[guest_name]. 2.4.2 Если нет, то вставляю новую запись 2.4.3 Если есть, то делаю Update по gevent_id.2.5 Конец цикла. 2.6 Конец обработки.Думаю, проще уже некуда. Во всех таблицах Primary_KEY по Id. Ну и Хедеры я сортирую с помощью SWITCH, где для каждого вызывается своя функция сортировки.
обычно как раз база сортирует всё. это делается иначе. смотри синтаксис insert ... on duplicate key update я не понял, ты что, херачишь запросы в цикле? --- Добавлено --- покажи код
какая разница для реляционной БД, в каком порядке записаны строки? Всегда можно сделать индекс на нужное поле.
С этой штукой надо быть аккуратным. Она сразу возвращает успешный ответ от базы. Если при этом, при вставке, ты не можешь гарантировать 100% успех, мало ли, по какой причине, то ты будешь с некоторой вероятностью просирать строки, которые, в противном случае, можно было бы хотя бы пометить в логе как пофейлившиеся. Я щас еще дальше шагну, зацени. Автор, опиши изначальную проблему, которую пытаешься решать, а не то, как ты видишь ее решение. Тут какой-то очень специфичный кейс, и есть вероятность, что это "проблема X-Y".
Вот код самой грузоподъемной функции. PHP: public function analyzeEvent($fullData){ // Debug('Start AnalyzeOdd at:'.time()); $result['result'] = 0; $result['msg'] = ''; $result['error'] = ''; $result_event = array(); $peer = $fullData['Peer']; $module = $fullData['Header']['ModuleId']; $data = array('peer_id'=>$peer['Id']); $gevent_peer_id = $peer['Id']; $table = 'peers'; $ev = $this->evExists($data,$table); // проверяет, существует ли Пир if(!empty($ev)){ // peer found // Debug(' Found Ev:'.$ev['peer_id'].' at'.time()); $outcome = $fullData['Peer']['Events']['Event']; // $result['msg'] .= ' Found Ev:'.$ev['peer_id']; $data=array('event_id'=>$outcome['Id']); $event = $this->evExists($data,'events'); if(empty($event)){ // event not found $newEv = $this->insertEv($outcome); if(empty($newEv)){ // cannot insert BType $result['result'] = 0; $result['error'] = 'Cannot insert Event '.$outcome['Id'].':'.$outcome['Name']; return $result; } } $gevent_event_id = $outcome['Id']; $cond_guests = array('guest_event_id = '.$outcome['Id']); $guests = $this->model->getAllData('guests',$cond_guests); // Debug($guests); foreach($outcome['Guests'] as $guesten){ $gevent_name_name = $this->replaceT($guesten['Name']); $adds = array_key_exists('Phone',$guesten)?$this->replaceT($guesten['Phone']):''; $conditions = array( 'gevent_peer_id'=>$gevent_peer_id, 'gevent_event_id'=>$gevent_event_id, 'gevent_name_name'=>$gevent_name_name, 'gevent_adds'=>$adds, 'gevent_type'=>$module ); $gevent = $this->evExists($conditions,'gevents'); if(empty($gevent)){ // not found gevent need insert //Debug('Not Found Guest:'.' at'.time()); //Debug($conditions); $found = -1; if(!empty($guests)){ foreach($guests as $guest){ if($guest['guest_name'] == $this->replaceT($guesten['Name'])){ $found = $guest['guest_id']; } } } if($found == -1){ // not found guest $newGuest = $this->insertGuest($outcome['Id'],$guesten); if(empty($newGuest)){ // cannot insert guest $result['result'] = 0; $result['error'] = 'Cannot insert guest '.$guesten['Name'].', Event:'.$outcome['Name']; return $result; }else{ $data=array('guest_event_id' =>$outcome['Id'],'guest_name'=>$guesten['Name']); $guestArr = $this->evExists($data,'guests'); $guest_id = $guestArr['guest_id']; } }else{ $guest_id = $found; } $input['gevent_peer_id'] = $peer['Id']; $input['gevent_event_id'] = $outcome['Id']; $input['gevent_event_name'] = $this->replaceT($outcome['Name']); $input['gevent_guest_id'] = $guest_id; $input['gevent_name_name'] = $this->replaceT($guesten['Name']); $input['gevent_adds'] = $adds; $input['gevent_phone'] = $guesten['Phone']; $input['gevent_status'] = $guesten['Status']; $input['gevent_type'] = $module; $newGevent = $this->model->insert($input,'gevents'); if(empty($newGevent)){ // cannot insert gevent $result_ins[$events['Id']]['result'] = 0; $result_ins[$events['Id']]['error'] = 'Cannot insert gevent '; }else{ $result_ins[$events['Id']]['result'] = 1; $result_ins[$events['Id']]['msg'] = 'Insert gevent successfull'; } }else{ // gevent found $input['gevent_phone'] = $guesten['Phone']; $input['gevent_status'] = $guesten['Status']; $cond = array('gevent_id = '.$gevent['gevent_id']); $update = $this->model->update($cond,$input,'gevents',$this->connDb); if(empty($update)){ // not update Gevent $result_ins[$gevent['gevent_id']]['result'] = 0; $result_ins[$events['Id']]['error'] = 'Cannot Update gevent:'.$gevent['gevent_id']; }else{ // updated $result_ins[$gevent['gevent_id']]['result'] = 1; } } } }else{ // peer not found $result['result'] = 1; $result['error'] = 'Not found Peer '; } if(!empty($result_ins)){ $result['result']=1; foreach($result_ins as $key=>$value){ if($value['result'] == 0){ $result['error'] .= $value['error']; $result['result'] = 0; } } }else{ $result['error'] .= 'No insert or Update'; } return $result; } Задача, которую пытаюсь решить. Приходят на сервер пакеты (до 1000 в секунду) с актуальными данными в формате json. Есть 10 Модулей - 10 разных вариантов данных. Когда приходит пакет, нужно сравнить, есть ли в базе данные о нем. Если есть, то обновить, если нет, то вставить. Как -то так. Сортировать, имеется ввиду данные, какие в какие таблицы совать. Ну да. Я ж тут проверяю соответствие каждого ко множеству, получается
Сравнивание идет по какому признаку? По некоему идентификатору? Для сравнивания ты сначала вытаскиваешь из БД значение, потом проверяешь, потом кладешь обратно? В таком случае ответ тебе уже был дан: И ничего не надо проверять. Просто отдавай базе на запись, она сама разберется. У тебя же всего два кейса - если нет данных, то создать, если есть, то обновить. Точка. Вытаскивать и проверять что-то руками нет смысла. Ты можешь накапливать запросы и отдавать их пачкой. Погляди синтаксис множетвенной вставки. Можно за один присест отдать базе хоть тыщщу строк. И работать оно будет чуть ли не в тыщщу раз быстрее, чем тыщща раз по разу.
В общем, я тут подумал, потестировал все и сделал такой вывод: 1. Нужен сервер помощней. 2. Входные данные записываю в МонгуДБ в разные коллекции, исходя из заголовков. 3. В этих коллекция раз в 5 минут делается чистка: выбираются записи и сортируются в МайэСКуэЛ (некоторые) и в "Архив". Таким образом в коллекциях сохраняются только актуальные данные и новые. 4. на сайте отображаются данные из коллекций. Если кто-то хочет посмотреть статистику за прошлые периоды, тогда достаем из архива. 5. МySQL использовать для технических нужд сайта, где нужны зависимости. Уже 4 дня тест проходит хорошо. Сервер не перегружен, все довольны. Всем спасибо за участие.
Я чет всё прочитал и так и не понял в чем затык. Написать код, который будет принимать любой пакет и приводить его к общему виду, если присутствуют уникальные идентификаторы, то это изи, если нет, то вычленить за что можно зацепится, на крайняк добавить module_id. Унифицированные данные в массив массивов и построить запрос INSERT INTO ON DUPLICATE KEY UPDATE (один запрос!).