如何设计一个多用户ajax Web应用程序是同时安全的

Ray*_*nos 94 javascript ajax web-applications multi-user

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

每次用户交互并更改此数据(假设用户A重命名某些内容)时,它都会告诉服务器执行操作,服务器返回新更改的数据.

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

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

当多个用户同时使用该页面时,我有什么选择让页面与服务器保持同步?

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

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

赏金编辑:

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

即对数据库的功能和数据进行并发访问,而不存在数据或状态损坏的风险

Chr*_*sen 157

概述:

  • 介绍
  • 服务器架构
  • 客户架构
  • 更新案例
  • 提交案例
  • 冲突案件
  • 性能和可扩展性

嗨雷诺斯,

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

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

如果我在你的鞋子里,我会开发这样的东西:

1.服务器端:

  • 确定一个合理的级别,你可以定义我所谓的"原子工件"(页面?页面上的对象?对象内部的值?).这取决于您的Web服务器,数据库和缓存硬件,用户数量,对象数量等.这不是一个简单的决定.

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

    • 应用程序范围的唯一ID
    • 递增版本ID
    • 用于写访问的锁定机制(可能是互斥)
    • 环形缓冲区内的小历史或"更改日志"(共享内存适用于那些).单个键值对可能也可以,但可扩展性较差.见http://en.wikipedia.org/wiki/Circular_buffer
  • 服务器或伪服务器组件,能够有效地向相关用户提供相关的更改日志.观察者模式是你的朋友.

2.客户端:

  • 一个javascript客户端,它能够与上面的服务器进行长时间运行的HTTP连接,或者使用轻量级轮询.

  • 一个javascript artifact-updater组件,当连接的javascript客户端通知被监视的工件历史记录中的更改时刷新站点内容.(再次观察者模式可能是一个不错的选择)

  • 一个javascript artifact-committer组件,可能会请求更改原子工件,尝试获取互斥锁.它将通过比较已知的clientside artifact-version-id和当前的serverside artifact-version-id来检测工件的状态是否在几秒钟之前被另一个用户更改(javascript客户端和提交进程因素的延迟).

  • 一个javascript冲突解决方案,允许人类改变是正确的决定.你可能不想只告诉用户"有人比你快.我删除了你的改变.去哭吧." 很多选择来自相当技术的差异或更加用户友好的解决方案似乎是可能的.

那怎么滚...

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

  • 浏览器呈现页面
  • javascript"看到"工件,每个工件至少有一个值字段,unique-和version-id
  • javascript客户端启动,请求从他们找到的版本开始"观察"找到的工件历史记录(较旧的更改不感兴趣)
  • 服务器进程记录请求并持续检查和/或发送历史记录
  • 历史条目可能包含简单通知"工件x已更改,客户端请求数据"允许客户端独立轮询或完整数据集"工件x已更改为值foo"
  • javascript artifact-updater尽可能地在知道更新后获取新值.它执行新的ajax请求或由javascript客户端提供.
  • 更新页面DOM内容,可选地通知用户.历史观察仍在继续.

案例2:现在提交:

  • artifact-committer从用户输入中获知所需的新值,并向服务器发送更改请求
  • 服务器端互斥是被收购的
  • 服务器收到"嘿,我从版本123知道工件x的状态,让我把它设置为值foo pls."
  • 如果工件x的Serverside版本等于(不能小于)123接受新值,则生成新版本ID 124.
  • 新的状态信息"更新到版本124"和可选的新值foo放在工件x的ringbuffer的开头(更改日志/历史记录)
  • serverside mutex发布
  • 请求工件提交者很高兴收到提交确认和新ID.
  • 同时,服务器端服务器组件继续轮询/推送环缓冲区到连接的客户端.观察工件x的缓冲区的所有客户端将在其通常的延迟期间获得新的状态信息和值(参见案例1).

案例3:冲突:

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

关于性能和可伸缩性的一些话

HTTP轮询与HTTP"推送"

  • 轮询创建请求,每秒一次,每秒5次,无论您认为什么是可接受的延迟.如果你没有将你的(Apache?)和(php?)配置得足以成为"轻量级"的启动器,这对你的基础设施来说可能相当残酷.希望在服务器端优化轮询请求,使其运行的时间远小于轮询间隔的长度.将运行时分成两半可能意味着将整个系统负载降低多达50%,
  • 通过HTTP推送(假设webworkers太过离谱,以支持他们)会要求你有每个用户可用的一个阿帕奇/ lighthttpd过程中所有的时间.为每个进程和系统总内存保留的驻留内存将是您将遇到的一个非常确定的扩展限制.减少连接的内存占用量是必要的,并且限制每个中的连续CPU和I/O工作量(您需要大量的睡眠/空闲时间)

后端扩展

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

前端缩放

  • 无论您是轮询还是接收"推送",都要尝试在一个步骤中获取所有监视工件的信息.

"创意"调整

  • 如果客户端正在轮询并且许多用户倾向于观看相同的工件,您可以尝试将这些工件的历史记录发布为静态文件,允许apache缓存它,然后在工件更改时在服务器端刷新它.这会将PHP/memcache从游戏中取出一些请求.Lighthttpd在提供静态文件方面非常有效.
  • 使用像cotendo.com这样的内容传送网络来推送工件历史记录.推送延迟将更大,但可扩展性是一个梦想
  • 编写用户使用java或flash(?)连接的真实服务器(不使用HTTP).您必须处理在一个服务器线程中为许多用户提供服务.循环通过打开的插座,完成(或委托)所需的工作.可以通过分叉过程或启动更多服务器进行扩展.互斥体必须保持全球独特.
  • 根据负载方案,按工件ID范围对前端服务器和后端服务器进行分组.这将允许更好地使用持久性存储器(没有数据库具有所有数据)并且可以扩展静音.您的javascript必须同时保持与多个服务器的连接.

我希望这可以成为你自己想法的开始.我相信还有更多的可能性.我非常欢迎任何批评或改进此帖子,维基启用.

Christoph Strasen

  • +500你肯定是这一个.这是一个很好的答案. (2认同)

vic*_*ooi 13

我知道这是一个老问题,但我想我只是想进来.

OT(操作转换)似乎非常适合您对并发和一致的多用户编辑的要求.这是Google Docs中使用的一种技术(也用于Google Wave):

有一个基于JS的库使用操作变换 - ShareJS(http://sharejs.org/),由Google Wave团队的成员编写.

如果你愿意,还有一个完整的MVC网络框架 - 基于ShareJS的DerbyJS(http://derbyjs.com/)可以为你完成所有工作.

它使用BrowserChannel进行服务器和客户端之间的通信(我相信WebSockets支持应该在工作中 - 它之前是通过Socket.IO,但是由于开发人员的Socket.io问题而被取出)初学者文档是一个但是,此刻有点稀疏.


Chr*_*ker 5

我会考虑为每个数据集添加基于时间的修改图章.因此,如果要更新数据库表,则应相应地更改已修改的时间戳.使用AJAX,您可以将客户端的修改时间戳与数据源的时间戳进行比较 - 如果用户落后,则更新显示.与此网站定期检查问题的方式类似,以确定在您输入答案时是否有其他人已回答.