我有一个网页,显示来自服务器的大量数据。通信是通过ajax完成的。
每次用户交互并更改此数据(假设用户 A 重命名某些内容)时,它都会告诉服务器执行该操作,然后服务器返回新更改的数据。
如果用户 B 同时访问该页面并创建一个新的数据对象,它将再次通过 ajax 告诉服务器,服务器将为用户返回新的对象。
在 A 的页面上,我们有带有重命名对象的数据。在 B 的页面上,我们有一个新对象的数据。在服务器上,数据既有重命名的对象又有新的对象。
当多个用户同时使用页面时,我可以选择哪些选项来保持页面与服务器同步?
诸如锁定整个页面或在每次更改时将整个状态转储给用户之类的选项是可以避免的。
如果有帮助,在此特定示例中,网页会调用在数据库上运行存储过程的静态 Web 方法。存储过程将返回它已更改的所有数据,仅此而已。然后,静态 Web 方法将存储过程的返回转发给客户端。
赏金编辑:
如何设计一个使用 Ajax 与服务器通信但避免并发问题的多用户 Web 应用程序?
IE。并发访问数据库上的功能和数据,没有任何数据或状态损坏的风险
概述:
- Intro
- 服务器架构
- 客户端架构
- 更新案例
- 提交案例
- 冲突案例
- 性能和可扩展性
嗨雷诺斯,
我不会在这里讨论任何特定的产品。其他人提到的是一个很好的工具集,值得一看(也许将 node.js 添加到该列表中)。
从体系结构的角度来看,您似乎遇到了与版本控制软件中相同的问题。一个用户签入对对象的更改,另一个用户想要以另一种方式更改同一对象=>冲突。您必须将用户更改集成到对象,同时能够及时有效地交付更新,检测并解决上述冲突。
如果我处于你的立场,我会开发出这样的东西:
1.服务器端:
2. 客户端:
一个 JavaScript 客户端,能够与上述服务器建立长期运行的 HTTP 连接,或者使用轻量级轮询。
一个 javascript 工件更新程序组件,当连接的 javascript 客户端通知监视工件历史记录中的更改时,该组件会刷新站点内容。 (观察者模式可能是一个不错的选择)
一个 JavaScript 工件提交者组件,可能会请求更改原子工件,尝试获取互斥锁。它将通过比较已知的客户端工件版本 ID 和当前的服务器端工件版本 ID 来检测工件的状态是否在几秒钟前被另一个用户更改(JavaScript 客户端的延迟和提交过程因素)。
一个 JavaScript 冲突解决程序,允许人类做出哪个更改是正确的决定。您可能不想只告诉用户“有人比您更快。我删除了您的更改。去哭吧。”。来自技术差异或更用户友好的解决方案的许多选择似乎都是可能的。
那要怎么滚呢...
情况 1:更新的序列图:
- 浏览器渲染页面
- javascript“看到”每个工件至少有一个值字段、唯一的和版本 ID
- javascript 客户端启动,请求从找到的版本开始“观察”找到的工件历史记录(旧的更改并不有趣)
- 服务器进程记录请求并持续检查和/或发送历史记录
- 历史条目可能包含简单的通知“工件 x 已更改,客户端请请求数据”,允许客户端独立轮询或完整数据集“工件 x 已更改为值 foo”
- javascript artifact-updater 会在得知新值已更新后立即获取新值。它执行新的 ajax 请求或由 javascript 客户端提供数据。
- 页面 DOM 内容被更新,用户可以选择收到通知。历史观察仍在继续。
案例 2:现在提交:
- 工件提交者从用户输入中知道所需的新值并向服务器发送更改请求
- 获取服务器端互斥锁
- 服务器收到“嘿,我知道版本 123 中的工件 x 的状态,让我将其设置为值 foo pls。”
- 如果工件 x 的服务器端版本等于(不能小于)123,则接受新值,并生成新版本 id 124。
- 新的状态信息“更新到版本 124”和可选的新值 foo 被放置在工件 x 的环形缓冲区的开头(变更日志/历史记录)
- 服务器端互斥体被释放
- 请求工件的提交者很高兴收到提交确认以及新的 ID。
- 同时,服务器端服务器组件不断轮询/推送环形缓冲区到连接的客户端。所有观察工件 x 缓冲区的客户端都将在其通常的延迟内获取新的状态信息和值(参见案例 1。)
情况 3:对于冲突:
- 工件提交者从用户输入中知道所需的新值并向服务器发送更改请求
- 与此同时,另一个用户成功更新了相同的工件(参见案例 2),但由于各种延迟,我们的其他用户还不知道这一点。
- 因此,获取服务器端互斥锁(或等待“更快”的用户提交更改)
- 服务器收到“嘿,我知道版本 123 中的工件 x 的状态,让我将其设置为值 foo。”
- 在服务器端,工件 x 的版本现在已经是 124。发出请求的客户端无法知道他将覆盖的值。
- 显然,服务器必须拒绝更改请求(不包括上帝干预的覆盖优先级),释放互斥锁,并足够好地将新版本 ID 和新值直接发送回客户端。
- 面对被拒绝的提交请求和请求更改的用户还不知道的值,JavaScript 工件提交者会调用冲突解决程序,向用户显示并向用户解释问题。
- 智能冲突解决程序 JS 向用户提供一些选项,允许用户再次尝试更改该值。
- 一旦用户选择了他认为正确的值,该过程就会从情况 2 开始(如果其他人更快,则再次从情况 3 开始)
关于性能和可扩展性的一些话
HTTP 轮询与 HTTP“推送”
- 轮询会创建请求,每秒 1 个、每秒 5 个,无论您认为可接受的延迟是什么。如果您没有将您的(Apache?)和(php?)配置得足够好以成为“轻量级”启动器,这对您的基础设施来说可能相当残酷。需要优化服务器端的轮询请求,使其运行时间远小于轮询间隔的长度。将运行时间分成两半很可能意味着将整个系统负载降低多达 50%,
- 通过 HTTP 推送(假设 webworkers 距离太远而无法支持它们)将要求您为每个用户提供一个 apache/lighthttpd 进程每时每刻。为每个进程保留的驻留内存和系统总内存将是您将遇到的一个非常确定的扩展限制。减少连接的内存占用是必要的,并限制每个连接中连续执行的 CPU 和 I/O 工作量(您需要大量的睡眠/空闲时间)
后端缩放
- 忘记数据库和文件系统,你会need某种基于共享内存的后端,用于频繁轮询(如果客户端不直接轮询,则每个正在运行的服务器进程都会直接轮询)
- 如果你选择内存缓存,你可以更好地扩展,但它仍然很昂贵
- 即使您希望有多个前端服务器来进行负载平衡,提交的互斥体也必须在全局范围内工作。
前端缩放
- 无论您是轮询还是接收“推送”,请尝试一步获取所有观看的工件的信息。
“创意”调整
- 如果客户端正在轮询并且许多用户倾向于观看相同的工件,您可以尝试将这些工件的历史记录发布为静态文件,允许 apache 缓存它,但当工件发生更改时在服务器端刷新它。这使得 PHP/memcache 不再参与一些请求。 Lighthttpd 在提供静态文件方面非常有效。
- 使用像 cotendo.com 这样的内容交付网络将工件历史记录推送到那里。推送延迟会更大,但可扩展性只是一个梦想
- 编写一个真实的服务器(不使用 HTTP),用户使用 java 或 flash(?)连接到该服务器。您必须在一个服务器线程中为许多用户提供服务。通过打开的套接字循环,执行(或委派)所需的工作。可以通过分叉进程或启动更多服务器进行扩展。不过,互斥体必须保持全局唯一。
- 根据负载场景,按工件 ID 范围对前端和后端服务器进行分组。这将允许更好地使用持久内存(没有数据库拥有所有数据),并且可以扩展互斥。不过,您的 JavaScript 必须同时维护与多个服务器的连接。
好吧,我希望这可以成为您自己想法的开始。我相信还有很多更多的可能性。
我非常欢迎对这篇文章提出任何批评或改进,维基已启用。
克里斯托夫·斯特拉森
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)