Git Product home page Git Product logo

socket.io-push's Introduction

socket.io-push Build Status Coverage Status

整合了小米,华为,友盟,苹果推送的统一解决方案

更有应用内超低延迟顺序(生产环境平均200MS以下)透传功能,支持websocket

白板

广播可支持同频道10w以上在线,每台前端(proxy)可支持5W以上长连接(取决于你的推送量)

notification

目前处于只修bug,不会更新新功能状态

视频介绍

NPM

为什么做这个东西?

我们Java后端开发,以提供HTTP API的方式,做了几个简单的增删改查式的APP,但是在开发一款直播类APP的时候遇到了问题

  • 直播间内的公屏,礼物广播,如何以最低延迟发给直播间内的用户?
  • 频道里都有谁在线? 都是哪些人?
  • 主播断线,或者与主播连麦的人断线了,如何知晓?
  • 如何推送离线系统通知(IOS,Android,小米,华为)?

参考其他一些团队的解决方案,大多是用c++写长连接服务器,甚至用Java的netty。开发维护成本太高,所以就有了这套推送系统,我们服务器依旧使用HTTP提供服务,下行推送,也只需要调用一个简单的HTTP接口,如 http://localhost:11001/api/push?topic=abc&json=hello 向abc这个直播间发送一条透传。

特点

  • 厂商通道: 透明集成了小米, 华为push,第三方推送由于政策问题,无法做到
  • 非厂商通道: 友盟推送 + socket.io 双通道客户端sdk去重, 可以享受友盟号称上万APP互相拉起, 大概可以增加非厂商通道50%以上送达率
  • IOS推送实现了可以部署代理节点, 可以通过国内专线->香港->苹果服务器, 极大提升成功率和吞吐能力
  • 支持浏览器, 微信小程序, Unity3D (完成度很低)
  • 与业务服务同机房部署,第三方服务无法比拟

性能

  • 目前使用的最高日活的一款APP有350W日活, 评估目前架构至少可以支持千万日活APP
  • 可以部署70台(实测)或者更多机器, 支持百万以上同时在线
  • 单机广播速度可以达到10W条/秒,如果只使用系统通知功能,单机支撑一个10W左右日活的APP,平均1W以上同时在线,几乎不占机器负载
  • 从推送接口调用,到客户端收到回包给服务器,RTT只有280m(线上平均延迟)

更新日志

  • 0.9.14 api增加删除设备的接口
  • 0.9.13 支持按uid,pushId查询推送送达率
  • 0.9.12 支持谷歌的fcm推送
  • 0.9.11 去掉apiRouter中的buffer机制,避免拥堵延迟
  • 0.9.10 更新调用华为新版接口(华为开发者后台需要配置apk的sha256指纹)
  • 0.9.9 修复自动重启进程的问题,更新node-apn版本
  • 0.9.7 修正小米notify_foreground配置未生效
  • 0.9.6 修正 push uid ttl未生效
  • 0.9.5 修正notificationBuffer删错方向bug
  • 0.9.4 修正demo后台bug
  • 0.9.3 notfication加个type用于统计
  • 0.9.3 删除redis-adapter一个指数级的调用
  • 0.9.1 重构redis-adapter,优化批量推送大量uid的性能
  • 0.8.97 接口支持post100mb以上数据

基本功能和实现原理

1. push (在线透传)

目标:
  • pushId: 对某个设备

  • topic: 已经订阅了某topic的设备列表,订阅关系在redis和socket.io实例里保存,如socket.io服务重启,会丢失,客户端重连上来会自动重新订阅建立关系

  • uid: 已绑定的设备列表,设备连接后读一次数据库,然后此关系以topic的方式实现

    实现原理:
      使用socket.io通道,只针对当时在线,也可以通过制定timeToLive参数实现重传, 无论push给任何目标,只有一次redis pub操作,不走数据库,可靠性,速度非常高
    

2. notification(手机系统通知栏)

目标:
  • pushId: 对某个设备
  • uid: 已绑定的设备列表
  • tags: 绑定了某tag的设备列表,存储在数据库持久化
  • pushAll: 推送所有设备
实现原理:

无论给哪个目标发,都要查一次mongodb,用于确定目标设备的类型和token。

  • ios设备,走苹果apn推送。
  • 小米和华为,走该厂商通道。
  • 其它设备(安卓或者浏览器等),走socket.io push通道。 如有上报umeng token,会调用友盟推送再发一次。安卓客户端有可能收到两次,SDK层做去重保证手机只弹出一次。

Quick Start

高级功能文档

相关文章

Q&A

  • 相比第三方推送有什么优劣?

优势:

  1. 同机房调用, 成功率100% vs 第三方 99.2%(我们调用小米接口成功率) 99.6%(我们调用华为接口成功率)
  2. 测试,正式环境,可以分开部署, 完全隔离
  3. 支持苹果推送多bundleId, 开发,发布,马甲版, 都可以自动匹配推送
  4. 苹果推送进程可以独立部署在香港/国外

劣势

  1. 需要自己运维部署服务器
  2. 如果需要扩容, 需要自己来评估, 第三方推送通常是给钱就可以了 =======

名词

  • push-server 推送服务器, 提供客户端长连接, http api接口
  • 业务服务器 push-server api的调用方
  • 客户端 业务app服务器
  • 长连接 客户端到push-server之间的socket.io连接
  • notification 发送通知栏消息, ios走apns通道, 华为,小米走厂商通道(如配置开启), 浏览器/android手机走长连接
  • push 协议透传, 走长连接通道. app主进程存活的时候才能收到.主要应用场景如直播间聊天,送礼物,股价实时推送
  • topic 服务器push广播的对象,类似于直播间/频道的概念, 客户端进入某直播间(id=001)后(topic="room001"),业务服务器可以向此topic发聊天push,subscribe了这个topic的客户端即可收到push
  • pushId 某个设备的唯一标识, app安装后生成的随机字符串, 用于服务器单播
  • uid 业务服务器用于标识某个用户的id,字符串类型.可以通过push-server的接口进行绑定,通过客户端SDK解除绑定
  • timeToLive 过期时间

socket.io-push's People

Contributors

censhanhe avatar davidnotes avatar gnosnah avatar huangzhilong avatar kk17 avatar rek001 avatar wangfeihang avatar xuduo avatar yywk avatar

Stargazers

 avatar  avatar

Watchers

 avatar

Forkers

pctaigm

socket.io-push's Issues

npm 包有问题

版本号:0.8.99
问题: admin PushTest 页点击推送报错
2017-09-22 10 13 52

原因:
发布的包中static/push/index.hmtl 文件和github上的不一致,多定义了pushAll。

对于小米系统的手机,接入小米push

目前手机系统对后台运行,要求越来越严格。
小米系统后台杀掉以后,无法重新启动,需要接入小米push系统。

大概实现方案

客户端启动的时候,需要判断下是否小米系统,是的话,则启动小米service获取小米推送token,上报服务器。
服务器单个通知,读取token,如发现是小米系统,走小米推送,不走长连接
全网通知,调用小米的全网推送接口,不走长连接

请教2个问题。

1:其他android 设备 。走长连接推送 或者友盟。 目前的情况是。android设备的app在后台运行或者被用户杀掉的时候。长连接会被杀掉。这个时候怎么推送。苹果是apns统一推送。华为小米的推送也是自己的可以保证程序被杀后 ,还可以推送。 其他设备你们是怎么保证推送的?

2:你们竟然用java实现后台。自然团队应该了解netty。netty本身的原理也是一个异步框架。操作基本上都是Future/Promise模型。也就是说和nodejs的本后原理类似。有一个可以替代node的框架Vertx。就是用netty实现的。netty可以保证利用多核的。而且本身是java写的。性能自然比node高。netty的稳定性也是毋庸置疑的。而node 的特点 只能让他多部署服务来满足多核的利用。 这样维护性来看也不高。
不知道你们选node的原因。

addArrivalInfo对APN推送支持标题和消息体的问题

现在苹果APN推送支持标题和消息体区分,但是socket.io-push的支持有问题,
如果推送notification为下面数据的话,

{
    "android": {
        "message": "消息体",
        "title": "消息标题",
        "payload": {...}
    },
    "apn": {
        "alert": {
            "body": "消息体",
            "title": "消息标题"
        }
    },
    "id": "rOW60mpqNLxY",
    "timestamp": 1511411873267
}

ArrivalStats的addArrivalInfo会出现异常

2017-11-23 12:37:53 worker:07 D/ArrivalStats addArrivalInfo  rOW60mpqNLxY { '$inc': { target_android: 1 },
  '$set':
   { notification: { body: '消息体', title: '消息标题' },
     timeStart: 1511411873285,
     ttl: 0,
     expireAt: 1514003873285,
     type: 'pushMany' },
  '$setOnInsert': { __v: 0 } } { ok: 0, n: 0, nModified: 0 } { MongooseError: Cast to string failed for value "{ body: '消息体', title: '消息标题' }" at path "notification"
    at CastError (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/error/cast.js:26:11)
    at SchemaString.cast (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/schema/string.js:458:9)
    at SchemaString.SchemaType._castForQuery (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/schematype.js:1064:15)
    at SchemaString.SchemaType.castForQueryWrapper (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/schematype.js:1019:17)
    at castUpdateVal (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/services/query/castUpdate.js:308:19)
    at walkUpdatePath (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/services/query/castUpdate.js:150:20)
    at castUpdate (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/services/query/castUpdate.js:71:18)
    at model.Query._castUpdate (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/query.js:2902:10)
    at _update (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/query.js:2752:23)
    at model.Query.Query.update (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/query.js:2547:10)
    at _update (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/model.js:2453:16)
    at Function.update (/home/webedit/socket.io-push/push-server/node_modules/mongoose/lib/model.js:2369:10)
    at ArrivalStats.addArrivalInfo (/home/webedit/socket.io-push/push-server/lib/stats/arrivalStats.js:37:26)
    at ArrivalStats.addPushMany (/home/webedit/socket.io-push/push-server/lib/stats/arrivalStats.js:81:10)
    at NotificationService.sendByDevices (/home/webedit/socket.io-push/push-server/lib/service/notificationService.js:35:22)
    at ApiRouter.sendNotificationByDevices (/home/webedit/socket.io-push/push-server/lib/service/apiRouter.js:162:30)
  message: 'Cast to string failed for value "{ body: \'消息体\', title: \'消息标题\' }" at path "notification"',
  name: 'CastError',
  stringValue: '"{ body: \'消息体\', title: \'消息标题\' }"',
  kind: 'string',
  value: { body: '消息体', title: '消息标题' },
  path: 'notification',
  reason: undefined }

Unity3D Support needed!

手游自搭建想使用socketio push推送解决方案,需要Unity3D 相关客户端API
谢谢~

.p8的证书怎么生成

在nodejs里面的这个文件:com.xuduopushtest.p8
是怎么生成的?没见过有.p8的证书。

支持启动时指定配置文件

启动时添加-o --config 参数,可以启动时指定配置文件
如push-server -o config.js
默认配置应该打包到npm里

启动使没有指定-o参数的时候
默认使用当前目录 config.js
如果没有,使用npm包里的config.js

demo优化

demo需要重写.重新实现下画图功能

华为推送全部通知栏时报错

请求推送通知栏报错 result_code:80200001 huawei result_code pushtype is not valid
但是打印的请求参数pushtype是2 应该为正常,没有在华为推送文档里找到使用的该接口,是否此接口更新了所以报参数错误

push test 页发送数据的格式不对

版本号:0.8.99
问题:
1.在PUSH Test 页面中输入聊天室内容{"message":"2452","nickName":"wow","type":"chat_message"},topic 输入chatRoom,点击推送;
2.在聊天室中https://localhost:12001/client/会报错.

原因:
main.js initClient中

pushClient.on('push', function (data) {
           addChatMessage(data);
       })

收到的数据是string格式的,导致后面报错
说明发送的数据格式不对。

修改方案:
pull request : #30
将 data => 转换为Json ojb => 再转换为 json string

进程异常退出后respawn有问题

index.js中进程异常退出后respawn有问题,调用参数错误导致respawn失败

  let spawn = (processType, count) => {
    if (count > 0) {
      const env = {
        processType,
        ip
      };
      for (let i = 0; i < count; i++) {
        const worker = cluster.fork(env);
        worker.on('exit', (code, signal) => {
          logger.error('worker(%s) exit, code:%s, signal:%s', worker.id, code, signal);
          setTimeout(() => {
            logger.info('respanw worker');
            spawn(env);
          }, 5000);
        });
      }
    }
  }

Android push 到达率统计问题

您好:
我看你们是通过APP收到push消息后给服务器回包的方式统计到达率,我想问一下 APP收到通知栏消息后,APP是如何给服务器回包的,有没有相关的demo可以参考

重构

每个类,构造函数,需要统一为以下这种直接返回实例
if (!(this instanceof NotificationService)) return new NotificationService(apnConfigs, redis, ttlService);

对this的引用需要改成
var self = this;
目前很多代码写的是outerThis

monitor项目优化

集成告警功能(提供一个可配置js方法回调)
监控的机房/ip,端口,可以配置

运行时MongoDB发生StrictModeError

请问这问题该如何解决?

8/7/2017, 9:11:20 PM worker:03 I/Mongo query  socket-io-push_devices findAndModify { _id: 'v82ytThWiweosl60' }
8/7/2017, 9:11:20 PM worker:03 D/DeviceService connect  { id: 'v82ytThWiweosl60',
  version: 1,
  platform: 'browser',
  topics: [ 'noti', 'chatRoom' ],
  lastUnicastId: null,
  lastPacketIds: {} } { _id: 'v82ytThWiweosl60',
  __v: 0,
  updateTime: 2017-08-07T13:11:20.967Z,
  socketId: '192.168.70.31:6QdyZ84e-AzMsBmsAAAE',
  createTime: 2017-08-07T13:11:11.121Z,
  tags: [] } null
8/7/2017, 9:11:21 PM worker:01 I/Mongo query  socket-io-push_sessions update { _id: '192.168.70.31:1' }
8/7/2017, 9:11:21 PM worker:02 I/Mongo query  socket-io-push_sessions update { _id: '192.168.70.31:2' }
8/7/2017, 9:11:21 PM worker:01 E/TopicOnline topicOnline.update  { MongooseError: Path "_id" is not in schema, strict mode is `true`, and upsert is `true`.
    at StrictModeError (/Users/joecwu/node_modules/mongoose/lib/error/strict.js:21:11)
    at cast (/Users/joecwu/node_modules/mongoose/lib/cast.js:199:17)
    at model.Query.Query.cast (/Users/joecwu/node_modules/mongoose/lib/query.js:3103:12)
    at model.Query.Query._castConditions (/Users/joecwu/node_modules/mongoose/lib/query.js:1144:10)
    at model.Query.Query._execUpdate (/Users/joecwu/node_modules/mongoose/lib/query.js:2321:8)
    at /Users/joecwu/node_modules/kareem/index.js:250:8
    at /Users/joecwu/node_modules/kareem/index.js:23:7
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)
  message: 'Path "_id" is not in schema, strict mode is `true`, and upsert is `true`.',
  name: 'StrictModeError',
  path: '_id' }

ttlSchema的packets type

我想提一个性能的问题
在ttlSchema里面定义的packets字段为字符串:

      packets: {
        type: [String]
      },

type是否可以改成Schema.Types.Mixed ? 这样就不需要保存消息对象时候将json转换成string和发送消息时候又要将string转换成json,毕竟如果发送的量很大的话string和json之间转换会消耗cpu性能。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.