`

服务器和客户端同步状态,客户端不能依赖服务器的响应

 
阅读更多

有一类系统,基本上所有操作都要求在线。在客户端产生的数据,直接提交到服务器,本地不存数据,或者仅保存少量缓存数据。这类应用有一个优势,就是客户端和服务器的数据始终是同步的,罕有两端不一致的情况。但是也有缺点,即对网络条件要求高,在网络条件不好的时候,用户操作需要等待,甚至无法正常使用

我们的一个APP,与上述系统不同,是支持离线操作的。数据在本地和服务器各有一份,然后通过同步机制来保持一致。这样做的缺点是提升了系统的复杂性,因为如果设计有缺陷,就很容易发生两端数据不一致的情况;另外就是如果客户端长期离线,存在数据丢失的风险。当然好处也很明显,即操作不依赖网络,所以非常流畅。即使在无网络条件下,也可以正常使用,只要在网络恢复以后,与服务器做数据同步就可以了

这类数据同步方案的一个常见错误,是客户端依赖服务器的响应结果。我们的老方案就踩了不少坑,本文总结几个常见的场景

比如这个场景:

1、客户端向服务器发起一个请求

2、服务器收到此请求以后,往数据库写入一条记录,返回成功响应

3、客户端收到响应以后,往本地也写入一条记录

看起来这个方案还不错,很直观,并且两端都有了同样的数据。但是这只是理想情况。有一天我们的系统访问量突然增大,导致服务器的负载升高,每个请求都处理得很慢,于是客户端超时了。所以客户端认为此次请求失败,没有往本地写数据,但是其实服务器稍后还是处理了这条请求。于是服务器就比客户端多了一条数据。更糟糕的情况是,客户端认为操作失败,继续反复发起请求,当然毫无例外地全部超时了,于是服务器里就多了N条记录……这是一个很容易模拟重现的场景,只要在服务端的代码里打一个断点,然后等客户端超时以后,再把断点走下去就行了

后来为了解决这个问题,我们试过增加防重复提交的机制,但是也不理想。因为首先,“重复提交”本身就是一个模糊的概念,什么样的提交算是重复提交呢,如果认为用户在同一个界面重复点击是重复提交,那么如果用户关掉窗口再来一次,是否也算重复提交呢?其次,数据同步机制本来就比较复杂,再加一个新的纠错机制,会让系统更加复杂,隐藏更多出BUG的可能性。最后,即使真的有了完美的防重复提交方案,也只能解决“多N条”的问题,还是解决不了“多一条”的问题

最后我们采取的办法是,先往本地插入一条记录,然后再提交到服务器去异步处理。由于是异步的,必然存在一个时间差(特别是客户端离线的时候,时间差还会比较长),所以会有短时间的两端数据不一致,但是只要最终一致,也就达到了目的

再比如这个场景:

1、客户端生成自从上次同步以后的新差量数据

2、将数据发到服务器,服务器处理,返回成功响应

3、客户端收到成功响应以后,删除这批数据

和上一个场景也有类似的问题,服务端处理得慢,导致客户端超时,差量数据没有被清除,但是这些数据实际上已经写入了服务器。于是下次同步的时候,重复的数据就又发到了服务器;此外还有另一个BUG,由于上报的时候,有2份差量数据(上次未清除的,此次新生成的),所以上报也发生了错误

后来我们修改了这2个BUG,首先是服务器收到请求以后,不处理已经处理过的数据;客户端则是每次只上报最新的那份差量数据;另外还被迫加入了错误数据的清理机制……为了修复一个BUG,代码又复杂了很多

其实更好的做法,应该是客户端每次要同步的时候,都重新生成一份新的差量,上报以后,即使没有收到响应,也应该把这份差量删除。这样就避免了客户端堆积多份差量文件的情况。然后接下来,就面临一个选择:在没有收到响应的时候,应该怎么标识哪些数据是“新”的。这个时候,服务器的状态是不可知的(而不是确定的成功或者失败),如果假设服务器操作成功,那么就应该在客户端上把这些数据都标识成旧的,下次不要再提交;相反,如果假设服务器操作失败,就应该保留这些数据的状态,下次再提交上去。前者的风险在于,如果服务器真的处理失败了,客户端又不再提交这些数据,那么这部分数据就再也没有机会同步到服务器。后者的问题在于,服务器可能会重复写入。两相权衡,我们认为前者的问题更严重,因为会直接造成数据丢失,所以最终选择了后面的方案,但是在服务器加上防止重复写入的机制

以上2种场景,本质上都是因为客户端和服务器是分离的,明明是一个操作,却不在一个事务里,并且服务器的状况对于客户端来说有时是不可知的。具体的解决办法,需要根据实际场景分别处理,但是有一点是明确的,即客户端不能依赖服务器的响应,而是需要单独的机制来保证一致性

再延伸一点考虑,为什么在线的应用没有这类问题,就是因为客户端没有数据,所以在整个系统里只有一份数据,自然不存在数据不一致。当然,如果允许多个客户端同时操作,还是需要考虑并发操作时的异常场景。这是另外一个主题,本文不展开。还有一种更复杂的情况,就是有多于2份数据,比如有2个客户端都能操作,并且都和服务器同步,这时候系统的复杂性又大大提升,因为1份数据,什么都不需要做;2份数据,仅需要处理同步;>2份数据,则还需要处理数据合并。比如某个会员,原本的余额是3000。然后在客户端A做了2个操作,余额变成2000;在客户端B做了3个操作,余额变成2500。这时候他的实际余额是多少呢?显然2000和2500都是错误的,需要合并数据,一种思路是客户端定时把数据上报到服务器,在服务器执行合并逻辑,把数据合并到一个中心节点,再下发到各个客户端。这个场景不在本文的讨论范围,以后再单独写

分享到:
评论

相关推荐

    Flash游戏网络模块源码(有服务器和客户端)

    通过对这个Flash游戏网络模块源码的深入学习,开发者不仅能理解AS在网络编程中的应用,还能掌握网络游戏的关键技术,如服务器架构、客户端同步机制等。对于想要进入游戏开发领域的初学者,这是一个不可多得的实践...

    局域网时间同步(服务器+客户端)

    通常,系统会依赖NTP(Network Time Protocol)来与外部时间源进行同步,但在这个特定情况下,由于服务器端程序不能满足特定需求,因此开发者决定自行为客户端编写时间同步工具。 NTP是一种用于同步网络上分布式...

    五子棋网络版客户端和服务器程序

    而"WZQServer.zip"很可能是五子棋服务器端的源代码,包含了处理网络请求、维护游戏状态和同步数据的逻辑。 总的来说,五子棋网络版客户端和服务器程序的开发涵盖了Unity3D的网络编程、用户界面设计和游戏逻辑实现等...

    DELPHI 7 使用TService 制作的时间同步服务器客户端,可安装在windows服务中

    综上所述,这个“DELPHI 7 使用TService 制作的时间同步服务器客户端”项目展示了如何结合使用TService、TThread和TIdSNTP组件来创建一个高效、可靠的Windows服务,该服务能够自动与NTP服务器同步时间,确保系统的...

    ntp源码包含服务器端和客户端

    本压缩包文件包含NTP的源代码,适用于服务器端和客户端的开发与配置,涵盖了四种不同的工作模式:组播、广播、对等和客户端/服务器模式。 1. **NTP服务器端**: NTP服务器是负责提供准确时间的节点,它通常连接到...

    delphi实现的目录同步,含客户端和服务器端源码

    服务器端创建一个监听套接字等待连接,客户端则通过建立连接发送请求,服务器响应并处理请求,完成文件的传输。 4. **Windows系统支持**:由于项目描述中提到“支持Windows系统”,这意味着该目录同步方案可能依赖...

    PongGame:作为服务器和客户端都打乒乓球

    这款游戏的核心在于实现服务器与客户端之间的实时通信,确保游戏的流畅性和同步性。下面我们将深入探讨PongGame中的关键技术点。 1. **网络编程基础**:PongGame的实现依赖于网络编程技术,主要是TCP或UDP协议。TCP...

    RTP+客户端+服务器端

    在RTP应用中,客户端通常负责发起媒体流请求,而服务器端则响应这些请求并发送媒体数据。客户端可能需要解码和播放收到的媒体流,而服务器端则需要编码和打包数据。在某些情况下,系统可能包含多个客户端和服务器,...

    多线程聊天服务器和客户端的C++boostasio实现_C++_Makefile_下载.zip

    在本项目中,"多线程聊天服务器和客户端的C++boostasio实现_C++_Makefile_下载.zip" 是一个包含使用C++编程语言和Boost.Asio库开发的多线程聊天应用程序。Boost.Asio是Boost库的一部分,它提供了一种高效、灵活的...

    cpp-WebSocket基于CBoostAiso的websocket客户端服务器库

    对于客户端,流程类似,但需要额外处理连接到服务器、发送握手请求和接收服务器响应的步骤。 在使用这个库时,需要注意的是,由于WebSocket协议涉及到网络I/O,因此在多线程环境中要特别注意同步问题,防止数据竞争...

    基于UDP协议的unity客户端,内涵服务端源码

    7. **客户端与服务器的状态管理**:例如连接状态、同步更新等。 通过分析这个项目,开发者不仅可以掌握Unity中的网络通信技术,还能深入理解UDP协议在实际应用中的工作原理和优缺点。此外,对于想要从事多人在线...

    UE4 TCP连接 客户端 服务器 C++项目实例

    在三次握手中,客户端和服务器通过发送SYN(同步序列编号)和ACK(确认)标志来建立连接。一旦连接建立,双方就可以进行数据传输。当通信结束时,通过四次挥手释放连接,确保所有数据都被正确接收。 UE4是一个强大...

    LiveMedia结构与服务器/客户端代码分析

    响应消息则包括RTSP版本、状态码、解释信息、消息头和消息体。 **简单的RTSP交互过程**: 1. 客户端向服务端发送OPTIONS请求,询问服务端支持的方法。 2. 服务端返回支持的所有方法。 3. 客户端发送DESCRIBE请求...

    C++实现RTSP_RTP服务器的源码.zip

    在RTSP中,服务器响应客户端的命令,管理媒体流的传输,而客户端则通过发送RTSP请求来控制这些媒体流。 RTP是一种传输层协议,主要负责承载实时数据,如音频和视频数据。RTP通常与RTCP(Real-time Transport ...

    异步tcp Socket 完整版(服务器、客户端)

    异步TCP Socket则是在传统同步Socket的基础上增加了异步处理能力,使得在网络通信的过程中,可以避免阻塞操作,提高系统的整体性能和响应速度。 #### 二、异步Socket的特点 异步Socket的主要特点包括: 1. **非...

    MQTT 安卓客户端实现

    4. **心跳机制**:MQTT协议支持心跳机制,客户端应定时发送`PINGREQ`报文,服务器响应`PINGRESP`,以检测连接是否正常。 5. **离线消息处理**:对于QoS 1和2级别的消息,客户端在离线时可能会丢失消息,需要实现...

    基于MFC的服务器客户端型聊天程序设计

    在本项目中,"基于MFC的服务器客户端型聊天程序设计"是一个利用Microsoft Foundation Class (MFC) 库开发的通信应用,适用于学习和实践网络编程。MFC是微软为Windows平台提供的一套C++类库,它封装了Windows API,...

    服务器时钟同步

    服务器时钟同步确保了所有系统之间的操作协调一致,这对于日志记录、交易处理、安全审计和其他依赖精确时间戳的活动来说是必不可少的。Delphi是一种流行的面向对象的编程语言,常用于开发桌面应用程序,本案例中它被...

    非常好的基于状态帧同步的战斗系统游戏源代码100%好用.zip

    这种同步方法的核心优点在于减少了对实时网络条件的依赖,因为它不需要每一帧都进行全量数据交换,而是只传输关键状态变化。这大大降低了网络带宽需求,提高了游戏的响应速度。 在战斗系统中,状态帧同步尤为重要。...

    同步amqp客户端_A synchronous amqp client

    综上所述,这个同步AMQP客户端库为Ruby开发人员提供了一个简单易用的工具,用于与AMQP服务器进行交互,其特点在于不依赖eventmachine,优化了消息处理和ack机制,适用于脚本和控制台环境。开发者可以通过阅读`README...

Global site tag (gtag.js) - Google Analytics