`

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

 
阅读更多

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

我们的一个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都是错误的,需要合并数据,一种思路是客户端定时把数据上报到服务器,在服务器执行合并逻辑,把数据合并到一个中心节点,再下发到各个客户端。这个场景不在本文的讨论范围,以后再单独写

分享到:
评论

相关推荐

    net-ipc:一个没有依赖关系的简单的node.js IPC客户端-服务器

    一个基于消息的简单IPC客户端/服务器,可通过套接字和TCP进行双向通信。 特征 承诺 用于本地通信的Unix / Windows套接字 TCP用于远程通讯 支持多个客户 支持请求响应,调查和广播 支持同步zlib-stream(需要安装“ ...

    E2EE互联网服务器套件2.7.2

    [Redis同步客户端] 支持常用的同步操作。发送请求后,立马返回执行结果。 [Redist异步客户端] 支持Redis异步操作。通常用于发布订阅时订阅消息使用。 [2.7.1.6] 2020/04/05 [网站服务器] - 修复了上传文件时接收某些...

    单页面和多页面开发及应用

    在AngularJS框架中,控制器和模型状态在客户端的浏览器中维护,从而使生成新页面不依赖与服务器的交互。 -Ember.js是基于模型-视图-控制器(MVC)软件架构模型的客户端侧JavaScript Web应用程序框架。它允许开发人员...

    分布式数据库设计方案.doc

    中间件监测到数据库变化并同步数据,数据同步 完成后客户端才会得到响应,同步过程是并发完成的,所以同步到多个数据库和同步到 一个数据库的时间基本相等;另外同步的过程是在事务的环境下完成的,保证了多份数 据...

    经典JAVA.EE企业应用实战.基于WEBLOGIC_JBOSS的JSF_EJB3_JPA整合开发.pdf

    5.6 同时作为客户端和服务器的 RMI程序 222 5.6.1 开发客户端程序 222 5.6.2 开发服务器端程序 223 5.7 本章小结 225 第6章 利用JMS实现企业消息处理 226 6.1 面向消息的架构和JMS概述 227 6.1.1 面向消息的应用架构...

    next-todo-list:在服务器端同步待办事项列表,以便多个ppl可以协作

    我最初使用redux,但最终没有使用它,因为它并不是处理服务器端实时同步的最佳方法。 因此,我重新实现了使用swr以更少的代码来实现相同的目的。下一步每次数据更改时更新UI。 目前,它并没有像您期望的那样发生,...

    超级有影响力霸气的Java面试题大全文档

     forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。...

    Java并发编程实战

    4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的委托 4.3.1 示例:基于委托的车辆追踪器 4.3.2 独立的状态变量 4.3.3 当委托失效时 ...

    Java 并发编程实战

    4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的委托 4.3.1 示例:基于委托的车辆追踪器 4.3.2 独立的状态变量 4.3.3 当委托失效时 ...

    java 面试题 总结

    forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。 redirect...

    Java并发编程(学习笔记).xmind

    响应不灵敏 吞吐率过低 资源消耗过高 可伸缩性较低 线程的应用场景 Timer 确保TimerTask访问的对象本身是线程安全的 Servlet和JSP Servlet本身要是线程安全的 正确协同一个...

    C/C++笔试题(附答案,华为面试题系列)

    ,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 23.ICMP是什么协议,处于哪一层? 答:Internet控制报文协议,处于网络层(IP层) 24.触发器怎么工作的? 答:触发器主要是通过事件进行触发而被...

    PHPADM网络广告管理系统V4.0.zip

    并且由于其所依赖的性能很好的web服务软件和以高速著称的数据库软件在Linux和Windows平台上均能运行,因此具有跨平台功能,它的代码不用做修改就可以直接运行在两个不同的操作系统中。 (4)多层及多维广告位管理:...

    java开源包1

    WebSocket4J 并未实现客户端通讯协议,所以不能用它来连接 WebSocket 服务器。 Struts验证码插件 JCaptcha4Struts2 JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 ...

    java开源包11

    WebSocket4J 并未实现客户端通讯协议,所以不能用它来连接 WebSocket 服务器。 Struts验证码插件 JCaptcha4Struts2 JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 ...

    java开源包2

    WebSocket4J 并未实现客户端通讯协议,所以不能用它来连接 WebSocket 服务器。 Struts验证码插件 JCaptcha4Struts2 JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 ...

    java开源包3

    WebSocket4J 并未实现客户端通讯协议,所以不能用它来连接 WebSocket 服务器。 Struts验证码插件 JCaptcha4Struts2 JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 ...

    java开源包6

    WebSocket4J 并未实现客户端通讯协议,所以不能用它来连接 WebSocket 服务器。 Struts验证码插件 JCaptcha4Struts2 JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 ...

    java开源包5

    WebSocket4J 并未实现客户端通讯协议,所以不能用它来连接 WebSocket 服务器。 Struts验证码插件 JCaptcha4Struts2 JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 ...

    java开源包10

    WebSocket4J 并未实现客户端通讯协议,所以不能用它来连接 WebSocket 服务器。 Struts验证码插件 JCaptcha4Struts2 JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 ...

Global site tag (gtag.js) - Google Analytics