-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hprose 推送功能的改进 #8
Comments
现有实现在讨论修改之前,先说一下现有实现是如何设计的。 首先,服务器端通过
当服务器发布了推送主题后(后面会专门介绍推送),客户端会跟服务器端保持一个长连接,如果达到超时时间,仍然没有任何消息推送给客户端,则返回响应:
此时,如果客户端仍然在线的话,则会立即再次发送获取推送主题的请求。服务器端通过这个方式可以获知客户端是否还在线。
当服务器端推送数据给客户端后,如果客户端在
但是需要注意的是, 因此, 对于推送频繁的服务器来说, 如果 因此, 客户端在订阅请求时,会发起对推送主题的调用:
服务器通过客户端发起请求的这个连接来返回推送数据,例如:
为服务器的返回的 Hprose 序列化数据。例如如果推送的数据为字符串“hello”,则返回数据为:
当服务器端没有数据推送时,客户端发起请求的连接会一直保持跟服务器相连,直到客户端超时或者服务器端超时。如果客户端先超时,客户端会断开该连接,并通过新连接发起请求。如果服务器先超时,服务器会返回:
客户端如果收到推送数据,则对订阅的回调方法进行回调,同时再次发起对订阅主题的请求。 如果客户端收到的是超时返回的 如果服务器返回异常,或者客户端和服务器之间发生网络异常,例如网络中断等,客户端会忽略异常,并再次发起对订阅的请求。 |
问题分析在考虑解决方案之前,先来分析一下问题产生的原因。 前两个问题实际上是同一个问题,即当同一个客户端发起对同一个主题的二次请求时,服务器端没有一个好的处理方法的问题。 因为这时不管是直接返回 null,还是抛出异常,按照目前的设计,如果前一个客户端并没有断开连接,那么前一个客户端收到这个响应之后,就会重新发起请求,而这个重新发起的请求,就会迫使刚才发起请求的客户端再次返回 null 或者抛出异常,这会引起不断交替请求的问题。目前的解决方法时,客户端超时后,客户端断开连接,然后再发起新的请求,这样当服务器端返回对前一个请求的响应时,前一个客户端就无法收到了,也就不会出现交替请求的问题了。但是如果不是这种情况下,发起的二次请求,例如用户编程时重复订阅导致的二次请求,就无法避免这种交替请求的问题了。 第三个问题来自于当服务器端高频推送时,客户端每次只能返回一个推送结果,因此会导致服务器推送数据的堆积,为了防止心跳检测的时间设置过长,导致服务器推送数据堆积,只能将心跳检测时间缩短,而缩短导致的结果就是容易误判客户端掉线,一旦误判客户端掉线,服务器堆积的消息就会被清空,当客户端再次连上来时,清空的数据就无法被返回了。 第四个问题来自于 第五个问题来自于退订主题的实现,目前的实现方式仅仅是删除客户端所保存的关于订阅主题的处理方法。而服务器端要等待服务器超时和心跳超时之后,才能判断客户端是否已经退订了主题,也就是说当服务器端没有向客户端推送任何消息时,服务器端需要至少 |
解决方案要解决上面这些问题,需要对客户端和服务器都做修改。下面的解决方案可以解决上面所说的几个问题,但是会与之前的实现不兼容,因此,如果使用新版本的推送功能,需要客户端和服务器都进行升级才行。 首先,客户端请求格式不变。 服务器如果有数据返回,返回格式为:
当仅有一个数据被推送时,返回值为:
这样如果服务器推送 null,返回是:
客户端不需要特殊处理,也可以正常接收服务器端推送的 null 了。 当服务器返回第一个推送结果,而客户端新的请求还没有到达服务器端时,如果服务器端积压了多个推送结果,当客户端新的请求到达时,服务器端可以一次返回所有积压的推送数据。这样一方面减少了不必要的请求响应,另一方面减少了服务器端积压的推送数据,心跳检测时间就可以设置的稍长一点,这样就可以避免掉线误判导致推送数据丢失的问题了。 当服务器端没有数据,而服务器端超时时间已到时,服务器端返回:
或者:
客户端收到这个空的数组结果后,重新提交请求。也就是用这个结果代替原来的 当客户端超时,服务器端未超时,或者客户端关闭后又打开,又或者什么其它原因客户端在第一个订阅请求还未返回时,客户端发送了第二个订阅请求,服务器端对前一个请求返回:
客户端如果能够收到该响应,则不再重新发起订阅请求。如果原来发送订阅请求的客户端早已关闭,客户端收不到这个响应,自然也不会重新发起订阅请求。 这样就可以解决前面说的前两个问题了。 如果服务器端推送了一个异常,服务器按照通常的方式将异常返回给客户端,客户端需要把这个异常传递给订阅的回调函数,用户可以自己根据情况来确定是否退订,不应该像之前版本那样忽略掉。而如果不是服务器返回产生的异常,而是因为网络中断产生的异常,则启动自动重试机制。 退订时,客户端除了完成之前所做的工作以外,还应该向服务器发送一个退订请求,请求格式如下:
即在之前表示订阅的调用中增加一个
而按照前面的约定,客户端收到此响应后,不会再发起订阅请求。订阅就此结束。 通过这种方式,就可以服务器的快速退订检测了。 优点:以上问题全部可以解决。 缺点:跟旧的实现不兼容,客户端和服务器端需要同步升级。 |
另外,如果客户端和服务器端传递共享数据的功能先实现的话,推送可以借助这个功能来简化使用。方法如下: 客户端 id 不再通过参数方式传递,而改为通过共享数据传递,客户端 id 设置为客户端的全局共享数据,这样不仅仅对推送的方法带有 id,每个普通调用上也都带有 id,因此,在服务器的可以实现为自动获取该 id,并表示为服务器端的 这样修改之后,客户端的订阅请求是这样的:
退订请求是这样的:
|
建议在服务器端的推送上加个功能,服务器端可以主动拒绝客户端的订阅。 |
嗯,我发现如果按照上面的协议实现的话,服务器端主动拒绝客户端的订阅是可以实现的,只要服务器端实现一个方法,然后返回给客户端
就可以实现主动退订。 |
建议在在客户端增加订阅事件 开启或关闭自动重连 |
在 hprose 2.0 的推送方案和上面的改进设计方案中,每订阅一个主题,就需要发起一个请求,如果底层连接是 HTTP 或 TCP 半双工的情况下,每个订阅就会独占一个连接,如果一个客户端订阅了许多个推送主题,将会占用多个连接,而浏览器对并发连接数是有限制的,因此如果能够所有订阅同时只使用一个或两个连接(一个或两个并发请求)的话,那么这个问题就可以很好的解决了。为此,上面的改进方案最好做进一步修改。 |
客户端必须首先生成一个唯一 之后,客户端在发起请求时,通过 hprose 3.0 的 因为 requestHeaders 部分的数据是相同的,所以下面在讨论推送时,不再写 requestHeaders 部分的数据内容。 订阅请求从每个主题一个方法改为统一方法,该方法用符号
表示订阅主题 当再有新的订阅时,比如
服务器收到该请求之后,前一个请求返回:
客户端收到该响应时,不再发起新的请求。 当服务器超时后,返回:
或者
表示没有推送数据,此时客户端重新发起请求:
因为已经订阅的主题在服务器端有记录,所以后面重新发起请求时,不需要发送订阅的主题参数。 当订阅的主题有数据推送时,服务器端返回:
也就是返回一个
只有包含推送数据的主题才包含键值对,没有推送数据的主题,将不返回键值对。 例如只有
客户端不需要特殊处理,也可以正常接收服务器端推送的 当服务器返回第一个推送结果,而客户端新的请求还没有到达服务器端时,服务器端可以把新的推送数据放入推送队列中存储。当客户端新的请求到达时,服务器端可以一次返回推送队列中积压的所有推送数据。这样一方面减少了不必要的请求响应,另一方面减少了服务器端积压的推送数据,心跳检测时间就可以设置的稍长一点,这样还可以避免掉线误判导致推送数据丢失的问题。 当客户端超时,服务器端未超时,或者客户端关闭后又打开,又或者什么其它原因客户端在第一个订阅请求还未返回时,客户端发送了第二个订阅请求,服务器端对前一个请求也同样返回:
客户端如果能够收到该响应,则不再重新发起订阅请求。如果原来发送订阅请求的客户端早已关闭,客户端收不到这个响应,自然也不会重新发起订阅请求。 客户端退订某个主题时,发送:
当服务器端发现该客户端订阅的所有主题都已经取消订阅后,会对之前的订阅请求返回:
因为客户端收到该结果时,表示不再发送请求,因此客户端的所有订阅就此结束。 当服务器的要主动拒绝客户端订阅某个主题时,可以返回:
客户端收到该消息后,可以触发
当服务器的在收到该请求之后,发现该客户端在服务器端还有其它有效的订阅,会继续按照前面的步骤进行。如果发现该客户端已经没有有效订阅的主题的话,则返回:
客户端不再发起新的请求,订阅结束。 |
为了清晰区分出用于接受推送消息的长链接调用和用于发送订阅和退订指令的短链接调用,我打算把上面方案中统一的一个方法 |
在具体实现时,客户端提供的 API 接口可以用 |
最终实现改为一个长连接调用用于获取推送消息,7个短链接调用用于发送指令和推送消息。下面是方法名列表:
推送的消息体为:
这样的结构。例如:
序列化后为:
这是指单一的数据,推送的数据是以数组返回,每个消息是数组中的一个元素。下面是实际实现后的一个推送过程中通讯的消息:
在上面的例子中,
是一个长连接请求,请求发出后,服务器不会立即返回,直到该请求被取消,或有推送消息,或者服务器超时才会返回。 其它指令性请求,都会立即返回结果。 另外,该实现是在 RPC 层实现的,并不涉及到具体的编解码协议,上面的例子中虽然通讯内容是 hprose RPC 协议格式,但是 hprose 3.0 的编码层被设计为可替换的,因此通讯部分的数据格式,可以完全是 jsonrpc 协议格式的,或者任何自定义协议格式的。但是推送本身的实现逻辑不变。 目前该设计已经在 TypeScript 版本中被实现。具体代码可以参加: 服务器:https://github.com/hprose/hprose-ts/blob/master/src/rpc/Broker.ts |
该推送优化方案有人在Java版本实现了吗? |
@Kenchizhuo 目前还没有。到目前为止,已经实现的版本只有 TypeScript 版本,到春节的时候,.NET 版本应该也能完成实现。到正月十五,Dart 版本应该能够完成。至于 Java 版本以后看看什么时候来做吧,Java 版本以后可能会考虑用 Java 11 来写,或者用 Kotlin 来写,底层通讯可能也会从原生 JDK 的 NIO 换成 Netty。所以,Java 版本的 3.0 版本相对于目前的版本改动应该会很大,所以开发周期应该不会很短。 |
在最新的实现中,为了减小批量推送时返回响应的大小,将返回的消息体:
|
Hprose 2.0 中增加了服务器推送的功能。但是该功能目前仍然有一些问题需要解决:
客户端超时必须要大于服务器端超时,否则,客户端可能会收不到或丢失部分服务器端推送的数据。
当客户端短时间内关闭并重启,且使用同一个客户端 id 来订阅推送主题时,客户端会收不到或丢失部分服务器端的推送数据。
当服务器对客户端进行高频数据推送时,客户端如果来不及响应,客户端会丢失部分服务器端的推送数据。
4、无法推送 null,无法推送异常。
5、无法快速退订主题。
要解决这些问题,就需要对现有实现做出修改,下面是如何修改的一些想法。
The text was updated successfully, but these errors were encountered: