712 читателя, 21 пост
Администрация
Модераторы
Блог поддержки веб-сервера «engine X» от Игоря Сысоева.
Новости, проблемы, решения, модули итд
Новости, проблемы, решения, модули итд


- procedure PROCESS_TRANS(p_account_id in number,
- p_operator_id in number,
- p_sum in number,
- o_result out number) is
-
- l_cnt number;
-
- cursor cur_account is
- select id
- from pay_account
- where id = p_account_id
- for update of balance;
-
- cursor cur_operator is
- select id
- from pay_operator
- where id = p_operator_id
- and total >= p_sum
- for update of total;
-
- begin
-
- o_result := -1; -- tansaction begins
-
- select count(1) -- if such operator exist?
- into l_cnt
- from pay_operator opr
- where opr.id = p_operator_id;
-
- if (l_cnt != 1)
- then
- o_result := -2; -- Operator not found!
- return;
- end if;
-
- open cur_account;
- open cur_operator;
-
- fetch cur_account into l_cnt; -- need to fetch to update rows later
- fetch cur_operator into l_cnt; -- 0/1 row in cursor as selected by prim_key
-
- if cur_account%notfound then
-
- o_result := -3; -- Account not found!
-
- elsif cur_operator%notfound then
-
- o_result := -4; -- Operator has not enough money
-
- else
-
- update pay_account
- set balance = (balance + p_sum)
- where current of cur_account;
-
- update pay_operator
- set total = (total - p_sum)
- where current of cur_operator;
-
- insert
- into pay_transaction
- (
- id,
- account_id,
- operator_id,
- money,
- datetime
- )
- values
- (
- pay_transaction_seq.nextval,
- p_account_id,
- p_operator_id,
- p_sum,
- sysdate
- );
-
- commit;
- o_result := 1; -- transaction successfully finished!
-
- end if;
-
- close cur_account;
- close cur_operator;
-
- end;
* This source code was highlighted with Source Code Highlighter.
- const char *bind_address = ":9000";
- const char *db_user_name = "orauser";
- const char *db_password = "pass";
- const char *db_conn_str = "comp:1521/xe";
-
- int main(int argc, char* const argv[] )
- {
- using namespace oracle::occi;
-
- int listenQueueBacklog = 4000;
- FCGX_Request request;
-
- if(FCGX_Init())
- exit(1);
-
- int listen_socket = FCGX_OpenSocket(bind_address, listenQueueBacklog);
- if (listen_socket < 0)
- exit(1);
-
- if (fchmod(listen_socket, S_IROTH | S_IWOTH))
- exit(1);
-
- if(FCGX_InitRequest(&request, listen_socket, 0)) exit(1);
-
- Environment*env = Environment::createEnvironment(Environment::DEFAULT);
- Connection *conn = env->createConnection(db_user_name, db_password, db_conn_str);
- Statement *stmt = conn->createStatement(
- "BEGIN PAY_SYS.PROCESS_TRANS(:v1,:v2,:v3,:v4); END;");
-
- while(FCGX_Accept_r(&request) == 0)
- {
- bool noException = false, succesful = false;
-
- long start = GetTickCount();
- char *params = FCGX_GetParam("QUERY_STRING", request.envp);
- int operatorID,accountID,moneySum, result=-1;
-
- operatorID = GetIntParam(params, "operator=");
- accountID = GetIntParam(params, "account=");
- moneySum = GetIntParam(params, "money=");
-
- if (operatorID <= 0 || accountID <= 0 || moneySum <= 0)
- {
- FCGX_FPrintF(request.out, "HTTP/1.0 503 Params Error\n");
- FCGX_FPrintF(request.out, "Content-type: text/html\r\n\r\n");
- FCGX_FPrintF(request.out, "wrong params<br>\n");
- FCGX_Finish_r(&request);
- continue;
- }
-
- try
- {
- stmt->setInt(1, accountID);
- stmt->setInt(2, operatorID);
- stmt->setInt(3, moneySum);
- stmt->registerOutParam(4,OCCIINT,sizeof(result));
- stmt->execute();
- result = stmt->getInt(4);
-
- if (result == 1)
- succesful = true;
- }
- catch(SQLException &sqlExcp)
- {
- error_log(sqlExcp.getMessage().c_str());
- }
-
- if (succesful)
- {
- FCGX_FPrintF(request.out, "HTTP/1.0 200 OK\n");
- FCGX_FPrintF(request.out, "Content-type: text/html\r\n\r\n");
- FCGX_FPrintF(request.out, "SQL result = %d", result);
- }
- else
- {
- FCGX_FPrintF(request.out, "HTTP/1.0 503 DatabaseError\n");
- FCGX_FPrintF(request.out, "Content-type: text/html\r\n\r\n");
- FCGX_FPrintF(request.out, "Database error occured");
- error_log("db error");
- }
-
- FCGX_Finish_r(&request);
- }
-
- conn->terminateStatement(stmt);
- env->terminateConnection(conn);
- Environment::terminateEnvironment(env);
-
- return 0;
- }
* This source code was highlighted with Source Code Highlighter.



- // вариант БЕЗ поддержки настроек через nginx.conf и ОБРАБОТКИ ошибок
-
- #include <ngx_config.h>
- #include <ngx_core.h>
- #include <ngx_http.h>
- #include <string.h>
- #include <oci.h>
-
- const text *db_user_name = (const text*)"orauser";
- const text *db_password = (const text*)"pass";
- const text *db_conn_str = (const text*)"comp:1521/xe";
- const text *command = (const text*)
- "BEGIN PAY_SYS.PROCESS_TRANS(:v1,:v2,:v3,:v4); END;";
-
- OCIEnv *env;
- OCISvcCtx *context;
- OCISession *session;
- OCIServer *server;
- OCIError *error;
- OCIStmt *statement;
- OCIBind *bnd1, *bnd2, *bnd3, *bnd4;
-
- int operatorID, accountID, sum, result;
- static char* ngx_http_payment_init (ngx_conf_t *cf,
- ngx_command_t *cmd, void *conf);
-
- // массив возможных опций модуля в nginx.conf
- static ngx_command_t ngx_http_payment_commands[] =
- { // задаем только одну основную опцию, включающую модуль в указанном Location
- { ngx_string("payment_enabled"),
- NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
- ngx_http_payment_init,
- NGX_HTTP_LOC_CONF_OFFSET,
- 0,
- NULL },
- ngx_null_command // терминирует массив
- };
- // callback-и, не используем
- static ngx_http_module_t ngx_http_payment_module_ctx =
- {
- NULL, /* preconfiguration */
- NULL, /* postconfiguration */
- NULL, /* create main configuration */
- NULL, /* init main configuration */
- NULL, /* create server configuration */
- NULL, /* merge server configuration */
- NULL, /* create location configuration */
- NULL /* merge location configuration */
- };
- // дескриптор модуля, включает в себя все параметры
- ngx_module_t ngx_http_payment_module =
- {
- NGX_MODULE_V1,
- &ngx_http_payment_module_ctx, /* module context */
- ngx_http_payment_commands, /* module directives */
- NGX_HTTP_MODULE, /* module type */
- NULL, /* init master */
- NULL, /* init module */
- NULL, /* init process */
- NULL, /* init thread */
- NULL, /* exit thread */
- NULL, /* exit process */
- NULL, /* exit master */
- NGX_MODULE_V1_PADDING
- };
-
- void SetCallParams(int oper, int account, int money)
- {
- if (bnd1 != 0) OCIHandleFree((dvoid*)bnd1, OCI_HTYPE_BIND);
- if (bnd2 != 0) OCIHandleFree((dvoid*)bnd2, OCI_HTYPE_BIND);
- if (bnd3 != 0) OCIHandleFree((dvoid*)bnd3, OCI_HTYPE_BIND);
- if (bnd4 != 0) OCIHandleFree((dvoid*)bnd4, OCI_HTYPE_BIND);
-
- // копируем из стека, чтобы иметь возможность передать валидный указатель
- result = 0;
- operatorID = oper;
- accountID = account;
- sum = money;
-
- // привязываем параметры к вызову SQL
- OCIBindByPos(statement, &bnd1, error, 1, &accountID,
- sizeof(accountID), SQLT_INT,(dvoid *) 0,
- (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT);
- OCIBindByPos(statement, &bnd2, error, 2, &operatorID,
- sizeof(operatorID), SQLT_INT,(dvoid *) 0,
- (ub2 *) 0, (ub2 *) 0,(ub4) 0, (ub4 *) 0, OCI_DEFAULT);
- OCIBindByPos(statement, &bnd3, error, 3, &sum, sizeof(sum), SQLT_INT,
- (dvoid *) 0, (ub2 *) 0, (ub2 *) 0, (ub4) 0,
- (ub4 *) 0, OCI_DEFAULT);
- OCIBindByPos(statement, &bnd4, error, 4, &result, sizeof(result),SQLT_INT,
- (dvoid *) 0,(ub2 *) 0,(ub2 *) 0,(ub4) 0,(ub4 *)0, OCI_DEFAULT);
- }
- // основной обработчик, вся работа выполняется здесь
- static ngx_int_t ngx_http_payment_handler(ngx_http_request_t *r)
- {
- // очищаем body-раздел
- ngx_int_t rc = ngx_http_discard_request_body®;
-
- if (rc != NGX_OK && rc != NGX_AGAIN)
- {
- ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
- "Failed ngx_http_discard_request_body()");
- return rc;
- }
-
- // строки в nginx хранятся как пара length-data,
- // а не как традиционный C-style zero-end
- // копируем для корректной работы парсера
- const int buflen = 100;
- char buf[buflen+1];
- int len = (r->args.len < buflen)? r->args.len : buflen;
- buf[len] = '\0';
- ngx_memcpy(buf, r->args.data, r->args.len);
-
- int successful = 0;
- int operator,abonent,money, result=-1;
-
- // вызов простейшего парсера
- operator = GetIntParam(buf, "operator=");
- abonent = GetIntParam(buf, "abonent=");
- money = GetIntParam(buf, "money=");
-
- if (operator > 0 && abonent > 0 && money > 0)
- {
- SetCallParams(operator, abonent, money);
-
- int retCode = OCIStmtExecute(
- context, statement, error, (ub4) 1, (ub4) 0,
- (OCISnapshot *) NULL, (OCISnapshot *) NULL,
- (ub4) OCI_COMMIT_ON_SUCCESS);
-
- if (retCode == OCI_SUCCESS)
- successful = 1;
- }
-
- r->headers_out.content_type.len = sizeof("text/html") - 1;
- r->headers_out.content_type.data = (u_char *) "text/html";
- r->headers_out.content_length_n = 0;
-
- if (successful == 1)
- r->headers_out.status = NGX_HTTP_OK;
- else
- r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR;
-
- // ... формирование body ответа и передача его nginx-у.. см примеры emiller
- }
-
- static char* ngx_http_payment_init(ngx_conf_t *cf,
- ngx_command_t *cmd,
- void *our_conf)
- {
- // set nginx handler
- ngx_http_core_loc_conf_t *core_conf =
- ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
- core_conf->handler = ngx_http_payment_handler;
-
- // oracle
- bnd1 = (OCIBind *) 0;
- bnd2 = (OCIBind *) 0;
- bnd3 = (OCIBind *) 0;
- bnd4 = (OCIBind *) 0;
-
- OCIEnvCreate((OCIEnv **)&env,(ub4)OCI_DEFAULT,
- (dvoid *)0,(dvoid * (*)(dvoid *, size_t))0,
- (dvoid * (*)(dvoid *, dvoid *, size_t))0,
- (void (*)(dvoid *, dvoid *))0,(size_t)0,(dvoid **)0);
- /* allocate a server handle */
- OCIHandleAlloc ((dvoid *)env, (dvoid **)&server,
- OCI_HTYPE_SERVER, 0, (dvoid **) 0);
- /* allocate an error handle */
- OCIHandleAlloc ((dvoid *)env, (dvoid **)&error,
- OCI_HTYPE_ERROR, 0, (dvoid **) 0);
- /* create a server context */
- int retCode = OCIServerAttach (server, error, db_conn_str,
- strlen((const char*)db_conn_str), OCI_DEFAULT);
-
- /* allocate a service handle */
- retCode = OCIHandleAlloc ((dvoid *)env, (dvoid **)&context,
- OCI_HTYPE_SVCCTX, 0, (dvoid **) 0);
- /* set the server attribute in the service context handle*/
- retCode = OCIAttrSet ((dvoid *)context, OCI_HTYPE_SVCCTX,
- (dvoid *)server, (ub4) 0, OCI_ATTR_SERVER, error);
- /* allocate a user session handle */
- retCode = OCIHandleAlloc ((dvoid *)env, (dvoid **)&session,
- OCI_HTYPE_SESSION, 0, (dvoid **) 0);
- // set up user & password for our session
- retCode = OCIAttrSet ((dvoid *)session, OCI_HTYPE_SESSION,
- (void*)db_user_name,(ub4)strlen((const char*)db_user_name),
- OCI_ATTR_USERNAME, error);
- retCode = OCIAttrSet ((dvoid *)session, OCI_HTYPE_SESSION,
- (void*)db_password,(ub4)strlen((const char*)db_password),
- OCI_ATTR_PASSWORD, error);
- // start session
- retCode = OCISessionBegin (context, error, session,
- OCI_CRED_RDBMS, OCI_DEFAULT);
-
- /* set the user session attribute in the service context handle*/
- retCode = OCIAttrSet ((dvoid *)context, OCI_HTYPE_SVCCTX,
- (dvoid *)session, (ub4) 0,
- OCI_ATTR_SESSION, error);
- OCIHandleAlloc((dvoid *) env, (dvoid **) &statement,
- OCI_HTYPE_STMT, (size_t) 0, (dvoid **) 0);
- OCIStmtPrepare(statement, error, command, (ub4)strlen((char*)command),
- (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT);
- return NGX_CONF_OK;
- }
* This source code was highlighted with Source Code Highlighter.
комментарии (30)
Одна заметка — в аналитике обычно принято учитывать не «Самый длительный запрос», а скорее «95% запросов выполнено в пределах ххх сек.»
И вообще, с интересом жду продолжения игры в OLTP, как про код приложения, так и про сервер и БД.
Ещё одна статья, в которую можно тыкать носом тех, кто говорит что lighttpd лучше нашего nginx :-)
Обязательно поэксперементируем… руки чешутся. =)
А как на счёт масштабируемости решения на основе модуля веб-сервера?
Попробуем засунуть Business layer в динамическую библиотеку. При усложнении транзакций, как написано в конце статьи, будем использовать неблокирующие запросы к БД, чтобы не тормозить цикл мультиплексора.
В окончательном варианте, конечно, спрячем во wrapper.
1. Уберите каунт — лучше обойтись экзистом или вообще вывалиться по no_data_found
2. Откажитесь от явного объявления курсоров.
3. Коммиты скидывайте пачками
4. Вместо установки флагов, лучше пользовать exception
Признаюсь действительно был не прав в некоторых местах:
1. Тут действительно лучше подошло бы лучше вроде:
или
Tom Kyte так же советует вариант через for.
2. Неявные курсоры конечно лучше, но я ими воспользовался, чтобы одновременно залочить строчки FOR UPDATE, посмотреть есть ли в них данные (чтобы сообщить об этом клиенту) и по ним же обновиться. Так делают (ниже в комментах). Конструкция:
не скомпилируется.
3. Пачками не коммитили, чтобы БД нагрузить и фиксировать все выполненые транзакции (вдруг БД упадет)
4. Excetion действительно лучше. My fault. Какая-то привычка из С++ сыграла, там иногда пытаются избегать exception т.к. они занижают производительность и их применять стараются только в случае явных ошибок программ.
Асинхронные вызовы на OCI мы планируем использовать в дальнейших опытах.
вам придется расковыривать OCI и переносить из нее точку синхроноизации в epoll_wait в nginx. Да, я верю что это возможно, но есть подозрение что придется этот OCI переделать.
И плохо что вы не написали что это только прототип. И в жизни это использовать нельзя.
Эта мысль подается и в заголовке, и в тексте. Все вышенаписаное — лишь эксперимент.
Претендовать на best practices по nginx даже и в мыслях не было.
С этим понятно, но мне не понятно как вы хотите и что пилить? Внести точку синхронизации из nginx в OCI? Возможно, но OCI закрыт.
Помойму вы не понимаете что вы хотите сделать, честно.
имел небольшой опыт использования логики модулей, отказался из-за указанных catap причинами.
проблема в регулировании таймаутов, надо делать коннект на неблокируемых сокетах
Кирилл спасибо за развернутый ответ.
Сейчас вы имеете, фактически, демонстрацию понятной проблемы — в асинхронном сервере (nginx/fastcgi) кто-то заблокировался. То, что overhead на блокировку если внести ее в nginx меньше, понятно. Только в чем тут фикус, новшество и зачем вообще, кроме письку показать, стоило писать сей текст?
Делать так в контексте высокой нагрузки нельзя. А зачем так извращаться в других случая — я не понимаю.
Спасибо за Ваш профессиональный и конструктивный подход.
решение на базе модуля ни есть наилучший вариант по надежности
концепция nginx такова, что модули должны содержать как можно менеьше внешних библиотечных зависимостей (в данном случае OCI, одно из тяжелых решений).
во первых — тяжелеет сам nginx
во вторых — что будет если соединение с Oracle отвалится?
грохнется весь сервер, по умолчанию в ОС время коннекта 60 сек…
При нагрузках — это будет пилипец всему и сразу…
в третьих, тесты тестами, а в реале: логика платежей тяжелее, разве мы строи систему для одного вида входящего запроса?
Как делать постоянный коннект и следить за ним, чтоб не отвалился, я пока не знаю.
Мутить пуул коннекций, сложновато.
Мутить обмен через shm & libevent? а отдельным клиентом общаться с БД и отдавать данные через shm…
тут есть много над чем подумать.
С удовольствием над этим подумаем. =)
С момента написания статьи многие вещи в построении Transport level OLTP уже пересмотрели.
Все вышеизложенное в статье с самого начала не претендовало на production решение, так и, выясняя по ходу дела больше подробностей про внутрености такого подхода (тем более модуля), понятно насколько все далеко от идеала!
Будем изучать дальше.
Так же любопытно попробовать обезопаситься от «отваливания» Oracle через ихнюю же технологию TAF (Transparent Application Failover), чтобы при «смерти» одного узла (почти) мгновенно он переназначил Virtual IP другому и тот ответил, что все плохо. TCP timeout будет незначительным. Только для таких экспериментов наверное нужно будет поднять среду Oracle RAC. =)
А идея про Connection pool звучит очень заманчиво!
я давно думаю над пуулом (MySQL + PHP ), но я не спец по Ораклу и ничего сказать не могу.
из моих исследований по MySQL, хотел сделать демон, который бы раздавал коннекции РНР-вокерам через shmem
но наткнутся на sigfault, для ликвидации которого надо было переписать более 33% кода mysqlclientlib Сизыфов труд.
как вариант — сделать демона, который запускал бы n MySQLClient вокеров, которые постоянно держали бы коннекцию с мускулем, но в этом случае при обмене из PHP, мы клали бы в shmem запрос, оповещали демона и ждали бы оповещения выполнения запроса, после этого брали данные из shm, формировали массив и возвращали в РНР код. т.е. по сути написал бы свою новую либу, а хотелось совместимости.
В общем, история про пул — это на какие грабли мне пришлось наступать и куда копать дальше.
Вам тоже спасибо, давно искал сравнительные характеристики nginx & lighttpd (про lighttpd2.0 узнал из комментов этого блога)
посмотри в сторону использования шаред мемори и libevent
это должно быть быстрее чем fastcgi
(будет время, сам покапаю в эту сторону)
мне тоже нужно будет запускать не легкие процессы…