`
奔跑的羚羊
  • 浏览: 576836 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

[译]Nginx & Comet:低延迟信息推送

阅读更多

原文链接:Nginx & Comet: Low Latency Server Push

 

服务器推送(Server Push)是高效的、延迟低的数据交换方式。如果数据发送端与接收端都在互联网中公开可见,可以使用PubSubHubbub simpler Webhook 等方法完成任务。但是如果数据接收方在防火墙内、在内网或它只是一个浏览器(只可以向外发送数据请求,无法处理传入的数据),则实现服务器推送就更难了。如果你有冒险精神,你可以建立一个反向HTTP服务器 。如果你寻求可靠的解决方案,也许你要等待HTML5的WebSocket’s API 特性了。但如果你需要即刻可以实现的解决方案,你可以妥协一下,使用异步推送模式来代替,你可以使用Comet ,也被称为反向Ajax、HTTP服务器推送或HTTP流。

 

早在2006年Alex Russel 提出了一个不坏的技术思路,那就是长连接(Comet)概念:从客户端发起并保持一个连接直到数据出现并传送(long polling ),或者永远保持一个连接,通过它推送数据到客户端(streaming )。这两种方法的好处是数据传送非常及时。因此长连接技术广泛用于聊天应用(Facebook, Google, Meebo等)以及实现即时触发的机制。

 

Nginx 变成一个长连接服务器

 

实现长连接服务比较大的问题是特殊的隐形需求以及事件驱动web服务器能否高效处理众多的长连接。Friendfeed的Tornado 服务器是一个标准应用级服务器的好例子。另外,感谢Leo Ponomarev的努力,你现在可以用nginx_http_push_module 插件使你的Nginx服务器变身成为一台完全功能的长连接服务器。

 

使用自定义的一套框架结构,Leo的插件只提供两个对外的接口:一个是订阅者,一个是发布者。客户端连接Nginx服务器,创建针对一个频道的long- polling长连接并等待数据。同时,发布者只是简单的将数据使用POST方法提交给Nginx,插件收到数据后将它一个个发给等待的客户端。这表明发 布者不需要直接传递数据,它只是一个简单的事件产生器!

 

还有更强大的功能是,客户端和发布端可以建立任意的channel,插件提供消息队列功能,也就是说Nginx服务器会在客户端断线的情况下临时保存消息。队列消息可以按照时间、等待列表长度或内存限制大小来失效释放。

 

Nginx Ruby 配置例子

 

首先,你需要从源代码编译一个Nginx。解压源代码包,从GitHub获取插件的源码并放入Nginx的源码目录,然后使用下面的参数编译(./configure –add-module=/path/to/plugin && make && make install )。下一步,参考readme 文件和协议 文件,了解所有的参数选项。一个多客户端接收信息的配置例子如下:

 

> nginx-push.conf

 

# internal publish endpoint (keep it private / protected)
# 内部发布点(保证私有或不对外公开)
location /publish {
  set $push_channel_id $arg_id;      #/?id=239aff3 or somesuch
  push_publisher;
 
  push_store_messages on;            # enable message queueing  # 打开消息队列
  push_message_timeout 2h;           # expire buffered messages after 2 hours # 2小时后消息失效
  push_max_message_buffer_length 10; # store 10 messages # 保存10条消息
  push_min_message_recipients 0;     # minimum recipients before purge # 清除前面接收人数目
}
 
# public long-polling endpoint
# 公开的长连接接收点
location /activity {
  push_subscriber;
 
  # how multiple listener requests to the same channel id are handled
  # 每个channel  id能有多少客户端同时连接
  # - last: only the most recent listener request is kept, 409 for others.
  # – last: 只有最频繁请求的客户端能保持,其它连接返回409
  # - first: only the oldest listener request is kept, 409 for others.
  # – first: 只有最早连接的那个客户端可以保持,其它连接返回409
  # - broadcast: any number of listener requests may be long-polling.
  # – broadcast: 所有的客户端连接都会是长连接
  push_subscriber_concurrency broadcast;
  set $push_channel_id $arg_id;
  default_type  text/plain;
}

 

编译配置好Nginx,并且启动它,我们可以建立一个简单的广播场景,一个数据发送广播方,几个订阅信息接收方来测试我们的长连接服务器。

 

> comet-push-consume.rb

 

require 'rubygems'
require 'em-http'
 
def subscribe(opts)
  listener = EventMachine::HttpRequest.new('http://127.0.0.1/activity?id='+ opts[:channel]).get :head => opts[:head]
  listener.callback {
    # print recieved message, re-subscribe to channel with
    # 输出所获取的内容,并重新订阅这个频道
    # the last-modified header to avoid duplicate messages 
    # 使用header last-modified去忽略之前已经获取的数据
    puts "Listener recieved: " + listener.response + "\\n"
 
    modified = listener.response_header['LAST_MODIFIED']
    subscribe({:channel => opts[:channel], :head => {'If-Modified-Since' => modified}})
  }
end
 
EventMachine.run {
  channel = "pub"
 
  # Publish new message every 5 seconds
  # 每5秒钟发布一个新的消息
  EM.add_periodic_timer(5) do
    time = Time.now
    publisher = EventMachine::HttpRequest.new('http://127.0.0.1/publish?id='+channel).post :body => "Hello @ #{time}"
    publisher.callback {
      puts "Published message @ #{time}"
      puts "Response code: " + publisher.response_header.status.to_s
      puts "Headers: " + publisher.response_header.inspect
      puts "Body: \\n" + publisher.response
      puts "\\n"
    }
  end
 
  # open two listeners (aka broadcast/pubsub distribution)
  # 打开两个客户端
  subscribe(:channel => channel)
  subscribe(:channel => channel)
}

 

Download

nginx-push.zip (Full Nginx Config + Ruby client)

Downloads: 270 File Size: 2.7 KB

 

在上面的代码中,每5秒钟数据发布端向Nginx发出新的事件,nginx将数据通过长连接转发给两个订阅的客户端。当消息发送到客户端,服务器会断开 他们的连接,客户端会立即重连并等待下一次数据的到来。这样,就实现了Nginx将一个数据发布端到客户端的实时消息推送机制!

 

Long Polling, Streaming, and Comet in Production

Leo的模块还在开发 期,还需要时间来稳定下来,但它是一个需要关注的项目 。最近的更新计划都着重于bug修复,未来的计划里有描述要加入流的模式:代替现在每次数据获取后都要重连的情况(long polling),Nginx会保持连接,将数据一段段的实时传送给客户端。拥有这个功能后你能很方便的部署你自己的信息触发式API(例如:Twitter流 )。

 

 

分享到:
评论
5 楼 lenky0401 2010-03-09  
支持 不错
4 楼 奔跑的羚羊 2010-01-08  
<p>官方提供的聊天室的example,</p>
<p><a href="http://pushmodule.slact.net/js/dumbchat.js">dumbchat.js</a></p>
<p> </p>
<p>将整个demo整理了一下,</p>
<p>并且根据之前提供的nginx.conf修改index.html中的url,</p>
<p>见附件。</p>
<p> </p>
<p>官方地址:</p>
<p>http://pushmodule.slact.net/#examples</p>
<p> </p>
<p><img alt=""></p>
<p><img alt=""><img alt=""><img alt=""></p>
<p> </p>
3 楼 鹤惊昆仑 2010-01-08  
webhook是个好东西
2 楼 奔跑的羚羊 2010-01-07  
<p><span style="color: #0000ff;"><span><span>使用curl快速测试</span></span></span></p>
<p><span style="font-size: large;"><br></span></p>
<p><span style="color: #ff0000;"><span><span>新建一个订阅者</span></span></span></p>
<p>打开一个终端访问subscribe </p>
<pre name="code" class="java">curl -X GET http://localhost:8082/activity?id=0 </pre>
<p>可以看到HTTP请求被阻塞 </p>
<p> </p>
<p><span style="color: #ff0000;"><span><span>发布消息</span></span></span><br>打开另一个终端访问publish </p>
<pre name="code" class="java">curl -X POST http://localhost:8082/publish?id=0 -d "hello world"</pre>
 
<p><br><span style="color: #ff0000;"><span><span>订阅者收到消息</span></span></span><br>此时subscriber就可以收到字符串“Hello World”,完成HTTP请求。</p>
<pre name="code" class="java">curl -X GET http://localhost:8082/activity?id=0 </pre>
<p> </p>
<p> </p>
<p>一个简单测试就完成了。</p>
<p>通过这个例子,我们可以看出,如果订阅者没有收到消息,会被阻塞,直到发布者发布消息。当订阅者收到消息后,会立即断开。</p>
<p> </p>
<p> </p>
<p>再一起来看一下http header中信息</p>
<pre name="code" class="java">curl -X GET http://localhost:8082/activity?id=0 -verbose </pre>
<p> 这是终端会显示:</p>
 
<table style="background-color: #cdc9ce;" border="0"><tbody><tr>
<td># curl -X GET http://localhost:8082/activity?id=0 -verbose <br>* About to connect() to localhost port 8082<br>*   Trying 127.0.0.1... * connected<br>* Connected to localhost (127.0.0.1) port 8082<br>&gt; GET /activity?id=0 HTTP/1.1<br>User-Agent: curl/7.12.1 (i386-redhat-linux-gnu) libcurl/7.12.1 OpenSSL/0.9.7a zlib/1.2.2 libidn/0.5.6<br>Host: localhost:8082<br>Pragma: no-cache<br>Accept: */*<br>Referer: rbose<br><br>&lt; HTTP/1.1 200 OK<br>&lt; Server: nginx/0.8.31<br>&lt; Date: Thu, 07 Jan 2010 08:37:12 GMT<br>&lt; Content-Length: 33<br>&lt; <span style="color: #ff0000;">Last-Modified: Thu, 07 Jan 2010 08:37:10 GMT</span><br>&lt; Connection: keep-alive<br>&lt; Etag: 0<br>&lt; <span style="color: #ff0000;">Vary: If-None-Match, If-Modified-Since</span><br>* Connection #0 to host localhost left intact<br>* Closing connection #0<br>Hello world</td>
</tr></tbody></table>
<p> </p>
<p>从响应的头部可以看到<span style="color: #ff0000;">Last-Modified: Thu, 07 Jan 2010 08:37:10 GMT</span></p>
<p>这个就是发布者(publisher)上一次发布(publish)的时间<br>可以通过发送If-Modified-Since来获取指定时间之后的数据  </p>
<pre name="code" class="java">curl -X GET -H "If-Modified-Since:Thu, 07 Jan 2010 08:37:10 GMT" http://localhost:8082/activity?id=0 –verbose</pre>
<p>这时subscribe会重新被阻塞而不是接收下次publish的数据</p>
<p> </p>
<p> </p>
<p>到此,可以清晰的看出,nginx的push正是依靠这种“等待,断开,继续等待”,实现的长连接</p>
1 楼 奔跑的羚羊 2010-01-07  

push_authorized_channels_only [ on | off ]
  default: off
  context: http, server, location
  设置为On后则server必须先被publisher设置了push信息和id号,client才能获取到,否则会返回403,这个功能可以避免由于push id号设置不安全,被恶意用户把暴力猜测id号获得内容


push_subscriber  [ long-poll | interval-poll ]
  default: long-poll
  context: server, location
  设置client是长连还是短连。如果是long-poll则和普通的comnet pull方式一样,client发起请求后直到server返回信息或超时才停止;interval-poll则是如果server没有相关信息马上返回 304停止client请求,这个参数可以让应用更灵活。

push_subscriber_concurrency  [ last | first | broadcast ]
  default: broadcast
  context: http, server, location
  设置消息的发送方式,last表示同一个消息id号内容只发给最新链接上server的连接,first是最早连接的client获得内容,broadcast表示所有人连接获取这个id号的client都可以得到内容。

相关推荐

Global site tag (gtag.js) - Google Analytics