实现一个简单的服务端推送方案

35 篇文章 0 订阅

客户端和服务端的交互有和拉两种方式:如果是客户端拉的话,通常就是Polling;如果是服务端推的话,一般就是Comet,目前比较流行的Comet实现方式是Long Polling。

注:如果不清楚相关名词含义,可以参考:Browser 與 Server 持續同步的作法介紹

先来看看Polling,它其实就是我们平常所说的轮询,大致如下所示:

Polling

Polling

因为服务端不会主动告诉客户端它是否有新数据,所以Polling的实时性较差。虽然可以通过加快轮询频率的方式来缓解这个问题,但相应付出的代价也不小:一来会使负载居高不下,二来也会让带宽捉襟见肘。

再来说说Long Polling,如果使用传统的LAMP技术去实现的话,大致如下所示:

Long Polling

Long Polling

客户端不会频繁的轮询服务端,而是对服务端发起一个长连接,服务端通过轮询数据库来确定是否有新数据,一旦发现新数据便给客户端发出响应,这次交互便结束了。客户端处理好新数据后再重新发起一个长连接,如此周而复始。

在上面这个Long Polling方案里,我们解决了Polling中客户端轮询造成的负载和带宽的问题,但是依然存在服务端轮询,数据库的压力可想而知,此时我们虽然可以通过针对数据库使用主从复制,分片等技术来缓解问题,但那毕竟只是治标不治本。

我们的目标是实现一个简单的服务端推方案,但简单绝对不意味着简陋,轮询数据库是不可以接受的,下面我们来看看如何解决这个问题。在这里我们放弃了传统的LAMP技术,转而使用Nginx与Lua来实现。

Modified Long Polling

Modified Long Polling

此方案的主要思路是这样的:使用Nginx作为服务端,通过Lua协程来创建长连接,一旦数据库里有新数据,它便主动通知Nginx,并把相应的标识(比如一个自增的整数ID)保存在Nginx共享内存中,接下来,Nginx不会再去轮询数据库,而是改为轮询本地的共享内存,通过比对标识来判断是否有新消息,如果有便给客户端发出响应。

注:服务端维持大量长连接时内核参数的调整请参考:http长连接200万尝试及调优

首先,我们简单写一点代码实现轮询(篇幅所限省略了查询数据库的操作):

lua_shared_dict config 1m;

server {
    location /push {
        content_by_lua '
            local id = 0;

            local ttl = 100;

            local now = ngx.time();

            local config = ngx.shared.config;

            if not config:get("id") then
                config:set("id", "0");
            end

            while id >= tonumber(config:get("id")) do
                local random = math.random(ttl - 10, ttl + 10);

                if ngx.time() - now > random then
                    ngx.say("NO");
                    ngx.exit(ngx.HTTP_OK);
                end

                ngx.sleep(1);
            end

            ngx.say("YES");
            ngx.exit(ngx.HTTP_OK);
        ';
    }

    ...
}

注:为了处理服务端不知道客户端何时断开连接的情况,代码中引入超时机制。

其次,我们需要做一些基础工作,以便操作Nginx的共享内存:

lua_shared_dict config 1m;

server {
    location /config {
        content_by_lua '
            local config = ngx.shared.config;

            if ngx.var.request_method == "GET" then
                local field = ngx.var.arg_field;

                if not field then
                    ngx.exit(ngx.HTTP_BAD_REQUEST);
                end

                local content = config:get(field);

                if not content then
                    ngx.exit(ngx.HTTP_BAD_REQUEST);
                end

                ngx.say(content);
                ngx.exit(ngx.HTTP_OK);
            end

            if ngx.var.request_method == "POST" then
                ngx.req.read_body();

                local args = ngx.req.get_post_args();

                for field, value in pairs(args) do
                    if type(value) ~= "table" then
                        config:set(field, value);
                    end
                end

                ngx.say("OK");
                ngx.exit(ngx.HTTP_OK);
            end
        ';
    }

    ...
}

如果要写Nginx共享内存的话,可以这样操作:

shell> curl -d "id=123" http://<HOST>/config

如果要读Nginx共享内存的话,可以这样操作:

shell> curl http://<HOST>/config?field=id

注:实际应用时,应该加上权限判断逻辑,比如只有限定的IP地址才能使用此功能。

当数据库有新数据的时候,可以通过触发器来写Nginx共享内存,当然,在应用层通过观察者模式来写Nginx共享内存通常会是一个更优雅的选择。

如此一来,数据库就彻底翻身做主人了,虽然系统仍然存在轮询,但已经从轮询别人变成了轮询自己,效率不可相提并论,相应的,我们可以加快轮询的频率而不会造成太大的压力,从而在根本上提升用户体验。

突然想起另一个有趣的服务端推的做法,不妨在一起唠唠:如果DB使用Redis的话,那么可以利用其提供的BLPOP方法来实现服务端推,这样的话,连sleep都不用了,不过有一点需要注意的是,一旦使用了BLPOP方法,那么Nginx和Redis之间的连接便会一直保持下去,从Redis的角度看,Nginx是客户端,而客户端的可用端口数量是有限的,这就意味着一台Nginx至多只能建立六万多个连接(net.ipv4.ip_local_port_range),有点儿少。

当然,本文的描述只是沧海一粟,还有很多技术可供选择,比如Pub/SubWebSocketNginx_Http_Push_Module等等,篇幅所限,这里就不多说了,有兴趣的读者请自己查阅。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
安卓推送方案及比较 经常有朋友困扰于Android上面实现推送的技术,希望知道各种方案的优缺点、性能、开发难度等,于是特意写了这篇文章。 方案一: Google官方的服务: 但,通过对比研究发现C2DM机制存在以下缺点: 1)GCM要求Android系统必须是2.2以上的版本,所以对于不少2.2以前的系统没法推送 2)国内服务不稳定。而且不少国内的终端厂商纷纷把Google的服务去掉,替换上自己的。 3)需要用户绑定Google账号,但不少国内用户没有Google账号。 方案二: 利用MQTT协议,broker做代理服务器,但是随着用户的增多这个方案会有问题,因为broker的连接数有上限,到了一定程度后就无法连接了,这也就导致消息很难发送出去。 总之,连接数量有限制。 方案三: 基于XMPP协议,很多人都建议使用这个,谷歌官方的C2DM也是基于XMPP研发的,使用这个方案不会依赖android系统,也不依赖于谷歌服务器。 •XMPP协议比较费电费流量,对当前智能机的消耗太大 •在窄带网络和不稳定的(手机)网络都不是最优的选择。 方案四: 最近新出的一种是APNS,这个也不需要自己架设服务器(可以查看http://www.push-notification.mobi/),很简单,自己不用开发服务端。不过很少有人去用,不是很稳定 主要有以下特点: •快速集成:提供一种比C2DM更加快捷的使用方式,避免各种限制. •无需架设服务器:通过使用"云服务",减少额外服务器负担. •可以同时推送消息到网站页面,android 手机 •耗电少,占用流量少. 第三方服务: 目前也有不少第三方提供了推送服务,由于接入简单、服务比较专业可靠、还提供报表等,不少国内开发者和企业都采用这种方案。比如国外的parse、pubnub,国内的个推,都是这类。36kr前段时间有报道,新浪微博就是用的一家叫“个推”的第三方服务(有兴趣的朋友可以前往查看 http://www.igetuicom) 特点: •方便,易集成 •没有C2DM中的版本限制和必须用gmail绑定 •云服务,不用架设自己的服务器 •系统稳定、专业,能够承受高并发支持 •简单高效,并且省电

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值