суббота, 4 декабря 2010 г.

10 тысяч запросов в секунду

Накидал очень простой (100 строк кода) прототип асинхронного веб-сервера на базе iolib. Он умеет принимать GET-запрос и не обращая на него внимание отдавать одну и ту же страницу. Практической пользы от него никакой, но для исследования вопроса вполне сгодится. Так вот, планка в 10 000 запросов в секунду (тестировал через ab) на моей машине была уверенно взята без каких-либо оптимизаций - временами скорость доходила до 11 000 запросов в секунду. Код не полностью корректен, но от него это и не требуется. Всё обработка ведётся в одном потоке. Собственно, код:
(asdf:operate 'asdf:load-op '#:iolib)
(asdf:operate 'asdf:load-op '#:iterate)

(defpackage #:http.test
(:use #:cl #:iter)
(:export #:start #:stop))

(in-package #:http.test)

(defparameter *event-base* nil)

(defparameter *reply*
(let ((endl #.(babel:octets-to-string (coerce #(13 10) '(vector (unsigned-byte 8)))))
(content "<html>
<head>
<title>Hello world</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>"))
(babel:string-to-octets
(with-output-to-string (out)
(write-string "HTTP/1.0 200 OK" out)
(write-string endl out)
(format out "Content-Length: ~A" (length content))
(write-string endl out)
(write-string "Content-Type: text/html" out)
(write-string endl out)
(write-string endl out)
(write-string content out))
:encoding :latin1)))

(defvar *bucket-pool* nil)

(defun get-bucket ()
(or (pop *bucket-pool*)
(make-array 4096 :element-type '(unsigned-byte 8))))

(defun free-bucket (bucket)
(push bucket *bucket-pool*))

(defun read-http-headers (socket callback)
(let ((headers (get-bucket))
(size 0))
(flet ((read-handler (fd event errorp)
(declare (ignore event errorp fd))
(multiple-value-bind (buffer count) (iolib.sockets:receive-from socket :buffer headers)
(declare (ignore buffer))
(incf size count))

(when (and (> size 4)
(equal '(13 10 13 10)
(coerce (subseq headers (- size 4) size) 'list)))
(iolib.multiplex:remove-fd-handlers *event-base*
(iolib.sockets:socket-os-fd socket)
:read t)
(free-bucket headers)
(funcall callback))))
(iolib.multiplex:set-io-handler *event-base*
(iolib.sockets:socket-os-fd socket)
:read #'read-handler))))

(defun send-http-reply (socket data callback)
(let ((curpos 0)
(total-length (length data)))
(flet ((write-handler (fd event errorp)
(declare (ignore fd event errorp))
(cond
((= curpos total-length)
(iolib.multiplex:remove-fd-handlers *event-base*
(iolib.sockets:socket-os-fd socket)
:write t)
(funcall callback))
(t (incf curpos
(iolib.sockets:send-to socket data :start curpos))))))
(iolib.multiplex:set-io-handler *event-base*
(iolib.sockets:socket-os-fd socket)
:write #'write-handler))))

(defun accept-connection (passive-socket)
(let ((active-socket (iolib.sockets:accept-connection passive-socket)))
(read-http-headers active-socket
(lambda ()
(send-http-reply active-socket
*reply*
(lambda ()
(close active-socket)))))))

(defun start (&optional (port 8080))
(setf *event-base* (make-instance 'iolib.multiplex:event-base))
(flet ((impl ()
(iolib.sockets:with-open-socket (acceptor :connect :passive
:address-family :internet
:type :stream
:external-format '(:utf-8 :eol-style :crlf)
:ipv6 nil)
(iolib.sockets:bind-address acceptor
iolib.sockets:+ipv4-unspecified+
:port port
:reuse-addr t)
(iolib.sockets:listen-on acceptor :backlog 5)

(flet ((accept (fd event errorp)
(declare (ignore fd event errorp))
(accept-connection acceptor)))
(iolib.multiplex:set-io-handler *event-base*
(iolib.sockets:socket-os-fd acceptor)
:read #'accept))

(iolib.multiplex:event-dispatch *event-base*)
(close *event-base*))))
(bordeaux-threads:make-thread #'impl
:name "*http-server*")))

(defun stop ()
(iolib.multiplex:exit-event-loop *event-base*))

3 комментария:

  1. А на клиентской части что было? И тестировалось ли это в пределах localhost, или клиент с сервером были разнесены на разные машины? На одной машине, как бы, не очень честно.

    ОтветитьУдалить
  2. @13-49
    Да принципиальной разницы где клиент, а где сервер я не увидел. Вот ща запустил с ноута - ну получилось чушь хуже, 8000 запросов в секунду где-то.

    Но, когда я делаю 1000 параллельных запросов (-с 1000) с localhost, то производительность заметно падает. А когда с соседний машины, то скорость не зависит от колличества парралельных запросов, что 10, что 1000 - получается примерно одно и то же. Наверное ab тормозит (

    ОтветитьУдалить