如何设计一个并发安全的多用户ajax web应用程序

我有一个网页,显示了来自服务器的大量数据。通信是通过ajax完成的

每次用户交互和更改此数据(例如用户A重命名了某个内容)时,它都会告诉服务器执行此操作,服务器会返回新更改的数据

如果用户B同时访问页面并创建新的数据对象,它将再次通过ajax通知服务器,服务器将为用户返回新对象

在A的页面上,我们有一个重命名对象的数据。在B的页面上,我们有一个新对象的数据。在服务器上,数据同时具有重命名对象和新对象

当多个用户同时使用页面时,我可以选择哪些选项来保持页面与服务器同步

每次更改时,都可以避免锁定整个页面或将整个状态转储给用户等选项

如果有帮助,在这个特定示例中,网页将调用一个静态webmethod,该方法在数据库上运行存储过程。存储过程将返回它已更改的所有数据,不再返回。然后,静态webmethod将存储过程的返回转发给客户端

赏金编辑:

如何设计一个多用户web应用程序,它使用Ajax与服务器通信,但避免并发问题

即,在不存在任何数据或状态损坏风险的情况下,同时访问数据库上的功能和数据

概述:

  • 介绍
  • 服务器体系结构
  • 客户端架构
  • 更新案例
  • 立案
  • 冲突案例
  • 表现及;可伸缩性

你好,雷诺斯

我不会在这里讨论任何特定的产品。其他人提到的是一个很好的工具集,可以查看一下(可能会将node.js添加到该列表中)

从架构的角度来看,您似乎遇到了与版本控制软件相同的问题。一个用户签入对对象的更改,另一个用户希望以另一种方式更改同一对象=>冲突。您必须集成用户对对象的更改,同时能够及时高效地提供更新,检测和解决上述冲突

如果我站在你的立场上,我会发展成这样:

一,。服务器端:

  • 确定一个合理的级别,在这个级别上定义我称之为“原子工件”(页面?页面上的对象?对象中的值?)。这将取决于您的Web服务器、数据库和;缓存硬件、用户、对象等等,这不是一个容易的决定

  • 对于每个原子工件,有:

    • 应用程序范围的唯一id
    • 递增的版本id
    • 写访问的锁定机制(可能是互斥)
    • ringbuffer中的一个小历史记录或“变更日志”(共享内存对这些记录很有效)。单个键值对也可以,但扩展性较差。看见http://en.wikipedia.org/wiki/Circular_buffer
  • 一种服务器或伪服务器组件,能够有效地向连接的用户传递相关的变更日志。观察者模式是你的朋友

二,。客户端:

  • javascript客户端能够与上述服务器建立长期运行的HTTP连接,或者使用轻量级轮询

  • 一个javascript工件更新程序组件,当连接的javascript客户端通知所监视工件历史记录中的更改时,该组件刷新站点内容。(同样,观察者模式可能是一个不错的选择)

  • javascript工件提交程序组件,可请求更改原子工件,尝试获取互斥锁。它将通过比较已知的客户端工件版本id和当前的服务器端工件版本id来检测工件的状态是否在几秒钟前被另一个用户更改(javascript客户端的可用性和中的提交过程因素)

  • 一个javascript冲突解决程序,允许一个人做出正确的决定。你可能不想告诉用户“有人比你快。我删除了你的零钱。哭吧。”。从技术上的差异或更为用户友好的解决方案中选择许多选项似乎是可能的

那么它将如何滚动

案例1:用于更新的序列图类型:

  • 浏览器呈现页面
  • javascript“看到”工件,每个工件至少有一个值字段、唯一值和一个版本id
  • javascript客户端启动,请求“监视”从发现的版本开始的发现的工件历史(旧的更改不有趣)
  • 服务器进程记录请求并持续检查和/或发送历史记录
  • 历史记录条目可能包含简单通知“工件x已更改,客户端pls请求数据”,允许客户端独立轮询或完整数据集“工件x已更改为值foo”
  • javascript工件更新程序会尽其所能在新值被更新后立即获取新值。它执行新的ajax请求或由javascript客户端提供反馈
  • 页面DOM内容更新后,用户可以选择得到通知。历史观察继续

案例2:现在提交:

  • 工件提交者从用户输入中知道所需的新值,并向服务器发送更改请求
  • 已获取服务器端互斥
  • 服务器收到“嘿,我从版本123知道工件x的状态,让我将其设置为value foo pls。”
  • 如果工件x的服务器端版本等于(不能小于)123,则接受新值,生成新版本id 124
  • 新的状态信息“更新到版本124”和可选的新值foo放在工件x的ringbuffer(changelog/history)的开头
  • 服务器端互斥被释放
  • 请求工件提交者很高兴收到提交确认和新id
  • 同时,服务器端服务器组件保持轮询/将环形缓冲区推送到连接的客户端。所有观察工件x缓冲区的客户端都将在其通常的延迟内获得新的状态信息和值(参见案例1)

案例3:冲突:

  • 工件提交者从用户输入中知道所需的新值,并向服务器发送更改请求
  • 同时,另一个用户成功地更新了相同的工件(参见案例2),但是由于各种延迟,我们的另一个用户还不知道这一点
  • 因此获得服务器端互斥(或等待“更快”的用户提交更改)
  • 服务器收到“嘿,我从版本123知道工件x的状态,让我将其设置为value foo。”
  • 在服务器端,工件x的版本现在已经是124。请求的客户端无法知道他将要覆盖的值
  • 显然,服务器必须拒绝更改请求(不包括覆盖优先级),释放互斥,并将新版本id和新值直接发送回客户端
  • 面对一个被拒绝的提交请求和一个变更请求用户还不知道的值,javascript工件提交者引用冲突解决程序,它向用户显示并解释问题
  • 智能冲突解决程序JS向用户提供了一些选项,允许用户再次尝试更改该值
  • 一旦用户选择了一个他认为正确的值,该过程将从案例2开始(如果其他人更快,则从案例3开始)

关于性能和性能的几句话;可伸缩性

HTTP轮询与HTTP“推送”

  • 轮询创建请求,每秒一个,每秒5个,不管您认为延迟是可接受的。如果您没有很好地配置(Apache?)和(php?)以成为“轻量级”启动器,那么这对您的基础架构来说可能相当残酷。最好在服务器端优化轮询请求,使其运行的时间远远少于轮询间隔的长度。将运行时间一分为二可能意味着将整个系统负载降低50%
  • 通过HTTP进行推送(假设webworkers离支持他们太远)将要求每个用户始终有一个apache/lighthttpd进程可用。为每个进程保留的驻留内存和系统总内存将是您将遇到的一个非常确定的扩展限制。减少连接的内存占用是必要的,同时限制在每个连接中完成的连续CPU和I/O工作量(您需要大量的睡眠/空闲时间)

后端扩展

  • 忘记数据库和文件系统,您将需要某种基于共享内存的后端来进行频繁轮询(如果客户端不直接轮询,则每个正在运行的服务器进程都将轮询)
  • 如果你选择memcache,你可以更好地扩展,但它仍然很昂贵
  • 即使您希望有多个前端服务器进行负载平衡,提交的互斥锁也必须全局工作

前端缩放

  • 无论您是轮询还是接收“推送”,请尝试在一个步骤中获取所有受关注工件的信息

“创造性”调整

发表评论