Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/ictar/uwsgi-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ictar committed Dec 22, 2016
2 parents f631b21 + 66a66b7 commit 51d63b9
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 132 deletions.
53 changes: 25 additions & 28 deletions articles/FunWithPerlEyetoyRaspberrypi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,19 +156,18 @@ uWSGI进程将会在此之后立即退出,因为我们并没有告诉它要做
uwsgi::sharedarea_wait(0, 50);
这个函数This function suspends the current request until the specified shared area (the 'zero' one) gets an update. As this function is basically a busy-loop poll, the second argument specifies the polling frequency in milliseconds. 50 milliseconds gave us good results (feel free to try with other values).
这个函数挂起当前请求,直到指定的共享区域 ('zero'那个) 得到更新。由于这个函数基本上是一个频繁循环的poll,因此第二个参数指定了poll的频率,以毫秒为单位。50毫秒就能有不错的结果了(随意尝试其他值)。

.. code-block:: pl
uwsgi::websocket_send_binary_from_sharedarea(0, 0)
This is a special utility function sending a websocket binary message directly from the sharedarea (yep, zero-copy). The first argument is the sharedarea id (the 'zero' one) and the second is the position
in the sharedarea to start reading from (zero again, as we want a full frame).
这是一个特别的功能性函数,直接发送一个来自于共享区域(是哒,zero拷贝)的websocket二进制消息。第一个参数是共享区域id ('zero'那个),而第二个是共享区域中开始读取的位置 (再次是0,因为我们想要一个完整的帧)。

第三步:HTML5
*************

The HTML part (well it would be better to say Javascript part) is very easy, aside from the YUYV to RGB(A) transform voodoo.
HTML部分 (好吧,说是Javascript部分更恰当些) 是非常简单的,除了从YUYV到RGB(A)转换。

.. code-block:: html

Expand Down Expand Up @@ -238,8 +237,7 @@ The HTML part (well it would be better to say Javascript part) is very easy, asi
</body>
</html>

Nothing special here. The vast majority of the code is related to YUYV->RGBA conversion. Pay attention to set the websocket communication in 'binary' mode (binaryType = 'arraybuffer' is enough) and be sure to use
an Uint8ClampedArray (otherwise performance will be terribly bad)
这里没啥特别的。绝大部分的代码是关于YUYV->RGBA转换。注意设置websocket通信为“二进制”模式 (binaryType = 'arraybuffer'就够了),并且一定要使用Uint8ClampedArray (否则性能将会很糟糕)

准备观看
**************
Expand All @@ -248,59 +246,57 @@ an Uint8ClampedArray (otherwise performance will be terribly bad)
./uwsgi --plugin capture --v4l-capture /dev/video0 --http-socket :9090 --psgi uwsgi-capture/rpi-examples/eyetoy.pl --mule="captureloop()"
Connect with your browser to TCP port 9090 of your Raspberry Pi and start watching.
连接你的浏览器到你的树莓派到TCP端口9090,然后开始看看。

并发性
***********

While you watch your websocket stream, you may want to start another browser window to see a second copy of your video. Unfortunately
you spawned uWSGI with a single worker, so only a single client can get the stream.
当你看你的websocket流时,你或许想要启动另一个浏览器窗口来看看你的视频的第二份拷贝。不幸的是,你生成的uWSGI只有一个worker,因此只有一个客户端才能获取到流。

You can add multiple workers easily:
你可以轻松添加多个worker:

.. code-block:: sh
./uwsgi --plugin capture --v4l-capture /dev/video0 --http-socket :9090 --psgi uwsgi-capture/rpi-examples/eyetoy.pl --mule="captureloop()" --processes 10
Like this up to 10 people will be able to watch the stream.
就像这个,最多支持10个人观看视频流。

But coroutines are way better (and cheaper) for I/O bound applications such as this:
但是对于像这样的绑定I/O应用,协程是更好的方式 (并且更便宜):

.. code-block:: sh
./uwsgi --plugin capture --v4l-capture /dev/video0 --http-socket :9090 --psgi uwsgi-capture/rpi-examples/eyetoy.pl --mule="captureloop()" --coroae 10
Now, magically, we are able to manage 10 clients with but a single process! The memory on the RPI will be grateful to you.
现在,奇妙的是,我们能够只用一个进程来管理10个客户端!树莓派上的内存将会对你心存感激。

Zero-copy all the things
零拷贝所有的东西
************************

Why are we using the SharedArea?
为什么我们要使用共享区域?

The SharedArea is one of the most advanced uWSGI features. If you give a look at the uwsgi-capture plugin you will see how it easily creates a sharedarea pointing
to a mmap()'ed region. Basically each worker, thread (but please do not use threads with Perl) or coroutine will have access to that memory in a concurrently safe way.
共享区域是uWSGI最高级的特性之一。如果你看看uwsgi-capture插件,那么你会看到它是如何轻松创建一个指向一个mmap()区域的共享区域的。基本上,每个worker,线程(但是在Perl中请千万不要使用线程)或者协程将会以一种并发安全的方式访问那个内存。

In addition to this, thanks to the websocket/sharedarea cooperation API you can directly send websocket packets from a sharedarea without copying memory (except for the resulting websocket packet).
除此之外,多亏了websocket/共享区域合作API,你可以直接发送来自于一个共享区域的websocket包,而无需拷贝内存 (除了结果websocket包)。

This is way faster than something like:
这是比下面这样更快的方式:

.. code-block:: pl
my $chunk = uwsgi::sharedarea_read(0, 0)
uwsgi::websocket_send_binary($chunk)
We would need to allocate the memory for $chunk at every iteration, copying the sharedarea content into it and finally encapsulating it in a websocket message.
我们需要每次迭代的时候为$chunk分配内存,拷贝共享区域内容到它里面,最后在一个websocket消息中封装它。

With the sharedarea you remove the need to allocate (and free) memory constantly and to copy it from sharedarea to the Perl VM.
有了共享区域,你移除了不断分配(和释放)内存,以及将其从共享区域拷贝到Perl VM的需求。

其他方法
**********************

There are obviously other approaches you can follow.
显然你还可以使用其他方法。

You could hack uwsgi-capture to allocate a second sharedarea into which it will directly write RGBA frames.
你可以破解uwsgi-capture,分配直接写入RGBA帧的第二个共享区域。

JPEG encoding is relatively fast, you can try encoding frames in the RPI and sending them as MJPEG frames (instead of using websockets):
JPEG编码是相当快的,你可以尝试在RPI中编码帧,然后将其作为MJPEG帧发送 (而不是使用websockets):

.. code-block:: pl
Expand All @@ -318,14 +314,15 @@ JPEG encoding is relatively fast, you can try encoding frames in the RPI and sen
其他语言
***************

At the time of writing, the uWSGI PSGI plugin is the only one exposing the additional API for websockets+sharedarea. The other language plugins will be updated soon.
在写这篇文章的时候,uWSGI PSGI插件是唯一一个为websockets+sharedarea公开了额外API的插件。其他语言插件将在不久后进行更新。


More hacking
更多的hack
************

The RPI board is really fun to tinker with and uWSGI is a great companion for it (especially its lower-level API functions).
捣鼓RPI板子是相当有趣的,而uWSGI则是它的一个不错的伴侣 (特别是它的低层次API函数)。

.. note::

As an exercise left to the reader: remember you can mmap() the address 0x20200000 to access the Raspberry PI GPIO controller... ready to write a uwsgi-gpio plugin?
留给读者的一个练习:记住,你可以mmap()地址0x20200000来访问Raspberry PI GPIO控制器……准备好写一个uwsgi-gpio插件了吗?

63 changes: 29 additions & 34 deletions articles/OffloadingWebsocketsAndSSE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ uWSGI卸载

这个概念并非是个新东西,或者是uWSGI特有的。诸如nodejs或者twisted这样的项目已经用它多年了。

.. note:: an example of a webapp serving a static file is not very interesting, nor the best thing to show, but will be useful later, when presenting a real-world scenario with X-Sendfile
.. note:: 一个提供静态文件服务的web应用的例子并不非常有趣,也不是用来展示的最好的东西,但是在稍后展示一个使用X-Sendfile的真实世界的例子的时候,会游泳。

Immagine this simple WSGI app:
想象这个简单的WSGI应用:

.. code-block:: python
Expand All @@ -40,25 +40,23 @@ Immagine this simple WSGI app:
# do not do it, if the file is 4GB it will allocate 4GB of memory !!!
yield f.read()
This will simply return the content of /etc/services. It is a pretty tiny file, so in few milliseconds your process will be ready to process another request.
这将会简单返回/etc/services的内容。它是一个相当小的文件,因此在几毫秒时间内,你的进程将会准备好处理另一个请求。

What if /etc/services is 4 gigabytes? Your process (or thread) will be blocked for several seconds (even minutes), and will not be able to manage another request
until the file is completely transferred.
那要是/etc/services是4千兆字节呢?你的进程(或者线程)将会阻塞几秒(甚至几分钟),并且不能够管理其他请求,直到完全传输这个文件。

Wouldn't it be cool if you could tell another thread to send the file for you, so you will be able to manage another request?
如果你可以告诉其他线程为你发送这个文件,这样你就能够管理其他请求,岂不是很酷?

Offloading is exactly this: it will give you one ore more threads for doing simple and slow task for you. Which kind of tasks? All of those that can be managed
in a non-blocking way, so a single thread can manage thousand of transfers for you.
卸载就是这样的:它会为你提供一个或多个线程来为你完成一些简单并且慢点任务。哪种任务呢?所有那些可以以一种非阻塞方式管理的任务,因此单个线程就可以为你管理上千个传输。

You can see it as the DMA engine in your computer, your CPU will program the DMA to transfer memory from a controller to the RAM, and then will be freed to accomplish another task while the DMA works in background.
你可以将其视为你的电脑中的DMA引擎,你的CPU将会编程DMA来将内存从控制器传输到RAM,然后将会被释放,以完成其他任务,同时DMA在后台工作。

To enable offloading in uWSGI you only need to add the ``--offload-threads <n>`` option, where <n> is the number of threads per-process to spawn. (generally a single thread will be more than enough, but if you want to use/abuse your multiple cpu cores feel free to increase it)
要在uWSGI中启用卸载,你只需要添加 ``--offload-threads <n>`` 选项,其中<n>是每个进程要生成的线程数。 (一般而言,单个线程就够了,但是如果你想要使用/滥用你的多CPU核,那么随意增加)

Once offloading is enabled, uWSGI will automatically use it whenever it detects that an operation can be offloaded safely.
一旦启用了卸载,那么每当uWSGI检测到一个操作能够安全被卸载的时候,它将自动使用它。

In the python/WSGI case any use of wsgi.file_wrapper will be offloaded automatically, as well as when you use the uWSGI proxy features for passing requests to other server speaking the uwsgi or HTTP protocol.
在python/WSGI中,任何对wsgi.file_wrapper的使用将会被自动卸载,以及当你使用uWSGI代理特性来传递请求到其他使用uwsgi或者HTTP协议到服务器时。

A cool example (showed even in the Snippets page of uWSGI docs) is implementing an offload-powered X-Sendfile feature:
一个很酷的例子 (甚至在uWSGI文档的Snippets页面也有展示) 是实现一个卸载助力的X-Sendfile特性:

.. code-block:: ini
Expand Down Expand Up @@ -86,7 +84,7 @@ A cool example (showed even in the Snippets page of uWSGI docs) is implementing
wsgi-file = myapp.py
Now in our app we can X-Sendfile to send static files without blocking:
现在,在我们的应用中,我们可以X-Sendfile来在无阻塞情况下发送静态文件:

.. code-block:: python
Expand All @@ -95,12 +93,12 @@ Now in our app we can X-Sendfile to send static files without blocking:
return []
A very similar concept will be used in this article: We will use a normal Django to setup our session, to authorize the user and whatever (that is fast) you want, then we will return a special header that will instruct uWSGI to offload the connection to another uWSGI instance (listening on a private socket) that will manage the websocket/sse transaction using gevent in a non-blocking way.
在这篇文章中将会使用一个非常类似的概念:我们将会使用一个正常的Django来设置我们的会话,来认证用户,以及任意(快的)东东,然后我们会返回一个特别的头,它会指示uWSGI卸载连接到另一个uWSGI实例 (监听一个私有socket),这个实例将使用gevent,以一种非阻塞方式管理websocket/sse事务。

我们的SSE应用
-----------

The SSE part will be very simple, a gevent-based WSGI app will send the current time every second:
SSE部分将非常简单,一个基于gevent的WSGI应用将会每秒发送当前时间:

.. code-block:: python
Expand All @@ -125,13 +123,13 @@ The SSE part will be very simple, a gevent-based WSGI app will send the current
# send to the client
yield str(session)
Let's run it on /tmp/foo UNIX socket (save the app as sseapp.py)
让我们在/tmp/foo UNIX socket上运行它 (将应用保存为sseapp.py)

.. code-block:: sh
uwsgi --wsgi-file sseapp.py --socket /tmp/foo --gevent 1000 --gevent-monkey-patch
(monkey patching is required for time.sleep(), feel free to use gevent primitives for sleeping if you want/prefer)
(time.sleep()需要猴子补丁,如果你想要/喜欢的话,随意使用gevent原语来休眠)

(无趣的)HTML/Javascript
----------------------------
Expand All @@ -158,12 +156,12 @@ Let's run it on /tmp/foo UNIX socket (save the app as sseapp.py)
</body>
</html>

It is very simple, it will connect to /subscribe and will start waiting for events.
它非常简单,它将连接到/subscribe,并且将开始等待事件。

Django视图
---------------

Our django view, will be very simple, it will simply generate a special response header (we will call it X-Offload-to-SSE) with the username of the logged user as its value:
我们的django视图,将非常简单,它将简单生成一个特色的响应头 (我们讲称之为X-Offload-to-SSE),并且把登录用户的用户名作为它的值:

.. code-block:: python
Expand All @@ -172,13 +170,12 @@ Our django view, will be very simple, it will simply generate a special response
response['X-Offload-to-SSE'] = request.user
return response
Now we are ready for the "advanced" part.
现在,我们已经为“高级”部分准备好了。


Let's offload the SSE transaction
让我们卸载SSE事务
---------------------------------

The configuration could look a bit complex but it is the same concept of the X-Sendfile seen before:
配置看起来会有点复杂,但是它与之前看到的X-Sendfile概念相同:

.. code-block:: ini
Expand All @@ -195,11 +192,9 @@ The configuration could look a bit complex but it is the same concept of the X-S
; if X_OFFLOAD is defined, offload the request to the app running on /tmp/foo
response-route-if-not = empty:${X_OFFLOAD} uwsgi:/tmp/foo,0,0
The only "new' part is the use of ``disableheaders`` routing action. It is required otherwise the headers generated by Django
will be sent along the ones generated by the gevent-based app.
唯一“新的”部分是使用 ``disableheaders`` 路由动作。这是必须的,否则Django生成的头将会伴着由基于gevent的应用生成的头发送。

You could avoid it (remember that ``disableheaders`` has been added only in 2.0.3) removing the call to start_response() in the gevent app (at the risk of being cursed by some WSGI-god) and changing the Django view
to set the right headers:
你可以避免它 (记住,只在2.0.3添加了 ``disableheaders`` ),在gevent应用中移除到start_response()到调用 (冒着被一些WSGI神诅咒的风险),然后修改Django视图来设置正确的头部:

.. code-block:: python
Expand All @@ -209,7 +204,7 @@ to set the right headers:
response['X-Offload-to-SSE'] = request.user
return response
Eventually you may want to be more "streamlined" and simply detect for 'text/event-stream' content_type presence:
最终,你或许想要更加“精简”,并简单检测'text/event-stream' content_type存在:

.. code-block:: ini
Expand All @@ -225,9 +220,9 @@ Eventually you may want to be more "streamlined" and simply detect for 'text/eve
response-route-if = equal:${CONTENT_TYPE};text/event-stream uwsgi:/tmp/foo,0,0
Now, how to access the username of the Django-logged user in the gevent app?
现在,如何在gevent应用中访问Django登录用户的用户名呢?

You should have noted that the gevent-app prints the content of the WSGI environ on each request. That environment is the same
你应该注意到,gevent应用在每个请求中打印了WSGI环境变量的内容。That environment is the same
of the Django app + the collected headers. So accessing environ['X_OFFLOAD'] will return the logged username. (obviously in the second example, where the content type is used, the variable with the username is no longer collected, so you should fix it)

You can pass all of the information you need using the same approach, you can collect all of the vars you need and so on.
Expand All @@ -252,7 +247,7 @@ You can even add variables at runtime:
; if CONTENT_TYPE is 'text/event-stream', forward the request
response-route-if = equal:${CONTENT_TYPE};text/event-stream uwsgi:/tmp/foo,0,0
Or (using goto for better readability):
或者 (使用goto以获得更好的可读性):

.. code-block:: ini
Expand Down Expand Up @@ -293,7 +288,7 @@ This allows a more "elegant" approach (even if highly non-portable):
uwsgi.add_var("OFFLOAD_SERVER", "/tmp/foo")
return HttpResponse()
Now the config can change to something more gentle:
现在,配置可以修改成更优雅:

.. code-block:: ini
Expand Down Expand Up @@ -321,7 +316,7 @@ Now we can go even further. We will not use the routing framework (except for di
uwsgi.route("uwsgi", "/tmp/foo,0,0")
return HttpResponse()
and a simple:
以及一个简单的:

.. code-block:: ini
Expand Down
11 changes: 5 additions & 6 deletions examples/CPythonForkServer.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Fork Server with CPython
Fork Server和CPython
========================

我们的"base"应用 (/var/www中的myforkbase.py):
Expand All @@ -13,7 +13,7 @@ Fork Server with CPython
return ['Hello World']
The base vassal (in /etc/forkvassals/base.ini)
base vassal (/etc/forkvassals/base.ini中)

.. code-block:: ini
Expand All @@ -26,7 +26,7 @@ The base vassal (in /etc/forkvassals/base.ini)
fork-server = /run/forkme
and now two vassals inheriting from the base
然后现在是两个继承自base vassal的vassal

.. code-block:: ini
Expand Down Expand Up @@ -61,10 +61,9 @@ and now two vassals inheriting from the base
uwsgi --emperor /etc/forkvassals --emperor-collect-attr myfork-base --emperor-fork-server-attr myfork-base
the `--emperor-collect-attr` option tells the Emperor to search for a 'myfork-base' attribute in the [emperor] section, while `--emperor-fork-server-attr`
instruct it to use the parameter as the fork-server to connect to.
`--emperor-collect-attr` 选项告诉Emperor在[emperor]部分搜索一个'myfork-base'属性,而 `--emperor-fork-server-attr` 指示它把参数当成fork服务器来连接。

待办事项
====

The --emperor-collect-attr could be implicited by emperor-fork-server-attr
--emperor-collect-attr可以由emperor-fork-server-attr隐含

0 comments on commit 51d63b9

Please sign in to comment.