如何devise一个多用户ajax web应用程序,以便同时安全
我有一个网页,显示从服务器的大量的数据。 通信是通过ajax完成的。
每次用户交互并更改此数据(说用户A重命名的东西)它告诉服务器执行操作,服务器返回新的更改的数据。
如果用户B同时访问页面并创build一个新的数据对象,它将再次通过ajax告诉服务器,服务器将返回给用户的新对象。
在A的页面上,我们有一个重命名的对象的数据。 而在B的页面上,我们有一个新的对象的数据。 在服务器上,数据同时具有重命名的对象和新的对象。
当多个用户同时使用它时,我有什么select让页面与服务器保持同步?
这样的选项,比如locking整个页面,或者把整个状态转储给用户,每次改变都是可以避免的。
如果有帮助,在这个特定的例子中,网页调用一个静态webmethod,在数据库上运行一个存储过程。 存储过程将返回所有已经更改的数据。 静态webmethod然后将存储过程的返回转发给客户端。
赏金编辑:
你如何devise一个多用户的Web应用程序,它使用Ajax与服务器通信,但避免了并发问题?
即并发访问function和数据库上的数据,而没有任何数据或状态损坏的风险
概述:
- 介绍
- 服务器架构
- 客户端架构
- 更新案例
- 提交案例
- 冲突的情况下
- 性能和可伸缩性
你好,雷诺斯,
我不会在这里讨论任何特定的产品。 其他人提到的是一个很好的工具集,可以看看(也许将node.js添加到列表中)。
从架构的angular度来看,您似乎有版本控制软件中可以看到的相同的问题。 一个用户检查一个对象的变化,另一个用户想以另一种方式改变同一个对象=>冲突。 您必须将用户更改集成到对象中,同时能够及时高效地提供更新,检测并解决上述冲突。
如果我在你的鞋子里,我会开发这样的东西:
1.服务器端:
-
确定一个合理的级别,在这个级别上你可以定义我所说的“primefaces构件”(页面上的对象?对象内部的值?)。 这将取决于您的networking服务器,数据库和caching硬件,用户数量,对象数量等。不是一个容易做出的决定。
-
对于每个primefaces神器有:
- 一个应用程序范围的唯一标识
- 一个递增的版本号
- 用于写入访问的locking机制(可能是互斥锁)
- 一个小的历史或者在缓冲区内的“更新日志”(共享内存对于那些工作很好)。 单个键值对也可以,但可扩展性较差。 请参阅http://en.wikipedia.org/wiki/Circular_buffer
-
一个服务器或伪服务器组件,能够有效地将相关更新日志传递给连接的用户。 Observer-Pattern是你的朋友。
2.客户端:
-
一个能够长时间运行的HTTP连接到上述服务器的JavaScript客户端,或者使用轻量级轮询。
-
一个JavaScript artifact-updater组件,用于在连接的javascript客户端通知监视的工件历史logging中的更改时刷新站点内容。 (再次,观察者模式可能是一个不错的select)
-
一个JavaScript工件提交器组件,可能请求更改primefaces工件,尝试获取互斥锁。 它会通过比较已知客户端工件版本ID和当前服务器端工件版本ID来检测工件的状态是否在几秒前(通过JavaScript客户端的提交和提交过程因子)被另一用户更改。
-
一个JavaScript冲突解决者允许一个人改变是正确的决定。 你可能不想告诉用户“有人比你快,我删除了你的变化,哭了。” 从技术上的差异或更人性化的解决scheme看,许多select似乎都是可能的。
那么它将如何滚动…
案例1:更新的种类图:
- 浏览器呈现页面
- javascript“看到”每个具有至less一个值字段,unique和version-id的工件
- JavaScript客户端开始,请求从他们发现的版本开始“观察”发现的工件历史logging(旧的更改不是有趣的)
- 服务器进程logging请求并不断检查和/或发送历史logging
- 历史条目可能包含简单的通知“工件x已经改变,客户端请求数据”允许客户端独立轮询或完整的数据集“工件x已经改变为值foo”
- javascript artifact-updater会尽快获取新的值,只要知道它们已经更新。 它执行新的Ajax请求或由JavaScript客户端获取。
- 页面DOM内容被更新,用户可以select通知。 历史观看继续。
案例2:现在提交:
- 工件提交者从用户input中知道所需的新值,并向服务器发送一个变更请求
- 服务器端互斥量被获取
- 服务器接收到“嗨,我知道神器x从123版本的状态,让我把它设置为值foo pls。”
- 如果服务器版本的工件x等于(不能小于123),则接受新值,则生成124的新版本ID。
- 新的状态信息“更新到版本124”和可选的新值foo放在工件x的环形缓冲区(changelog / history)的开始处
- serverside互斥体被释放
- 请求工件提交者很高兴与新的ID一起接收提交确认。
- 与此同时,服务器端服务器组件保持轮询/将环形缓冲区推送到连接的客户端。 所有观察工件x的缓冲区的客户端都将得到新的状态信息和价值,在他们平常的延迟时间内(见案例1)
案例3:争议:
- 工件提交者从用户input中知道所需的新值,并向服务器发送一个变更请求
- 同时另一个用户成功地更新了相同的工件(见案例2),但是由于各种延迟,我们的其他用户还不知道。
- 所以一个服务器端的互斥量被获取(或等待直到“更快”的用户提交他的改变)
- 服务器收到“嘿,我知道从123版本神器x的状态,让我把它设置为值foo”。
- 在服务器上,神器x的版本现在已经是124了。 请求的客户端无法知道他将要覆盖的值。
- 很显然,服务器必须拒绝更改请求(不包括上帝的干预覆盖优先级),释放互斥量,并且足以将新版本ID和新值直接发送回客户端。
- 面临被拒绝的提交请求和更改请求用户还不知道的值,JavaScript构件提交者引用了显示并向用户解释问题的冲突parsing器。
- 被智能冲突parsing器JS提供了一些选项的用户被允许再次尝试改变该值。
- 一旦用户select了他认为正确的价值,stream程将从案例2开始(或案例3,如果其他人更快)
关于性能和可伸缩性的一些话
HTTP轮询与HTTP“推”
- 轮询创build请求,每秒一次,每秒5次,无论您认为是可接受的延迟。 如果你没有足够好地configuration你的(Apache?)和(php?)来成为“轻量级”的初学者,那么对于你的基础设施来说,这可能会相当残酷。 希望优化服务器端的轮询请求,使其运行的时间比轮询间隔的时间less得多。 将运行时间分成一半可能意味着将整个系统负载降低高达50%
- 通过HTTP推送(假设networking工作人员离得太远而无法支持他们)将需要您每次都为每个用户提供一个apache / lighthttpd进程。 为每个进程保留的驻留内存和系统总内存将是您将遇到的一个非常确定的缩放限制。 减less连接的内存占用将是必要的,以及限制连续的CPU和I / O工作在每个工作中完成的数量(你需要大量的睡眠/空闲时间)
后端缩放
- 忘记数据库和文件系统,你将需要一些基于共享内存的后端频繁轮询(如果客户端不直接轮询,每个运行的服务器进程将会)
- 如果你去memcache你可以扩展更好,但它仍然是昂贵的
- 即使您想要使多个前端服务器负载均衡,提交的互斥体也必须全局工作。
前端缩放
- 无论您是在轮询还是接收“推送”,都可以一步获取所有观看过的文物的信息。
“创意”的调整
- 如果客户端正在轮询并且许多用户倾向于观察相同的工件,则可以尝试将这些工件的历史logging发布为静态文件,从而允许apachecaching它,但是当工件发生更改时,可以在服务器端对其进行刷新。 这使得PHP / memcache在某些请求中不在游戏中。 Lighthttpd在提供静态文件方面非常有效。
- 使用像cotendo.com这样的内容交付networking来推送那里的神器历史。 推迟延迟会更大,但可扩展性是一个梦想
- 编写一个真正的服务器(不使用HTTP),用户使用Java或Flash(?)连接到。 您必须处理在一个服务器线程中服务许多用户。 通过开放式套接字循环,做(或委托)所需的工作。 可以通过分叉进程或启动更多服务器进行扩展。 互斥量必须保持全球独一无二。
- 根据负载情况,按照artifact-id范围对前端服务器和后端服务器进行分组。 这将允许更好地使用持久性存储器(没有数据库具有所有的数据),并且使得可以扩展混淆。 虽然你的JavaScript必须保持连接到多个服务器。
那么我希望这可以成为你自己的想法的开始。 我相信有更多的可能性。 我不仅欢迎任何批评或改进这个职位,维基启用。
克里斯托弗·斯特拉森
我知道这是一个古老的问题,但我以为我只是在join。
OT(操作转换)似乎非常适合您对并发和一致的多用户编辑的要求。 这是Google Docs中使用的技术 (也用于Google Wave):
有一个基于JS的库,可以使用由Google Wave团队成员编写的Operational Transforms – ShareJS( http://sharejs.org/ )。
如果你想的话,有一个完整的MVCnetworking框架 – DerbyJS( http://derbyjs.com/ )build立在ShareJS上,这一切都为你做。
它使用BrowserChannel在服务器和客户端之间进行通信(我相信WebSockets支持应该在工作中 – 以前通过Socket.IO在那里,但由于开发人员与Socket.io的问题而被取消)初学者文档是但是目前位稀疏。
我会考虑为每个数据集添加基于时间的修改邮票。 所以,如果你正在更新数据库表,你会相应地改变修改的时间戳。 使用AJAX,您可以将客户端修改的时间戳与数据源的时间戳进行比较 – 如果用户在后面,则更新显示。 与本网站如何定期检查问题类似,以查看是否有其他人在您input答案的同时回答问题。
您需要使用推技术(也称为Comet或反向Ajax)将更改传播给用户。 目前可用的最佳技术似乎是Ajax长轮询,但并不是每个浏览器都支持,所以需要回退。 幸运的是,已经有解决scheme可以为您处理。 其中有:orbited.org和已经提到的socket.io。
将来会有更简单的方法来做这个叫做WebSockets的方法,但是现在还不确定什么时候这个标准会在黄金时间准备好,因为这个标准的当前状态存在安全问题。
新对象在数据库中不应该存在并发问题。 但是,当用户编辑一个对象时,服务器需要有一些逻辑来检查对象是否被编辑或删除。 如果对象已被删除,那么解决scheme也很简单:只需放弃编辑。
但最困难的问题出现在多个用户同时编辑同一个对象的时候。 如果用户1和用户2同时开始编辑对象,则他们将对相同的数据进行编辑。 假设用户1所做的更改首先发送到服务器,而用户2仍在编辑数据。 然后您有两个select:您可以尝试将用户1的更改合并到用户2的数据中,或者您可以告诉用户2他的数据已过期,并在他的数据发送到服务器后立即向他显示错误消息。 后者在这里不是非常用户友好的select,但是前者很难实现。
EtherPad是第一次真正实现这个目标的less数几个实现之一 ,它被Google收购。 我相信他们在Google Docs和Google Wave中使用了EtherPad的一些技术,但我无法确定。 谷歌也开源了EtherPad,所以也许值得一看,这取决于你想要做什么。
做这个同时编辑的东西真的不容易,因为由于延迟,不可能在网上进行primefaces操作。 也许这篇文章将帮助你更多地了解这个话题。
试图自己写这些都是一件很大的工作,要把它做好是非常困难的。 一种select是使用构build的框架,以使客户端与数据库保持同步,并实时与客户端保持同步。
我发现Meteor框架可以做到这一点( http://docs.meteor.com/#reactivity )。
“meteor包含了反应式编程的概念,这意味着你可以用简单的命令式编写你的代码,只要你的代码依赖于数据的改变,结果就会自动重新计算。
“这种简单的模式(反应式计算+反应式数据源)具有广泛的适用性,程序员可以避免编写取消订阅/重新订阅调用,并确保他们在正确的时间被调用,从而消除整个类别的数据传播代码,否则会阻塞应用程序与易错逻辑“。
我不敢相信没有人提到meteor 。 这确实是一个新的和不成熟的框架(并且只是官方支持一个数据库),但是它需要所有的咕噜工作,并且像海报所描述的那样从一个多用户应用程序中思考。 事实上,你不能build立一个多用户的实时更新应用程序。 这里有一个简要的总结:
- 一切都在node.js(JavaScript或CoffeeScript)中,所以你可以在客户端和服务器之间共享诸如validation之类的东西。
- 它使用websockets,但可以退回到旧版浏览器
- 它着重于即时更新本地对象(即UI感觉很快),并在后台发送到服务器的更改。 只有primefaces更新才能使混音更新更简单。 在服务器上拒绝的更新被回滚。
- 作为奖励,它会为您处理实时代码重新加载,并且即使在应用程序发生根本性变化时也会保留用户状态。
meteor很简单,我build议你至less看看它的想法偷。
这些维基百科页面可能有助于为deviseajax Web应用程序添加并发性和并发计算的帮助,这些应用程序可以在消息传递模式中 抽取或推送状态事件 ( EDA ) 消息 。 基本上,消息被复制到频道订户,其响应改变事件和同步请求。
- https://en.wikipedia.org/wiki/Category:Concurrency_control
- https://en.wikipedia.org/wiki/Distributed_concurrency_control
- https://en.wikipedia.org/wiki/CAP_theorem
- https://en.wikipedia.org/wiki/Operational_transformation
- https://en.wikipedia.org/wiki/Fallacies_of_Distributed_Computing
并发的基于Web的协同软件有很多种forms。
有许多用于etherpad-lite的HTTP API客户端库 ,一个协作的实时编辑器 。
django-realtime-playground使用各种实时技术(如Socket.io)在Django中实现实时聊天应用程序。
AppEngine和AppScale都实现了AppEngine Channel API ; 这与Google实时API截然不同, Google实时API由googledrive / realtime-playground进行演示。
服务器端推技术是这里的方法。 彗星是(或者是)一个stream行词。
你采取的特定方向很大程度上取决于你的服务器堆栈,以及你是多么灵活。 如果可以的话,我会看看socket.io ,它提供了一个跨浏览器的websocket实现,它提供了一个非常简单的方式来与服务器进行双向通信,从而允许服务器将更新推送到客户端。
特别是看这个图书馆作者的演示,这个演示几乎就是你描述的情况。