`

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

 
阅读更多

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

客户端和服务端的交互有和拉两种方式:如果是客户端拉的话,通常就是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/SubWebSocket等等,篇幅所限,这里就不多说了,有兴趣的读者请自己查阅。

This entry was posted in Technical and tagged Lua, Nginx by 老王. Bookmark the permalink.

23 thoughts on “实现一个简单的服务端推方案

分享到:
评论

相关推荐

    若依集成CIM(即时推送系统)实现将服务端修改为SpringBoot+Vue前后端分离版代码.rar

    CIM是一套基于mina或netty框架下的推送系统,或许有一些企业有着自己一套即时通讯系统的需求,那么CIM为您提供了一个解决方案,目前CIM支持websocket,android,ios,桌面应用,系统应用等多端接入支持,可应用于移动...

    MQTT方案消息推送安卓手机端+php服务端案例

    MQTT方案消息推送安卓手机端+php服务端案例,MQTT方案消息推送安卓手机端+php服务端案例,

    Android 内置RTSP/RTMP服务器,实现局域网内视频推流与播放 Demo

    本方案实现了如下功能 : 平板端内置RTSP/RTMP服务器 平板端获取摄像头的画面并同步进行RTMP推流 (目前仅支持了RTMP推流,未实现RTSP推流) 手机端支持对RTSP/RTMP视频流的播放 具体可以看我的博客 : ...

    安卓推送方案及比较

    经常有朋友困扰于Android上面实现推送的技术,希望知道各种方案的优缺点、性能、开发难度等,于是特意写了这篇文章。 方案一: Google官方的服务: 但,通过对比研究发现C2DM机制存在以下缺点: 1)GCM要求Android...

    WebSocket客户端和服务端实例源码

    众所周知,Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客户端浏览器将信息呈现,这种机制对于信息变化不是特别频繁的应用尚可,但对于实时要求高、海量...

    Node.js实现数据推送

    后端推送数据的解决方案有很多,比如轮询、Comet、WebSocket。 1. 轮询对于后端来说开发成本最低,就是按照传统的方式处理Ajax请求并返回数据,在学校的时候实验室的项目一直都采用轮询,因为它最保险也最容易实现。...

    Android、iOS和Windows Phone中的推送技术详解

    推送技术的实现通常会使用服务端向客户端推送消息的方式。也就是说客户端通过用户名、Key等ID注册到服务端后,在服务端就可以将消息向所有活动的客户端发送。 实际上,在很多移动操作系统中,官方都为其提供了推送...

    基于Vue和SSM框架的康养App后端源码,集成JWT双Token与WebSocket消息推送

    2. 实时通信:利用WebSocket技术实现服务端与客户端之间的实时消息推送,提高了用户互动体验和数据同步的实时性。 本项目是专为移动端设计的康养应用后端解决方案,通过整合现代开发技术与高效通信机制,为用户提供...

    NodeJs实现简易WEB上传下载服务器

    项目上的需求是集群均可生成PDF文件或是访问PDF文件,但是没有...为了实施简易,现采取NodeJs来实现一个小程序。 不多说,直接上代码: server.js var express = require('express'); var url = require('url'); var f

    基于MQTT协议的通用智能家居系统设计与实现 毕业硕士论文,请使用CAJVIEW软件打开

    通过这种在硬件以及信息管理上的通用方案,本系统能够建立起一个可以适用于大部分家电管理的平台。在信息共享以及传输上,本系统采用 MQTT 推送协议作为通信方式,由于本系统中多数设备都属于嵌入式设备,设备的计算...

    Android、iOS和WindowsPhone中的推送技术

    推送技术的实现通常会使用服务端向客户端推送消息的方式。也就是说客户端通过用户名、Key等ID注册到服务端后,在服务端就可以将消息向所有活动的客户端发送。实际上,在很多移动操作系统中,官方都为其提供了推送...

    V免签 —— 个人开发者收款解决方案

    用户扫码付款 -&gt; 收到款项后手机通知栏会有提醒 -&gt; V免签监控端监听到提醒,推送至服务端-&gt;服务端根据金额判断是哪笔订单 安装 推荐使用宝塔面板安装,以下教程为宝塔面板安装教程,其他环境请参考自行配置 1、下载...

    CIM推送系统 v3.8.0

    为您提供CIM推送系统下载,CIM项目是基于...那么CIM为您提供了一个解决方案或者思路,目前CIM支持 websocket,android,ios,桌面应用,系统应用等多端接入支持,目前CIM服务端使用springboot搭建仅仅拥有消息推送的

    java websocket

    流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会...

    阿里云javasdk源码-CNIMStudy:即时聊天后台服务端代码

    友盟、腾讯、阿里、个推、小米、、、 ####2、存储平台 七牛 阿里OSS 百度、腾讯 ####个推配置: Appid:z3XMHzGxto75mpW5ExvQn7 AppSecret:hv9MIQpECo5j6qVI196gz7 AppKey:xO2Cm6I3do61pXLseLU0S2 MasterSecret:...

    CIM推送系统-其他

    我们平常使用第三方的推送SDK,如极光推送,百度推送,小米推送,以及腾讯信鸽等来支撑自己的移动端的业务,或许有一些用户自己实现即时通讯系统的需求,那么CIM为您提供了一个解决方案或者思路,目前CIM支持 ...

    JAVA上百实例源码以及开源项目源代码

    Tcp服务端与客户端的JAVA实例源代码 2个目标文件 摘要:Java源码,文件操作,TCP,服务器 Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多...

    android-chat-开源免费的IM,功能和UI符合国内习惯,比XMPP具有更适合移动端的协议,可以作为IM组件代替环信、融云、网易云信等云通讯和XMPP http://docs.wildfirechat.cn.zip

    仓库说明备注android-chatAndroid平台的SDK和Demoios-chatiOS平台的SDK和Demopc-chat基于Electron开发的PC平台Demoproto野火IM的协议栈实现serverIM serverapp server应用服务端Demorobot_server机器人服务端...

    JAVA上百实例源码以及开源项目

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

Global site tag (gtag.js) - Google Analytics